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 <memory>
50 : #include <vector>
51 :
52 : // ObjexxFCL Headers
53 : #include <ObjexxFCL/Array1.hh>
54 :
55 : // EnergyPlus Headers
56 : #include <EnergyPlus/CTElectricGenerator.hh>
57 : #include <EnergyPlus/CurveManager.hh>
58 : #include <EnergyPlus/Data/EnergyPlusData.hh>
59 : #include <EnergyPlus/DataEnvironment.hh>
60 : #include <EnergyPlus/DataGlobalConstants.hh>
61 : #include <EnergyPlus/DataHVACGlobals.hh>
62 : #include <EnergyPlus/DataHeatBalance.hh>
63 : #include <EnergyPlus/DataIPShortCuts.hh>
64 : #include <EnergyPlus/DataPrecisionGlobals.hh>
65 : #include <EnergyPlus/EMSManager.hh>
66 : #include <EnergyPlus/ElectricPowerServiceManager.hh>
67 : #include <EnergyPlus/FuelCellElectricGenerator.hh>
68 : #include <EnergyPlus/HeatBalanceInternalHeatGains.hh>
69 : #include <EnergyPlus/ICEngineElectricGenerator.hh>
70 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
71 : #include <EnergyPlus/MicroCHPElectricGenerator.hh>
72 : #include <EnergyPlus/MicroturbineElectricGenerator.hh>
73 : #include <EnergyPlus/OutputProcessor.hh>
74 : #include <EnergyPlus/OutputReportPredefined.hh>
75 : #include <EnergyPlus/PVWatts.hh>
76 : #include <EnergyPlus/Photovoltaics.hh>
77 : #include <EnergyPlus/Plant/DataPlant.hh>
78 : #include <EnergyPlus/Plant/PlantLocation.hh>
79 : #include <EnergyPlus/PlantUtilities.hh>
80 : #include <EnergyPlus/ScheduleManager.hh>
81 : #include <EnergyPlus/UtilityRoutines.hh>
82 : #include <EnergyPlus/WindTurbine.hh>
83 : #include <EnergyPlus/ZoneTempPredictorCorrector.hh>
84 :
85 : #ifdef DEBUG_ARITHM_GCC_OR_CLANG
86 : # include <EnergyPlus/fenv_missing.h>
87 : #endif
88 :
89 : #ifdef DEBUG_ARITHM_MSVC
90 : # include <cfloat>
91 : #endif
92 :
93 : namespace EnergyPlus {
94 :
95 801 : void createFacilityElectricPowerServiceObject(const EnergyPlusData &state)
96 : {
97 801 : state.dataElectPwrSvcMgr->facilityElectricServiceObj = std::make_unique<ElectricPowerServiceManager>();
98 801 : }
99 :
100 2828408 : void initializeElectricPowerServiceZoneGains(const EnergyPlusData &state) // namespace routine for handling call from InternalHeatGains
101 : {
102 : // internal zone gains need to be re initialized for begin new environment earlier than the main call into manage electric power service
103 2828408 : if (state.dataElectPwrSvcMgr->facilityElectricServiceObj->newEnvironmentInternalGainsFlag && state.dataGlobal->BeginEnvrnFlag) {
104 6496 : state.dataElectPwrSvcMgr->facilityElectricServiceObj->reinitZoneGainsAtBeginEnvironment();
105 6496 : state.dataElectPwrSvcMgr->facilityElectricServiceObj->newEnvironmentInternalGainsFlag = false;
106 : }
107 2828408 : if (!state.dataGlobal->BeginEnvrnFlag) {
108 2821912 : state.dataElectPwrSvcMgr->facilityElectricServiceObj->newEnvironmentInternalGainsFlag = true;
109 : }
110 2828408 : }
111 :
112 13483490 : void ElectricPowerServiceManager::manageElectricPowerService(
113 : EnergyPlusData &state,
114 : bool const firstHVACIteration,
115 : bool &SimElecCircuits, // simulation convergence flag
116 : bool const UpdateMetersOnly // if true then don't resimulate generators, just update meters.
117 : )
118 : {
119 13483490 : if (getInputFlag_) {
120 801 : getPowerManagerInput(state);
121 801 : getInputFlag_ = false;
122 : }
123 :
124 13483490 : if (state.dataGlobal->MetersHaveBeenInitialized && setupMeterIndexFlag_) {
125 800 : setupMeterIndices(state);
126 800 : setupMeterIndexFlag_ = false;
127 : }
128 :
129 13483490 : if (state.dataGlobal->BeginEnvrnFlag && newEnvironmentFlag_) {
130 6496 : reinitAtBeginEnvironment();
131 6496 : newEnvironmentFlag_ = false;
132 : }
133 13483490 : if (!state.dataGlobal->BeginEnvrnFlag) {
134 13426430 : newEnvironmentFlag_ = true;
135 : }
136 :
137 : // retrieve data from meters for demand and production
138 13483490 : totalBldgElecDemand_ =
139 13483490 : GetInstantMeterValue(state, elecFacilityMeterIndex_, OutputProcessor::TimeStepType::Zone) / state.dataGlobal->TimeStepZoneSec;
140 13483490 : totalHVACElecDemand_ =
141 13483490 : GetInstantMeterValue(state, elecFacilityMeterIndex_, OutputProcessor::TimeStepType::System) / (state.dataHVACGlobal->TimeStepSysSec);
142 13483490 : totalElectricDemand_ = totalBldgElecDemand_ + totalHVACElecDemand_;
143 13483490 : elecProducedPVRate_ =
144 13483490 : GetInstantMeterValue(state, elecProducedPVMeterIndex_, OutputProcessor::TimeStepType::System) / (state.dataHVACGlobal->TimeStepSysSec);
145 13483490 : elecProducedWTRate_ =
146 13483490 : GetInstantMeterValue(state, elecProducedWTMeterIndex_, OutputProcessor::TimeStepType::System) / (state.dataHVACGlobal->TimeStepSysSec);
147 13483490 : elecProducedStorageRate_ =
148 13483490 : GetInstantMeterValue(state, elecProducedStorageMeterIndex_, OutputProcessor::TimeStepType::System) / (state.dataHVACGlobal->TimeStepSysSec);
149 13483490 : elecProducedCoGenRate_ =
150 13483490 : GetInstantMeterValue(state, elecProducedCoGenMeterIndex_, OutputProcessor::TimeStepType::System) / (state.dataHVACGlobal->TimeStepSysSec);
151 13483490 : elecProducedPowerConversionRate_ = GetInstantMeterValue(state, elecProducedPowerConversionMeterIndex_, OutputProcessor::TimeStepType::System) /
152 13483490 : (state.dataHVACGlobal->TimeStepSysSec);
153 :
154 13483490 : wholeBldgRemainingLoad_ = totalElectricDemand_;
155 :
156 13483490 : if (UpdateMetersOnly) { // just update record keeping, don't resimulate load centers
157 3588830 : if (facilityPowerInTransformerPresent_) {
158 34411 : facilityPowerInTransformerObj_->manageTransformers(state, 0.0);
159 : }
160 :
161 3588830 : updateWholeBuildingRecords(state);
162 3588830 : return;
163 : }
164 :
165 19231887 : for (auto &e : elecLoadCenterObjs) {
166 9337227 : e->manageElecLoadCenter(state, firstHVACIteration, wholeBldgRemainingLoad_);
167 9894660 : }
168 :
169 9894660 : updateWholeBuildingRecords(state);
170 : // The transformer call should be put outside of the "Load Center" loop because
171 : // 1) A transformer may be for utility, not for load center
172 : // 2) A tansformer may be shared by multiple load centers
173 9894660 : if (facilityPowerInTransformerPresent_) {
174 103709 : facilityPowerInTransformerObj_->manageTransformers(state, 0.0);
175 : }
176 :
177 9894660 : updateWholeBuildingRecords(state);
178 9894660 : if (powerOutTransformerObj_ != nullptr) {
179 8508 : powerOutTransformerObj_->manageTransformers(state, electSurplusRate_);
180 : }
181 :
182 : // Need to simulate through the Elec Manager at least twice to ensure that Heat Recovery information is included.
183 : // recheck this, may not be needed now that load centers are called more often.
184 : // Does the IF condition also need to check if any thermal following strategies have been specified?
185 : // That is, if only electrical following schemes, don't need to resimulate?
186 9894660 : if (firstHVACIteration) {
187 6753598 : SimElecCircuits = true;
188 : } else {
189 3141062 : SimElecCircuits = false;
190 : }
191 : }
192 :
193 6496 : void ElectricPowerServiceManager::reinitZoneGainsAtBeginEnvironment()
194 : {
195 6496 : if (facilityPowerInTransformerPresent_) {
196 117 : facilityPowerInTransformerObj_->reinitZoneGainsAtBeginEnvironment();
197 : }
198 6496 : if (powerOutTransformerObj_ != nullptr) {
199 5 : powerOutTransformerObj_->reinitZoneGainsAtBeginEnvironment();
200 : }
201 6496 : if (numLoadCenters_ > 0) {
202 10708 : for (auto &e : elecLoadCenterObjs) {
203 5362 : e->reinitZoneGainsAtBeginEnvironment();
204 5346 : }
205 : }
206 6496 : }
207 :
208 801 : void ElectricPowerServiceManager::getPowerManagerInput(EnergyPlusData &state)
209 : {
210 : static constexpr std::string_view routineName = "ElectricPowerServiceManager::getPowerManagerInput ";
211 :
212 801 : auto &s_ipsc = state.dataIPShortCut;
213 :
214 801 : numLoadCenters_ = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "ElectricLoadCenter:Distribution");
215 :
216 801 : if (numLoadCenters_ > 0) {
217 48 : for (int iLoadCenterNum = 1; iLoadCenterNum <= numLoadCenters_; ++iLoadCenterNum) {
218 : // call Electric Power Load Center constructor, in place
219 25 : elecLoadCenterObjs.emplace_back(new ElectPowerLoadCenter(state, iLoadCenterNum));
220 : }
221 : } else {
222 : // issue #4639. see if there are any generators, inverters, converters, or storage devcies, that really need a ElectricLoadCenter:Distribution
223 778 : bool errorsFound(false);
224 778 : int numGenLists = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "ElectricLoadCenter:Generators");
225 778 : if (numGenLists > 0) {
226 0 : ShowSevereError(state, "ElectricLoadCenter:Generators input object requires an ElectricLoadCenterDistribution input object.");
227 0 : errorsFound = true;
228 : }
229 778 : int numInverters = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "ElectricLoadCenter:Inverter:Simple");
230 778 : numInverters += state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "ElectricLoadCenter:Inverter:FunctionOfPower");
231 778 : numInverters += state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "ElectricLoadCenter:Inverter:LookUpTable");
232 778 : if (numInverters > 0) {
233 0 : ShowSevereError(state, "ElectricLoadCenter:Inverter:* input objects require an ElectricLoadCenter:Distribution input object.");
234 0 : errorsFound = true;
235 : }
236 778 : int numStorage = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "ElectricLoadCenter:Storage:Simple");
237 778 : numStorage += state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "ElectricLoadCenter:Storage:Battery");
238 778 : if (numStorage > 0) {
239 0 : ShowSevereError(state, "ElectricLoadCenter:Storage:* input objects require an ElectricLoadCenter:Distribution input object.");
240 0 : errorsFound = true;
241 : }
242 778 : int numGenerators = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "Generator:InternalCombustionEngine");
243 778 : numGenerators += state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "Generator:CombustionTurbine");
244 778 : numGenerators += state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "Generator:MicroCHP");
245 778 : numGenerators += state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "Generator:FuelCell");
246 778 : numGenerators += state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "Generator:Photovoltaic");
247 778 : numGenerators += state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "Generator:WindTurbine");
248 778 : if (numGenerators > 0) {
249 0 : ShowSevereError(state, "Electric generator input objects require an ElectricLoadCenter:Distribution input object.");
250 0 : errorsFound = true;
251 : }
252 :
253 778 : if (errorsFound) {
254 0 : ShowFatalError(state, "Simulation halted because of missing input objects related to ElectricLoadCenter.");
255 : }
256 :
257 : // if user input did not include an Electric Load center, create a simple default one here for reporting purposes
258 : // but only if there are any other electricity components set up (yet) for metering
259 778 : int anyElectricityPresent = GetMeterIndex(state, "ELECTRICITY:FACILITY");
260 778 : int anyPlantLoadProfilePresent = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "LoadProfile:Plant");
261 778 : if (anyElectricityPresent > -1 || anyPlantLoadProfilePresent > 0) {
262 710 : elecLoadCenterObjs.emplace_back(new ElectPowerLoadCenter(state, 0));
263 710 : numLoadCenters_ = 1;
264 : }
265 : }
266 :
267 : // see if there are any transformers of the type PowerInFromGrid
268 801 : numTransformers_ = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "ElectricLoadCenter:Transformer");
269 :
270 801 : if (numTransformers_ > 0) {
271 : int numAlphas; // Number of elements in the alpha array
272 : int numNums; // Number of elements in the numeric array
273 : int iOStat; // IO Status when calling get input subroutine
274 13 : bool foundInFromGridTransformer = false;
275 :
276 13 : s_ipsc->cCurrentModuleObject = "ElectricLoadCenter:Transformer";
277 26 : for (int loopTransformer = 1; loopTransformer <= numTransformers_; ++loopTransformer) {
278 26 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
279 13 : s_ipsc->cCurrentModuleObject,
280 : loopTransformer,
281 13 : s_ipsc->cAlphaArgs,
282 : numAlphas,
283 13 : s_ipsc->rNumericArgs,
284 : numNums,
285 : iOStat,
286 13 : s_ipsc->lNumericFieldBlanks,
287 13 : s_ipsc->lAlphaFieldBlanks,
288 13 : s_ipsc->cAlphaFieldNames,
289 13 : s_ipsc->cNumericFieldNames);
290 :
291 13 : if (Util::SameString(s_ipsc->cAlphaArgs(3), "PowerInFromGrid")) {
292 12 : if (!foundInFromGridTransformer) {
293 12 : foundInFromGridTransformer = true;
294 12 : facilityPowerInTransformerName_ = s_ipsc->cAlphaArgs(1);
295 12 : facilityPowerInTransformerPresent_ = true;
296 : } else {
297 : // should only have one transformer in input that is PowerInFromGrid
298 0 : ShowWarningError(state, format("{}{}=\"{}\", invalid entry.", routineName, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
299 0 : ShowContinueError(state, format("Invalid {} = {}", s_ipsc->cAlphaFieldNames(3), s_ipsc->cAlphaArgs(3)));
300 0 : ShowContinueError(state,
301 : "Only one transformer with Usage PowerInFromGrid can be used, first one in input file will be used and the "
302 : "simulation continues...");
303 : }
304 1 : } else if (Util::SameString(s_ipsc->cAlphaArgs(3), "PowerOutToGrid")) {
305 1 : if (powerOutTransformerObj_ == nullptr) {
306 1 : ++numPowerOutTransformers_;
307 1 : powerOutTransformerName_ = s_ipsc->cAlphaArgs(1);
308 1 : powerOutTransformerObj_ = std::make_unique<ElectricTransformer>(state, powerOutTransformerName_);
309 :
310 : } else {
311 0 : ShowWarningError(state,
312 : "Found more than one transformer set to PowerOutFromOnsiteGeneration, however only the first one will be used.");
313 : }
314 : }
315 : }
316 13 : if (foundInFromGridTransformer) {
317 : // call transformer constructor
318 12 : facilityPowerInTransformerObj_ = std::make_unique<ElectricTransformer>(state, facilityPowerInTransformerName_);
319 : }
320 : } // if transformers
321 :
322 801 : if (numLoadCenters_ > 0) {
323 1466 : SetupOutputVariable(state,
324 : "Facility Total Purchased Electricity Rate",
325 : Constant::Units::W,
326 733 : electPurchRate_,
327 : OutputProcessor::TimeStepType::System,
328 : OutputProcessor::StoreType::Average,
329 733 : name_);
330 1466 : SetupOutputVariable(state,
331 : "Facility Total Purchased Electricity Energy",
332 : Constant::Units::J,
333 733 : electricityPurch_,
334 : OutputProcessor::TimeStepType::System,
335 : OutputProcessor::StoreType::Sum,
336 733 : name_,
337 : Constant::eResource::ElectricityPurchased,
338 : OutputProcessor::Group::Plant,
339 : OutputProcessor::EndUseCat::Cogeneration);
340 :
341 1466 : SetupOutputVariable(state,
342 : "Facility Total Surplus Electricity Rate",
343 : Constant::Units::W,
344 733 : electSurplusRate_,
345 : OutputProcessor::TimeStepType::System,
346 : OutputProcessor::StoreType::Average,
347 733 : name_);
348 1466 : SetupOutputVariable(state,
349 : "Facility Total Surplus Electricity Energy",
350 : Constant::Units::J,
351 733 : electricitySurplus_,
352 : OutputProcessor::TimeStepType::System,
353 : OutputProcessor::StoreType::Sum,
354 733 : name_,
355 : Constant::eResource::ElectricitySurplusSold,
356 : OutputProcessor::Group::Plant,
357 : OutputProcessor::EndUseCat::Cogeneration);
358 :
359 1466 : SetupOutputVariable(state,
360 : "Facility Net Purchased Electricity Rate",
361 : Constant::Units::W,
362 733 : electricityNetRate_,
363 : OutputProcessor::TimeStepType::System,
364 : OutputProcessor::StoreType::Average,
365 733 : name_);
366 1466 : SetupOutputVariable(state,
367 : "Facility Net Purchased Electricity Energy",
368 : Constant::Units::J,
369 733 : electricityNet_,
370 : OutputProcessor::TimeStepType::System,
371 : OutputProcessor::StoreType::Sum,
372 733 : name_,
373 : Constant::eResource::ElectricityNet,
374 : OutputProcessor::Group::Plant,
375 : OutputProcessor::EndUseCat::Cogeneration);
376 :
377 1466 : SetupOutputVariable(state,
378 : "Facility Total Building Electricity Demand Rate",
379 : Constant::Units::W,
380 733 : totalBldgElecDemand_,
381 : OutputProcessor::TimeStepType::System,
382 : OutputProcessor::StoreType::Average,
383 733 : name_);
384 1466 : SetupOutputVariable(state,
385 : "Facility Total HVAC Electricity Demand Rate",
386 : Constant::Units::W,
387 733 : totalHVACElecDemand_,
388 : OutputProcessor::TimeStepType::System,
389 : OutputProcessor::StoreType::Average,
390 733 : name_);
391 1466 : SetupOutputVariable(state,
392 : "Facility Total Electricity Demand Rate",
393 : Constant::Units::W,
394 733 : totalElectricDemand_,
395 : OutputProcessor::TimeStepType::System,
396 : OutputProcessor::StoreType::Average,
397 733 : name_);
398 :
399 1466 : SetupOutputVariable(state,
400 : "Facility Total Produced Electricity Rate",
401 : Constant::Units::W,
402 733 : electProdRate_,
403 : OutputProcessor::TimeStepType::System,
404 : OutputProcessor::StoreType::Average,
405 733 : name_);
406 1466 : SetupOutputVariable(state,
407 : "Facility Total Produced Electricity Energy",
408 : Constant::Units::J,
409 733 : electricityProd_,
410 : OutputProcessor::TimeStepType::System,
411 : OutputProcessor::StoreType::Sum,
412 733 : name_);
413 :
414 733 : reportPVandWindCapacity(state);
415 :
416 733 : sumUpNumberOfStorageDevices();
417 :
418 733 : checkLoadCenters(state);
419 : }
420 801 : }
421 :
422 800 : void ElectricPowerServiceManager::setupMeterIndices(EnergyPlusData &state)
423 : {
424 1600 : elecFacilityMeterIndex_ = GetMeterIndex(state, "ELECTRICITY:FACILITY");
425 1600 : elecProducedCoGenMeterIndex_ = GetMeterIndex(state, "COGENERATION:ELECTRICITYPRODUCED");
426 1600 : elecProducedPVMeterIndex_ = GetMeterIndex(state, "PHOTOVOLTAIC:ELECTRICITYPRODUCED");
427 1600 : elecProducedWTMeterIndex_ = GetMeterIndex(state, "WINDTURBINE:ELECTRICITYPRODUCED");
428 1600 : elecProducedStorageMeterIndex_ = GetMeterIndex(state, "ELECTRICSTORAGE:ELECTRICITYPRODUCED");
429 800 : elecProducedPowerConversionMeterIndex_ = GetMeterIndex(state, "POWERCONVERSION:ELECTRICITYPRODUCED");
430 :
431 800 : if (numLoadCenters_ > 0) {
432 1466 : for (auto &e : elecLoadCenterObjs) {
433 734 : e->setupLoadCenterMeterIndices(state);
434 732 : }
435 : }
436 800 : if (facilityPowerInTransformerPresent_) {
437 12 : facilityPowerInTransformerObj_->setupMeterIndices(state);
438 : }
439 800 : }
440 :
441 6496 : void ElectricPowerServiceManager::reinitAtBeginEnvironment()
442 : {
443 6496 : wholeBldgRemainingLoad_ = 0.0;
444 6496 : electricityProd_ = 0.0;
445 6496 : electProdRate_ = 0.0;
446 6496 : electricityPurch_ = 0.0;
447 6496 : electPurchRate_ = 0.0;
448 6496 : electSurplusRate_ = 0.0;
449 6496 : electricitySurplus_ = 0.0;
450 6496 : electricityNetRate_ = 0.0;
451 6496 : electricityNet_ = 0.0;
452 6496 : totalBldgElecDemand_ = 0.0;
453 6496 : totalHVACElecDemand_ = 0.0;
454 6496 : totalElectricDemand_ = 0.0;
455 6496 : elecProducedPVRate_ = 0.0;
456 6496 : elecProducedWTRate_ = 0.0;
457 6496 : elecProducedStorageRate_ = 0.0;
458 6496 : elecProducedCoGenRate_ = 0.0;
459 :
460 6496 : if (numLoadCenters_ > 0) {
461 12176 : for (auto &e : elecLoadCenterObjs) {
462 6097 : e->reinitAtBeginEnvironment();
463 6079 : }
464 : }
465 6496 : if (facilityPowerInTransformerPresent_) {
466 129 : facilityPowerInTransformerObj_->reinitAtBeginEnvironment();
467 : }
468 6496 : if (powerOutTransformerObj_ != nullptr) {
469 6 : powerOutTransformerObj_->reinitAtBeginEnvironment();
470 : }
471 6496 : }
472 :
473 800 : void ElectricPowerServiceManager::verifyCustomMetersElecPowerMgr(EnergyPlusData &state)
474 : {
475 1534 : for (std::size_t loop = 0; loop < elecLoadCenterObjs.size(); ++loop) {
476 734 : elecLoadCenterObjs[loop]->setupLoadCenterMeterIndices(state);
477 : }
478 800 : }
479 :
480 23378150 : void ElectricPowerServiceManager::updateWholeBuildingRecords(EnergyPlusData &state)
481 : {
482 :
483 : // main panel balancing.
484 23378150 : totalBldgElecDemand_ =
485 23378150 : GetInstantMeterValue(state, elecFacilityMeterIndex_, OutputProcessor::TimeStepType::Zone) / state.dataGlobal->TimeStepZoneSec;
486 23378150 : totalHVACElecDemand_ =
487 23378150 : GetInstantMeterValue(state, elecFacilityMeterIndex_, OutputProcessor::TimeStepType::System) / (state.dataHVACGlobal->TimeStepSysSec);
488 23378150 : totalElectricDemand_ = totalBldgElecDemand_ + totalHVACElecDemand_;
489 23378150 : elecProducedPVRate_ =
490 23378150 : GetInstantMeterValue(state, elecProducedPVMeterIndex_, OutputProcessor::TimeStepType::System) / (state.dataHVACGlobal->TimeStepSysSec);
491 23378150 : elecProducedWTRate_ =
492 23378150 : GetInstantMeterValue(state, elecProducedWTMeterIndex_, OutputProcessor::TimeStepType::System) / (state.dataHVACGlobal->TimeStepSysSec);
493 23378150 : elecProducedStorageRate_ =
494 23378150 : GetInstantMeterValue(state, elecProducedStorageMeterIndex_, OutputProcessor::TimeStepType::System) / (state.dataHVACGlobal->TimeStepSysSec);
495 23378150 : elecProducedCoGenRate_ =
496 23378150 : GetInstantMeterValue(state, elecProducedCoGenMeterIndex_, OutputProcessor::TimeStepType::System) / (state.dataHVACGlobal->TimeStepSysSec);
497 23378150 : elecProducedPowerConversionRate_ = GetInstantMeterValue(state, elecProducedPowerConversionMeterIndex_, OutputProcessor::TimeStepType::System) /
498 23378150 : (state.dataHVACGlobal->TimeStepSysSec);
499 :
500 23378150 : electProdRate_ = elecProducedCoGenRate_ + elecProducedPVRate_ + elecProducedWTRate_ + elecProducedStorageRate_ + elecProducedPowerConversionRate_;
501 23378150 : electricityProd_ = electProdRate_ * state.dataHVACGlobal->TimeStepSysSec; // whole building
502 :
503 : // Report the Total Electric Power Purchased [W], If negative then there is extra power to be sold or stored.
504 23378150 : electPurchRate_ = totalElectricDemand_ - electProdRate_;
505 : // Check this value against a tolerance to aid in reporting.
506 23378150 : if (std::abs(electPurchRate_) < 0.0001) {
507 4953343 : electPurchRate_ = 0.0;
508 : }
509 23378150 : if (electPurchRate_ < 0.0) {
510 99275 : electPurchRate_ = 0.0; // don't want negative purchased...
511 : }
512 :
513 : // Report the Total Electric Energy Purchased [J]
514 23378150 : electricityPurch_ = electPurchRate_ * state.dataHVACGlobal->TimeStepSysSec;
515 :
516 : // report the total electric surplus....
517 23378150 : electSurplusRate_ = electProdRate_ - totalElectricDemand_;
518 23378150 : if (std::abs(electSurplusRate_) < 0.0001) {
519 4953343 : electSurplusRate_ = 0.0;
520 : }
521 23378150 : if (electSurplusRate_ < 0.0) {
522 18325532 : electSurplusRate_ = 0.0; // don't want negative surplus
523 : }
524 :
525 23378150 : electricitySurplus_ = electSurplusRate_ * state.dataHVACGlobal->TimeStepSysSec;
526 :
527 : // report the net electricity , + is purchased, - is surplus
528 23378150 : electricityNetRate_ = totalElectricDemand_ - electProdRate_;
529 :
530 23378150 : electricityNet_ = electricityNetRate_ * state.dataHVACGlobal->TimeStepSysSec;
531 23378150 : }
532 :
533 733 : void ElectricPowerServiceManager::reportPVandWindCapacity(EnergyPlusData &state)
534 : {
535 : // LEED report
536 733 : pvTotalCapacity_ = 0.0;
537 733 : windTotalCapacity_ = 0.0;
538 1468 : for (auto const &lc : elecLoadCenterObjs) {
539 735 : if (lc->numGenerators > 0) {
540 106 : for (auto const &g : lc->elecGenCntrlObj) {
541 85 : if (g->generatorType == GeneratorType::PV) {
542 58 : pvTotalCapacity_ += g->maxPowerOut;
543 : }
544 85 : if (g->generatorType == GeneratorType::WindTurbine) {
545 3 : windTotalCapacity_ += g->maxPowerOut;
546 : }
547 21 : }
548 : }
549 733 : }
550 : // put in total capacity for PV and Wind for LEED report
551 733 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchLeedRenRatCap, "Photovoltaic", pvTotalCapacity_ / 1000, 2);
552 733 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchLeedRenRatCap, "Wind", windTotalCapacity_ / 1000, 2);
553 :
554 : // future work: this legacy approach is relying on the correct power output to have been placed in the Generator list. There could be a
555 : // difference between this control input and the actual size of the systems as defined in the generator objects themselves. This method should be
556 : // replaced with queries that check the capacity from the generator models.
557 733 : }
558 :
559 733 : void ElectricPowerServiceManager::sumUpNumberOfStorageDevices()
560 : {
561 733 : numElecStorageDevices = 0;
562 1468 : for (auto const &e : elecLoadCenterObjs) {
563 735 : if (e->storageObj != nullptr) {
564 9 : ++numElecStorageDevices;
565 : }
566 733 : }
567 733 : }
568 :
569 733 : void ElectricPowerServiceManager::checkLoadCenters(EnergyPlusData &state)
570 : {
571 :
572 : // issue #5302, detect if storage used on more than one load center. This is really a kind of GlobalNames issue.
573 : // expanded to all devices on a load center
574 733 : bool errorsFound = false;
575 :
576 : // first fill in a vector of names
577 733 : std::vector<std::string> storageNames;
578 733 : std::vector<std::string> genListNames;
579 733 : std::vector<std::string> inverterNames;
580 733 : std::vector<std::string> converterNames;
581 733 : std::vector<std::string> transformerNames;
582 1468 : for (auto &e : elecLoadCenterObjs) {
583 735 : if (e->storageObj != nullptr) {
584 9 : storageNames.emplace_back(e->storageObj->name());
585 : }
586 735 : if (!e->elecGenCntrlObj.empty()) {
587 21 : genListNames.emplace_back(e->generatorListName());
588 : }
589 735 : if (e->inverterObj != nullptr) {
590 12 : inverterNames.emplace_back(e->inverterObj->name());
591 : }
592 735 : if (e->converterObj != nullptr) {
593 4 : converterNames.emplace_back(e->converterObj->name());
594 : }
595 735 : if (e->transformerObj != nullptr) {
596 0 : transformerNames.emplace_back(e->transformerObj->name());
597 : }
598 733 : }
599 :
600 : // then check the vectors for duplicates.
601 742 : for (std::size_t i = 0; i < storageNames.size(); ++i) {
602 18 : for (std::size_t j = 0; j < storageNames.size(); ++j) {
603 9 : if (storageNames[i] == storageNames[j] && i != j) {
604 0 : ShowSevereError(state,
605 0 : format("ElectricPowerServiceManager::checkLoadCenters, the electrical storage device named = {} is used in more than "
606 : "one ElectricLoadCenter:Distribution input object.",
607 0 : storageNames[i]));
608 0 : ShowContinueError(state, "Electric Load Centers cannot share the same storage device.");
609 0 : errorsFound = true;
610 0 : break;
611 : }
612 : }
613 9 : if (errorsFound) {
614 0 : break;
615 : }
616 : }
617 :
618 754 : for (std::size_t i = 0; i < genListNames.size(); ++i) {
619 48 : for (std::size_t j = 0; j < genListNames.size(); ++j) {
620 27 : if (genListNames[i] == genListNames[j] && i != j) {
621 0 : ShowSevereError(state,
622 0 : format("ElectricPowerServiceManager::checkLoadCenters, the generator list named = {} is used in more than one "
623 : "ElectricLoadCenter:Distribution input object.",
624 0 : genListNames[i]));
625 0 : ShowContinueError(state, "Electric Load Centers cannot share the same generator list (ElectricLoadCenter:Generators).");
626 0 : errorsFound = true;
627 0 : break;
628 : }
629 : }
630 21 : if (errorsFound) {
631 0 : break;
632 : }
633 : }
634 :
635 745 : for (std::size_t i = 0; i < inverterNames.size(); ++i) {
636 24 : for (std::size_t j = 0; j < inverterNames.size(); ++j) {
637 12 : if (inverterNames[i] == inverterNames[j] && i != j) {
638 0 : ShowSevereError(state,
639 0 : format("ElectricPowerServiceManager::checkLoadCenters, the inverter device named = {} is used in more than one "
640 : "ElectricLoadCenter:Distribution input object.",
641 0 : inverterNames[i]));
642 0 : ShowContinueError(state, "Electric Load Centers cannot share the same inverter device.");
643 0 : errorsFound = true;
644 0 : break;
645 : }
646 : }
647 12 : if (errorsFound) {
648 0 : break;
649 : }
650 : }
651 :
652 737 : for (std::size_t i = 0; i < converterNames.size(); ++i) {
653 8 : for (std::size_t j = 0; j < converterNames.size(); ++j) {
654 4 : if (converterNames[i] == converterNames[j] && i != j) {
655 0 : ShowSevereError(state,
656 0 : format("ElectricPowerServiceManager::checkLoadCenters, the converter device named = {} is used in more than one "
657 : "ElectricLoadCenter:Distribution input object.",
658 0 : converterNames[i]));
659 0 : ShowContinueError(state, "Electric Load Centers cannot share the same converter device.");
660 0 : errorsFound = true;
661 0 : break;
662 : }
663 : }
664 4 : if (errorsFound) {
665 0 : break;
666 : }
667 : }
668 :
669 733 : for (std::size_t i = 0; i < transformerNames.size(); ++i) {
670 0 : for (std::size_t j = 0; j < transformerNames.size(); ++j) {
671 0 : if (transformerNames[i] == transformerNames[j] && i != j) {
672 0 : ShowSevereError(state,
673 0 : format("ElectricPowerServiceManager::checkLoadCenters, the transformer device named = {} is used in more than one "
674 : "ElectricLoadCenter:Distribution input object.",
675 0 : transformerNames[i]));
676 0 : ShowContinueError(state, "Electric Load Centers cannot share the same transformer device.");
677 0 : errorsFound = true;
678 0 : break;
679 : }
680 : }
681 0 : if (errorsFound) {
682 0 : break;
683 : }
684 : }
685 :
686 733 : if (errorsFound) { // throw fatal, these errors could fatal out in internal gains with missleading data
687 0 : ShowFatalError(state, "ElectricPowerServiceManager::checkLoadCenters, preceding errors terminate program.");
688 : }
689 733 : }
690 :
691 : // TODO: Absolutely not. Constructors should not do this much work,
692 : // because if this fails, then the constructor fails and who knows who
693 : // will be referencing this object.
694 735 : ElectPowerLoadCenter::ElectPowerLoadCenter(EnergyPlusData &state, int const objectNum)
695 1470 : : numGenerators(0), bussType(ElectricBussType::Invalid), thermalProd(0.0), thermalProdRate(0.0), inverterPresent(false),
696 735 : subpanelFeedInRequest(0.0), subpanelFeedInRate(0.0), subpanelDrawRate(0.0), genElectricProd(0.0), genElectProdRate(0.0), storOpCVDrawRate(0.0),
697 735 : storOpCVFeedInRate(0.0), storOpCVChargeRate(0.0), storOpCVDischargeRate(0.0), storOpIsCharging(false), storOpIsDischarging(false),
698 2205 : genOperationScheme_(GeneratorOpScheme::Invalid), demandMeterPtr_(0), generatorsPresent_(false), myCoGenSetupFlag_(true), demandLimit_(0.0),
699 2205 : storagePresent_(false), transformerPresent_(false), totalPowerRequest_(0.0), totalThermalPowerRequest_(0.0),
700 2205 : storageScheme_(StorageOpScheme::Invalid), trackStorageOpMeterIndex_(0), converterPresent_(false), maxStorageSOCFraction_(1.0),
701 735 : minStorageSOCFraction_(0.0), designStorageChargePower_(0.0), designStorageChargePowerWasSet_(false), designStorageDischargePower_(0.0),
702 735 : designStorageDischargePowerWasSet_(false), facilityDemandTarget_(0.0), eMSOverridePelFromStorage_(false), // if true, EMS calling for override
703 735 : eMSValuePelFromStorage_(0.0), // value EMS is directing to use, power from storage [W]
704 735 : eMSOverridePelIntoStorage_(false), // if true, EMS calling for override
705 735 : eMSValuePelIntoStorage_(0.0) // value EMS is directing to use, power into storage [W]
706 : {
707 :
708 : static constexpr std::string_view routineName = "ElectPowerLoadCenter constructor ";
709 : int numAlphas; // Number of elements in the alpha array
710 : int numNums; // Number of elements in the numeric array
711 : int IOStat; // IO Status when calling get input subroutine
712 :
713 735 : auto &s_ipsc = state.dataIPShortCut;
714 :
715 735 : s_ipsc->cCurrentModuleObject = "ElectricLoadCenter:Distribution";
716 735 : bool errorsFound = false;
717 735 : if (objectNum > 0) {
718 50 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
719 25 : s_ipsc->cCurrentModuleObject,
720 : objectNum,
721 25 : s_ipsc->cAlphaArgs,
722 : numAlphas,
723 25 : s_ipsc->rNumericArgs,
724 : numNums,
725 : IOStat,
726 25 : s_ipsc->lNumericFieldBlanks,
727 25 : s_ipsc->lAlphaFieldBlanks,
728 25 : s_ipsc->cAlphaFieldNames,
729 25 : s_ipsc->cNumericFieldNames);
730 :
731 25 : ErrorObjectHeader eoh{routineName, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)};
732 :
733 25 : name_ = s_ipsc->cAlphaArgs(1);
734 : // how to verify names are unique across objects? add to GlobalNames?
735 :
736 25 : if (!s_ipsc->lAlphaFieldBlanks(2)) {
737 21 : generatorListName_ = s_ipsc->cAlphaArgs(2);
738 : // check that
739 :
740 21 : int testIndex = state.dataInputProcessing->inputProcessor->getObjectItemNum(state, "ElectricLoadCenter:Generators", generatorListName_);
741 21 : if (testIndex == 0) {
742 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(2), s_ipsc->cAlphaArgs(2));
743 0 : errorsFound = true;
744 : }
745 : }
746 :
747 25 : if (!s_ipsc->lAlphaFieldBlanks(3)) {
748 : // Load the Generator Control Operation Scheme
749 21 : genOperationScheme_ = static_cast<GeneratorOpScheme>(getEnumValue(generatorOpSchemeNamesUC, s_ipsc->cAlphaArgs(3)));
750 21 : if (genOperationScheme_ == GeneratorOpScheme::Invalid) {
751 0 : ShowSevereInvalidKey(state, eoh, s_ipsc->cAlphaFieldNames(3), s_ipsc->cAlphaArgs(3));
752 0 : errorsFound = true;
753 : }
754 : }
755 :
756 25 : demandLimit_ = s_ipsc->rNumericArgs(1);
757 :
758 25 : if (genOperationScheme_ == GeneratorOpScheme::TrackSchedule) {
759 1 : if (s_ipsc->lAlphaFieldBlanks(4)) {
760 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(4));
761 0 : errorsFound = true;
762 1 : } else if ((trackSched_ = Sched::GetSchedule(state, s_ipsc->cAlphaArgs(4))) == nullptr) {
763 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(4), s_ipsc->cAlphaArgs(4));
764 0 : errorsFound = true;
765 : }
766 : }
767 :
768 25 : demandMeterName_ = Util::makeUPPER(s_ipsc->cAlphaArgs(5));
769 : // meters may not be "loaded" yet, defered check to later subroutine
770 :
771 25 : if (s_ipsc->cAlphaArgs(6).empty()) {
772 0 : bussType = ElectricBussType::ACBuss;
773 25 : } else if ((bussType = static_cast<ElectricBussType>(getEnumValue(electricBussTypeNamesUC, s_ipsc->cAlphaArgs(6)))) ==
774 : ElectricBussType::Invalid) {
775 0 : ShowSevereInvalidKey(state, eoh, s_ipsc->cAlphaFieldNames(6), s_ipsc->cAlphaArgs(6));
776 0 : errorsFound = true;
777 25 : } else if (bussType == ElectricBussType::DCBussInverter) {
778 3 : inverterPresent = true;
779 22 : } else if (bussType == ElectricBussType::ACBussStorage) {
780 0 : storagePresent_ = true;
781 22 : } else if (bussType == ElectricBussType::DCBussInverterDCStorage || bussType == ElectricBussType::DCBussInverterACStorage) {
782 9 : inverterPresent = true;
783 9 : storagePresent_ = true;
784 : }
785 :
786 25 : if (inverterPresent) {
787 12 : if (!s_ipsc->lAlphaFieldBlanks(7)) {
788 12 : inverterName = s_ipsc->cAlphaArgs(7);
789 : } else {
790 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(7), s_ipsc->cAlphaFieldNames(6), s_ipsc->cAlphaArgs(6));
791 0 : errorsFound = true;
792 : }
793 : }
794 :
795 25 : if (storagePresent_) {
796 9 : if (!s_ipsc->lAlphaFieldBlanks(8)) {
797 9 : storageName_ = s_ipsc->cAlphaArgs(8);
798 : } else {
799 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(8), s_ipsc->cAlphaFieldNames(6), s_ipsc->cAlphaArgs(6));
800 0 : errorsFound = true;
801 : }
802 : }
803 :
804 25 : if (!s_ipsc->lAlphaFieldBlanks(9)) {
805 : // process transformer
806 0 : transformerName_ = s_ipsc->cAlphaArgs(9);
807 : // only transformers of use type powerFromLoadCenterToBldg are really held in a load center, The legacy applications for transformers are
808 : // held at the higher Electric service level
809 0 : transformerPresent_ = true;
810 : }
811 :
812 : // Begin new content for grid supply and more control over storage
813 : // user selected storage operation scheme
814 25 : if (s_ipsc->lAlphaFieldBlanks(10)) {
815 19 : storageScheme_ = StorageOpScheme::FacilityDemandStoreExcessOnSite;
816 6 : } else if ((storageScheme_ = static_cast<StorageOpScheme>(getEnumValue(storageOpSchemeNamesUC, s_ipsc->cAlphaArgs(10)))) ==
817 : StorageOpScheme::Invalid) {
818 0 : ShowSevereInvalidKey(state, eoh, s_ipsc->cAlphaFieldNames(10), s_ipsc->cAlphaArgs(10));
819 0 : errorsFound = true;
820 : }
821 :
822 25 : if (!s_ipsc->lAlphaFieldBlanks(11)) {
823 0 : trackSorageOpMeterName_ = s_ipsc->cAlphaArgs(11);
824 25 : } else if (storageScheme_ == StorageOpScheme::MeterDemandStoreExcessOnSite) { // throw error
825 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(11), s_ipsc->cAlphaFieldNames(10), s_ipsc->cAlphaArgs(10));
826 0 : errorsFound = true;
827 : }
828 :
829 25 : if (!s_ipsc->lAlphaFieldBlanks(12)) {
830 4 : converterName_ = s_ipsc->cAlphaArgs(12);
831 4 : converterPresent_ = true;
832 21 : } else if (storageScheme_ == StorageOpScheme::ChargeDischargeSchedules || storageScheme_ == StorageOpScheme::FacilityDemandLeveling) {
833 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(12), s_ipsc->cAlphaFieldNames(10), s_ipsc->cAlphaArgs(10));
834 0 : errorsFound = true;
835 : }
836 :
837 25 : if (s_ipsc->lNumericFieldBlanks(2)) {
838 19 : maxStorageSOCFraction_ = 1.0;
839 : } else {
840 6 : maxStorageSOCFraction_ = s_ipsc->rNumericArgs(2);
841 : }
842 25 : if (s_ipsc->lNumericFieldBlanks(3)) {
843 19 : minStorageSOCFraction_ = 0.0;
844 : } else {
845 6 : minStorageSOCFraction_ = s_ipsc->rNumericArgs(3);
846 : }
847 25 : if (s_ipsc->lNumericFieldBlanks(4)) {
848 21 : designStorageChargePowerWasSet_ = false;
849 : } else {
850 4 : designStorageChargePowerWasSet_ = true;
851 4 : designStorageChargePower_ = s_ipsc->rNumericArgs(4);
852 : }
853 25 : if (s_ipsc->lNumericFieldBlanks(5)) {
854 21 : designStorageDischargePowerWasSet_ = false;
855 : } else {
856 4 : designStorageDischargePowerWasSet_ = true;
857 4 : designStorageDischargePower_ = s_ipsc->rNumericArgs(5);
858 : }
859 :
860 25 : if (!s_ipsc->lNumericFieldBlanks(6)) {
861 1 : facilityDemandTarget_ = s_ipsc->rNumericArgs(6);
862 24 : } else if (storageScheme_ == StorageOpScheme::FacilityDemandLeveling) {
863 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cNumericFieldNames(6), s_ipsc->cAlphaFieldNames(10), s_ipsc->cAlphaArgs(10));
864 0 : errorsFound = true;
865 : }
866 :
867 25 : if (storageScheme_ == StorageOpScheme::ChargeDischargeSchedules) {
868 3 : if (s_ipsc->lAlphaFieldBlanks(13)) {
869 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(13), s_ipsc->cAlphaFieldNames(10), s_ipsc->cAlphaArgs(10));
870 0 : errorsFound = true;
871 3 : } else if ((storageChargeModSched_ = Sched::GetSchedule(state, s_ipsc->cAlphaArgs(13))) == nullptr) {
872 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(13), s_ipsc->cAlphaArgs(13));
873 0 : errorsFound = true;
874 : }
875 :
876 3 : if (s_ipsc->lAlphaFieldBlanks(14)) {
877 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(14), s_ipsc->cAlphaFieldNames(10), s_ipsc->cAlphaArgs(10));
878 0 : errorsFound = true;
879 3 : } else if ((storageDischargeModSched_ = Sched::GetSchedule(state, s_ipsc->cAlphaArgs(14))) == nullptr) {
880 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(14), s_ipsc->cAlphaArgs(14));
881 0 : errorsFound = true;
882 : }
883 : }
884 :
885 25 : if (storageScheme_ == StorageOpScheme::FacilityDemandLeveling) {
886 1 : if (s_ipsc->lAlphaFieldBlanks(15)) {
887 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(15), s_ipsc->cAlphaFieldNames(10), s_ipsc->cAlphaArgs(10));
888 0 : errorsFound = true;
889 1 : } else if ((facilityDemandTargetModSched_ = Sched::GetSchedule(state, s_ipsc->cAlphaArgs(15))) == nullptr) {
890 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(15), s_ipsc->cAlphaArgs(15));
891 0 : errorsFound = true;
892 : }
893 : }
894 :
895 : } else { // object num == 0
896 : // just construct an empty object and return
897 710 : return;
898 : }
899 :
900 : // now that we are done with processing get input for ElectricLoadCenter:Distribution we can call child input objects without IP shortcut problems
901 25 : s_ipsc->cCurrentModuleObject = "ElectricLoadCenter:Generators";
902 25 : int genListObjectNum = state.dataInputProcessing->inputProcessor->getObjectItemNum(state, s_ipsc->cCurrentModuleObject, generatorListName_);
903 25 : if (genListObjectNum > 0) {
904 42 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
905 21 : s_ipsc->cCurrentModuleObject,
906 : genListObjectNum,
907 21 : s_ipsc->cAlphaArgs,
908 : numAlphas,
909 21 : s_ipsc->rNumericArgs,
910 : numNums,
911 : IOStat,
912 21 : s_ipsc->lNumericFieldBlanks,
913 21 : s_ipsc->lAlphaFieldBlanks,
914 21 : s_ipsc->cAlphaFieldNames,
915 21 : s_ipsc->cNumericFieldNames);
916 :
917 : // Calculate the number of generators in list
918 21 : numGenerators = numNums / 2; // note IDD needs Min Fields = 6
919 21 : if (mod((numAlphas - 1 + numNums), 5) != 0) {
920 6 : ++numGenerators;
921 : }
922 21 : int alphaCount = 2;
923 106 : for (int genCount = 1; genCount <= numGenerators; ++genCount) {
924 : // call constructor in place
925 85 : generatorsPresent_ = true;
926 85 : elecGenCntrlObj.emplace_back(new GeneratorController(state,
927 85 : s_ipsc->cAlphaArgs(alphaCount),
928 85 : s_ipsc->cAlphaArgs(alphaCount + 1),
929 85 : s_ipsc->rNumericArgs(2 * genCount - 1),
930 85 : s_ipsc->cAlphaArgs(alphaCount + 2),
931 255 : s_ipsc->rNumericArgs(2 * genCount)));
932 85 : ++alphaCount;
933 85 : ++alphaCount;
934 85 : ++alphaCount;
935 : }
936 :
937 : // issue #5299 check for non-zero values in thermal electric ratio if gen op scheme is ThermalFollow*
938 21 : if (genOperationScheme_ == GeneratorOpScheme::ThermalFollow || genOperationScheme_ == GeneratorOpScheme::ThermalFollowLimitElectrical) {
939 : // check to make sure the user didn't input zeros for thermalToElectricControlRatio
940 2 : for (auto const &g : elecGenCntrlObj) {
941 1 : if (g->nominalThermElectRatio <= 0.0) {
942 0 : ShowWarningError(state,
943 0 : format("Generator operation needs to be based on following thermal loads and needs values for Rated Thermal to "
944 : "Electrical Power Ratio in {} named {}",
945 0 : s_ipsc->cCurrentModuleObject,
946 0 : s_ipsc->cAlphaArgs(1)));
947 : }
948 1 : }
949 : }
950 : }
951 :
952 25 : if (!errorsFound && inverterPresent) {
953 : // call inverter constructor
954 12 : inverterObj = std::make_unique<DCtoACInverter>(state, inverterName);
955 :
956 : // Make sure only Generator::PVWatts are used with Inverter:PVWatts
957 : // Add up the total DC capacity and pass it to the inverter.
958 12 : if (inverterObj->modelType() == DCtoACInverter::InverterModelType::PVWatts) {
959 1 : Real64 totalDCCapacity = 0.0;
960 4 : for (const auto &generatorController : elecGenCntrlObj) {
961 3 : if (generatorController->generatorType != GeneratorType::PVWatts) {
962 0 : errorsFound = true;
963 0 : ShowSevereError(state, format("{}ElectricLoadCenter:Distribution=\"{}\",", routineName, name_));
964 0 : ShowContinueError(state, "ElectricLoadCenter:Inverter:PVWatts can only be used with Generator:PVWatts");
965 0 : ShowContinueError(state,
966 0 : format("\"{}\" is of type {}",
967 0 : generatorController->name,
968 0 : generatorTypeNames[static_cast<int>(generatorController->generatorType)]));
969 : } else {
970 3 : totalDCCapacity += generatorController->pvwattsGenerator->getDCSystemCapacity();
971 :
972 : // Pass the inverter properties to the PVWatts generator class
973 3 : generatorController->pvwattsGenerator->setDCtoACRatio(inverterObj->pvWattsDCtoACSizeRatio());
974 3 : generatorController->pvwattsGenerator->setInverterEfficiency(inverterObj->pvWattsInverterEfficiency());
975 : }
976 1 : }
977 1 : if (!errorsFound) {
978 1 : inverterObj->setPVWattsDCCapacity(state, totalDCCapacity);
979 : }
980 : }
981 : }
982 :
983 25 : if (!errorsFound && storagePresent_) {
984 : // call storage constructor
985 9 : storageObj = std::make_unique<ElectricStorage>(state, storageName_);
986 : }
987 :
988 25 : if (!errorsFound && transformerPresent_) {
989 :
990 0 : s_ipsc->cCurrentModuleObject = "ElectricLoadCenter:Transformer";
991 0 : int transformerItemNum = state.dataInputProcessing->inputProcessor->getObjectItemNum(state, s_ipsc->cCurrentModuleObject, transformerName_);
992 0 : if (transformerItemNum > 0) {
993 : int iOStat;
994 0 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
995 0 : s_ipsc->cCurrentModuleObject,
996 : transformerItemNum,
997 0 : s_ipsc->cAlphaArgs,
998 : numAlphas,
999 0 : s_ipsc->rNumericArgs,
1000 : numNums,
1001 : iOStat,
1002 0 : s_ipsc->lNumericFieldBlanks,
1003 0 : s_ipsc->lAlphaFieldBlanks,
1004 0 : s_ipsc->cAlphaFieldNames,
1005 0 : s_ipsc->cNumericFieldNames);
1006 0 : if (Util::SameString(s_ipsc->cAlphaArgs(3),
1007 : "LoadCenterPowerConditioning")) { // this is the right kind of transformer
1008 0 : transformerObj = std::make_unique<ElectricTransformer>(state, transformerName_);
1009 : } else {
1010 0 : ShowWarningError(
1011 : state,
1012 0 : format("Transformer named {} associated with the load center named {} should have {} set to LoadCenterPowerConditioning.",
1013 0 : transformerName_,
1014 0 : name_,
1015 0 : s_ipsc->cAlphaFieldNames(3)));
1016 : }
1017 : } else {
1018 0 : ShowSevereError(state, format("Transformer named {}, was not found for the load center named {}", transformerName_, name_));
1019 0 : errorsFound = true;
1020 : }
1021 : }
1022 :
1023 25 : if (!errorsFound && converterPresent_) {
1024 : // call AC to DC converter constructor
1025 4 : converterObj = std::make_unique<ACtoDCConverter>(state, converterName_);
1026 : }
1027 :
1028 : // Setup general output variables for reporting in the electric load center
1029 50 : SetupOutputVariable(state,
1030 : "Electric Load Center Produced Electricity Rate",
1031 : Constant::Units::W,
1032 25 : genElectProdRate,
1033 : OutputProcessor::TimeStepType::System,
1034 : OutputProcessor::StoreType::Average,
1035 25 : name_);
1036 50 : SetupOutputVariable(state,
1037 : "Electric Load Center Produced Electricity Energy",
1038 : Constant::Units::J,
1039 25 : genElectricProd,
1040 : OutputProcessor::TimeStepType::System,
1041 : OutputProcessor::StoreType::Sum,
1042 25 : name_);
1043 50 : SetupOutputVariable(state,
1044 : "Electric Load Center Supplied Electricity Rate",
1045 : Constant::Units::W,
1046 25 : subpanelFeedInRate,
1047 : OutputProcessor::TimeStepType::System,
1048 : OutputProcessor::StoreType::Average,
1049 25 : name_);
1050 50 : SetupOutputVariable(state,
1051 : "Electric Load Center Drawn Electricity Rate",
1052 : Constant::Units::W,
1053 25 : subpanelDrawRate,
1054 : OutputProcessor::TimeStepType::System,
1055 : OutputProcessor::StoreType::Average,
1056 25 : name_);
1057 50 : SetupOutputVariable(state,
1058 : "Electric Load Center Produced Thermal Rate",
1059 : Constant::Units::W,
1060 25 : thermalProdRate,
1061 : OutputProcessor::TimeStepType::System,
1062 : OutputProcessor::StoreType::Average,
1063 25 : name_);
1064 50 : SetupOutputVariable(state,
1065 : "Electric Load Center Produced Thermal Energy",
1066 : Constant::Units::J,
1067 25 : thermalProd,
1068 : OutputProcessor::TimeStepType::System,
1069 : OutputProcessor::StoreType::Sum,
1070 25 : name_);
1071 50 : SetupOutputVariable(state,
1072 : "Electric Load Center Requested Electricity Rate",
1073 : Constant::Units::W,
1074 25 : totalPowerRequest_,
1075 : OutputProcessor::TimeStepType::System,
1076 : OutputProcessor::StoreType::Average,
1077 25 : name_);
1078 :
1079 25 : if (state.dataGlobal->AnyEnergyManagementSystemInModel && storagePresent_) {
1080 2 : SetupEMSActuator(state, "Electrical Storage", name_, "Power Draw Rate", "[W]", eMSOverridePelFromStorage_, eMSValuePelFromStorage_);
1081 2 : SetupEMSActuator(state, "Electrical Storage", name_, "Power Charge Rate", "[W]", eMSOverridePelIntoStorage_, eMSValuePelIntoStorage_);
1082 : }
1083 :
1084 25 : if (errorsFound) {
1085 0 : ShowFatalError(state, format("{}Preceding errors terminate program.", routineName));
1086 : }
1087 0 : }
1088 :
1089 9337227 : void ElectPowerLoadCenter::manageElecLoadCenter(EnergyPlusData &state, bool const firstHVACIteration, Real64 &remainingWholePowerDemand)
1090 : {
1091 : //
1092 9337227 : subpanelFeedInRequest = remainingWholePowerDemand;
1093 :
1094 9337227 : if (generatorsPresent_) {
1095 193566 : dispatchGenerators(state, firstHVACIteration, remainingWholePowerDemand);
1096 :
1097 : } // if generators present
1098 9337227 : updateLoadCenterGeneratorRecords(state);
1099 9337227 : if (bussType == ElectricBussType::DCBussInverter || bussType == ElectricBussType::DCBussInverterACStorage) {
1100 15232 : inverterObj->simulate(state, genElectProdRate);
1101 : }
1102 :
1103 9337227 : if (storagePresent_) {
1104 130975 : storageObj->timeCheckAndUpdate(state);
1105 130975 : dispatchStorage(state, subpanelFeedInRequest);
1106 : }
1107 :
1108 9337227 : if (bussType == ElectricBussType::DCBussInverterDCStorage) {
1109 130975 : if (inverterObj != nullptr) {
1110 130975 : inverterObj->simulate(state, storOpCVFeedInRate);
1111 : }
1112 : }
1113 :
1114 9337227 : if (converterObj != nullptr) {
1115 69372 : converterObj->simulate(state, storOpCVDrawRate);
1116 : }
1117 :
1118 9337227 : if (transformerObj != nullptr) {
1119 0 : if (storOpCVFeedInRate > 0.0) {
1120 0 : transformerObj->manageTransformers(state, storOpCVFeedInRate);
1121 0 : } else if (storOpCVDrawRate > 0.0) {
1122 0 : transformerObj->manageTransformers(state, subpanelDrawRate);
1123 : }
1124 : }
1125 9337227 : updateLoadCenterGeneratorRecords(state);
1126 9337227 : }
1127 :
1128 193566 : void ElectPowerLoadCenter::dispatchGenerators(EnergyPlusData &state,
1129 : bool const firstHVACIteration,
1130 : Real64 &remainingWholePowerDemand // power request in, remaining unmet request out
1131 : )
1132 : {
1133 :
1134 : // This funciton checks generator operation scheme and assigns requests to power generators
1135 : // the generators are called to simulate from here and passed some control data
1136 : // the actual production from each generator is recorded and accounting tracks how much of the load is met
1137 :
1138 : // If a generator is needed in the simulation for a small load and it is less than the minimum part load ratio
1139 : // the generator will operate at the minimum part load ratio and the excess will either reduce demand or
1140 : // be available for storage or sell back to the power company.
1141 :
1142 : // Both the Demand Limit and Track Electrical schemes will sequentially load the available generators. All demand
1143 193566 : Real64 loadCenterElectricLoad = 0.0;
1144 193566 : Real64 remainingLoad = 0.0;
1145 193566 : Real64 customMeterDemand = 0.0;
1146 :
1147 193566 : switch (genOperationScheme_) {
1148 :
1149 86024 : case GeneratorOpScheme::BaseLoad: {
1150 :
1151 86024 : loadCenterElectricLoad = remainingWholePowerDemand;
1152 :
1153 353825 : for (auto &g : elecGenCntrlObj) {
1154 :
1155 267801 : if (g->availSched->getCurrentVal() > 0.0) {
1156 : // Set the Operation Flag
1157 201816 : g->onThisTimestep = true;
1158 : // Set the electric generator load request
1159 201816 : g->powerRequestThisTimestep = g->maxPowerOut;
1160 : } else {
1161 65985 : g->onThisTimestep = false;
1162 65985 : g->powerRequestThisTimestep = 0.0;
1163 : }
1164 :
1165 : // now handle EMS override
1166 267801 : if (g->eMSRequestOn) {
1167 0 : g->powerRequestThisTimestep = max(g->eMSPowerRequest, 0.0);
1168 0 : if (g->powerRequestThisTimestep > 0.0) {
1169 0 : g->onThisTimestep = true;
1170 : } else {
1171 0 : g->onThisTimestep = false;
1172 : }
1173 : }
1174 :
1175 : // Get generator's actual electrical and thermal power outputs
1176 267801 : g->simGeneratorGetPowerOutput(
1177 267801 : state, g->onThisTimestep, g->powerRequestThisTimestep, firstHVACIteration, g->electProdRate, g->thermProdRate);
1178 :
1179 267801 : totalPowerRequest_ += g->powerRequestThisTimestep;
1180 267801 : remainingWholePowerDemand -= g->electProdRate; // Update whole building remaining load
1181 86024 : }
1182 86024 : break;
1183 : }
1184 16371 : case GeneratorOpScheme::DemandLimit: {
1185 : // The Demand Limit scheme tries to have the generators meet all of the demand above the purchased Electric
1186 : // limit set by the user.
1187 16371 : remainingLoad = remainingWholePowerDemand - demandLimit_;
1188 16371 : loadCenterElectricLoad = remainingLoad;
1189 :
1190 49758 : for (auto &g : elecGenCntrlObj) {
1191 :
1192 33387 : if (g->availSched->getCurrentVal() > 0.0 && remainingLoad > 0.0) {
1193 : // Set the Operation Flag
1194 6752 : g->onThisTimestep = true;
1195 :
1196 : // Set the electric generator load
1197 6752 : g->powerRequestThisTimestep = min(g->maxPowerOut, remainingLoad);
1198 :
1199 : // now handle EMS override
1200 6752 : if (g->eMSRequestOn) {
1201 0 : g->powerRequestThisTimestep = max(g->eMSPowerRequest, 0.0);
1202 0 : if (g->powerRequestThisTimestep > 0.0) {
1203 0 : g->onThisTimestep = true;
1204 : } else {
1205 0 : g->onThisTimestep = false;
1206 : }
1207 : }
1208 : } else {
1209 26635 : g->onThisTimestep = false;
1210 26635 : g->powerRequestThisTimestep = 0.0;
1211 :
1212 : // now handle EMS override
1213 26635 : if (g->eMSRequestOn) {
1214 0 : g->powerRequestThisTimestep = max(g->eMSPowerRequest, 0.0);
1215 0 : if (g->powerRequestThisTimestep > 0.0) {
1216 0 : g->onThisTimestep = true;
1217 : } else {
1218 0 : g->onThisTimestep = false;
1219 : }
1220 : }
1221 : }
1222 :
1223 : // Get generator's actual electrical and thermal power outputs
1224 33387 : g->simGeneratorGetPowerOutput(
1225 33387 : state, g->onThisTimestep, g->powerRequestThisTimestep, firstHVACIteration, g->electProdRate, g->thermProdRate);
1226 :
1227 33387 : if (g->eMSRequestOn) {
1228 0 : totalPowerRequest_ += max(g->eMSPowerRequest, 0.0);
1229 : } else {
1230 33387 : if (g->powerRequestThisTimestep > 0.0) {
1231 6752 : totalPowerRequest_ += g->maxPowerOut;
1232 6752 : totalPowerRequest_ = min(loadCenterElectricLoad, totalPowerRequest_);
1233 : }
1234 : }
1235 33387 : remainingLoad -= g->electProdRate; // Update remaining load to be met by this load center
1236 33387 : remainingWholePowerDemand -= g->electProdRate; // Update whole building remaining load
1237 16371 : }
1238 16371 : break;
1239 : }
1240 73756 : case GeneratorOpScheme::TrackElectrical: {
1241 : // The Track Electrical scheme tries to have the generators meet all of the electrical demand for the building.
1242 73756 : remainingLoad = remainingWholePowerDemand;
1243 73756 : loadCenterElectricLoad = remainingLoad;
1244 :
1245 541664 : for (auto &g : elecGenCntrlObj) {
1246 :
1247 467908 : if (g->availSched->getCurrentVal() > 0.0 && remainingLoad > 0.0) {
1248 : // Set the Operation Flag
1249 333331 : g->onThisTimestep = true;
1250 :
1251 : // Set the electric generator load
1252 333331 : g->powerRequestThisTimestep = min(g->maxPowerOut, remainingLoad);
1253 :
1254 : // now handle EMS override
1255 333331 : if (g->eMSRequestOn) {
1256 0 : g->powerRequestThisTimestep = max(g->eMSPowerRequest, 0.0);
1257 0 : if (g->powerRequestThisTimestep > 0.0) {
1258 0 : g->onThisTimestep = true;
1259 : } else {
1260 0 : g->onThisTimestep = false;
1261 : }
1262 : }
1263 : } else {
1264 134577 : g->onThisTimestep = false;
1265 134577 : g->powerRequestThisTimestep = 0.0;
1266 : // now handle EMS override
1267 134577 : if (g->eMSRequestOn) {
1268 0 : g->powerRequestThisTimestep = max(g->eMSPowerRequest, 0.0);
1269 0 : if (g->powerRequestThisTimestep > 0.0) {
1270 0 : g->onThisTimestep = true;
1271 : } else {
1272 0 : g->onThisTimestep = false;
1273 : }
1274 : }
1275 : }
1276 :
1277 : // Get generator's actual electrical and thermal power outputs
1278 467908 : g->simGeneratorGetPowerOutput(
1279 467908 : state, g->onThisTimestep, g->powerRequestThisTimestep, firstHVACIteration, g->electProdRate, g->thermProdRate);
1280 :
1281 467908 : if (g->eMSRequestOn) {
1282 0 : totalPowerRequest_ += max(g->eMSPowerRequest, 0.0);
1283 : } else {
1284 467908 : if (g->powerRequestThisTimestep > 0.0) {
1285 333331 : totalPowerRequest_ += g->maxPowerOut;
1286 333331 : totalPowerRequest_ = min(loadCenterElectricLoad, totalPowerRequest_);
1287 : }
1288 : }
1289 467908 : remainingLoad -= g->electProdRate; // Update remaining load to be met by this load center
1290 467908 : remainingWholePowerDemand -= g->electProdRate; // Update whole building remaining load
1291 73756 : }
1292 73756 : break;
1293 : }
1294 6135 : case GeneratorOpScheme::TrackSchedule: {
1295 : // The Track Schedule scheme tries to have the generators meet the electrical demand determined from a schedule.
1296 : // Code is very similar to 'Track Electrical' except for initial RemainingLoad is replaced by SchedElecDemand
1297 : // and PV production is ignored.
1298 6135 : remainingLoad = trackSched_->getCurrentVal();
1299 6135 : loadCenterElectricLoad = remainingLoad;
1300 :
1301 12270 : for (auto &g : elecGenCntrlObj) {
1302 :
1303 6135 : if (g->availSched->getCurrentVal() > 0.0 && remainingLoad > 0.0) {
1304 : // Set the Operation Flag
1305 504 : g->onThisTimestep = true;
1306 :
1307 : // Set the electric generator load
1308 504 : g->powerRequestThisTimestep = min(g->maxPowerOut, remainingLoad);
1309 :
1310 : // now handle EMS override
1311 504 : if (g->eMSRequestOn) {
1312 0 : g->powerRequestThisTimestep = max(g->eMSPowerRequest, 0.0);
1313 0 : if (g->powerRequestThisTimestep > 0.0) {
1314 0 : g->onThisTimestep = true;
1315 : } else {
1316 0 : g->onThisTimestep = false;
1317 : }
1318 : }
1319 : } else {
1320 5631 : g->onThisTimestep = false;
1321 5631 : g->powerRequestThisTimestep = 0.0;
1322 :
1323 : // now handle EMS override
1324 5631 : if (g->eMSRequestOn) {
1325 0 : g->powerRequestThisTimestep = max(g->eMSPowerRequest, 0.0);
1326 0 : if (g->powerRequestThisTimestep > 0.0) {
1327 0 : g->onThisTimestep = true;
1328 : } else {
1329 0 : g->onThisTimestep = false;
1330 : }
1331 : }
1332 : }
1333 :
1334 : // Get generator's actual electrical and thermal power outputs
1335 6135 : g->simGeneratorGetPowerOutput(
1336 6135 : state, g->onThisTimestep, g->powerRequestThisTimestep, firstHVACIteration, g->electProdRate, g->thermProdRate);
1337 :
1338 6135 : if (g->eMSRequestOn) {
1339 0 : totalPowerRequest_ += max(g->eMSPowerRequest, 0.0);
1340 : } else {
1341 6135 : if (g->powerRequestThisTimestep > 0.0) {
1342 504 : totalPowerRequest_ += g->maxPowerOut;
1343 504 : totalPowerRequest_ = min(loadCenterElectricLoad, totalPowerRequest_);
1344 : }
1345 : }
1346 6135 : remainingLoad -= g->electProdRate; // Update remaining load to be met by this load center
1347 6135 : remainingWholePowerDemand -= g->electProdRate; // Update whole building remaining load
1348 6135 : }
1349 6135 : break;
1350 : }
1351 0 : case GeneratorOpScheme::TrackMeter: {
1352 : // The TRACK CUSTOM METER scheme tries to have the generators meet all of the
1353 : // electrical demand from a meter, it can also be a user-defined Custom Meter
1354 : // and PV is ignored.
1355 0 : customMeterDemand =
1356 0 : GetInstantMeterValue(state, demandMeterPtr_, OutputProcessor::TimeStepType::Zone) / state.dataGlobal->TimeStepZoneSec +
1357 0 : GetInstantMeterValue(state, demandMeterPtr_, OutputProcessor::TimeStepType::System) / (state.dataHVACGlobal->TimeStepSysSec);
1358 :
1359 0 : remainingLoad = customMeterDemand;
1360 0 : loadCenterElectricLoad = remainingLoad;
1361 :
1362 0 : for (auto &g : elecGenCntrlObj) {
1363 0 : if (g->availSched->getCurrentVal() > 0.0 && remainingLoad > 0.0) {
1364 : // Set the Operation Flag
1365 0 : g->onThisTimestep = true;
1366 : // Set the electric generator load
1367 0 : g->powerRequestThisTimestep = min(g->maxPowerOut, remainingLoad);
1368 :
1369 : // now handle EMS override
1370 0 : if (g->eMSRequestOn) {
1371 0 : g->powerRequestThisTimestep = max(g->eMSPowerRequest, 0.0);
1372 0 : if (g->powerRequestThisTimestep > 0.0) {
1373 0 : g->onThisTimestep = true;
1374 : } else {
1375 0 : g->onThisTimestep = false;
1376 : }
1377 : }
1378 : } else {
1379 0 : g->onThisTimestep = false;
1380 0 : g->powerRequestThisTimestep = 0.0;
1381 :
1382 : // now handle EMS override
1383 0 : if (g->eMSRequestOn) {
1384 0 : g->powerRequestThisTimestep = max(g->eMSPowerRequest, 0.0);
1385 0 : if (g->powerRequestThisTimestep > 0.0) {
1386 0 : g->onThisTimestep = true;
1387 : } else {
1388 0 : g->onThisTimestep = false;
1389 : }
1390 : }
1391 : }
1392 :
1393 : // Get generator's actual electrical and thermal power outputs
1394 0 : g->simGeneratorGetPowerOutput(
1395 0 : state, g->onThisTimestep, g->powerRequestThisTimestep, firstHVACIteration, g->electProdRate, g->thermProdRate);
1396 :
1397 0 : if (g->eMSRequestOn) {
1398 0 : totalPowerRequest_ += max(g->eMSPowerRequest, 0.0);
1399 : } else {
1400 0 : if (g->powerRequestThisTimestep > 0.0) {
1401 0 : totalPowerRequest_ += g->maxPowerOut;
1402 0 : totalPowerRequest_ = min(loadCenterElectricLoad, totalPowerRequest_);
1403 : }
1404 : }
1405 0 : remainingLoad -= g->electProdRate; // Update remaining load to be met by this load center
1406 0 : remainingWholePowerDemand -= g->electProdRate; // Update whole building remaining load
1407 0 : } // end for
1408 0 : break;
1409 : }
1410 11280 : case GeneratorOpScheme::ThermalFollow: {
1411 : // Turn thermal load into an electrical load for cogenerators controlled to follow heat loads
1412 11280 : Real64 remainingThermalLoad = calcLoadCenterThermalLoad(state);
1413 11280 : Real64 loadCenterThermalLoad = remainingThermalLoad;
1414 22560 : for (auto &g : elecGenCntrlObj) {
1415 :
1416 11280 : if (g->availSched->getCurrentVal() > 0.0 && remainingThermalLoad > 0.0) {
1417 :
1418 4640 : if (g->nominalThermElectRatio > 0.0) {
1419 4640 : remainingLoad = remainingThermalLoad / g->nominalThermElectRatio;
1420 4640 : g->powerRequestThisTimestep = min(g->maxPowerOut, remainingLoad);
1421 4640 : g->onThisTimestep = true;
1422 : // now handle EMS override
1423 4640 : if (g->eMSRequestOn) {
1424 0 : g->powerRequestThisTimestep = max(g->eMSPowerRequest, 0.0);
1425 0 : if (g->powerRequestThisTimestep > 0.0) {
1426 0 : g->onThisTimestep = true;
1427 : } else {
1428 0 : g->onThisTimestep = false;
1429 : }
1430 : }
1431 : }
1432 : } else {
1433 6640 : g->onThisTimestep = false;
1434 6640 : g->powerRequestThisTimestep = 0.0;
1435 : // now handle EMS override
1436 6640 : if (g->eMSRequestOn) {
1437 0 : g->powerRequestThisTimestep = max(g->eMSPowerRequest, 0.0);
1438 0 : if (g->powerRequestThisTimestep > 0.0) {
1439 0 : g->onThisTimestep = true;
1440 : } else {
1441 0 : g->onThisTimestep = false;
1442 : }
1443 : }
1444 : }
1445 :
1446 : // Get generator's actual electrical and thermal power outputs
1447 11280 : g->simGeneratorGetPowerOutput(
1448 11280 : state, g->onThisTimestep, g->powerRequestThisTimestep, firstHVACIteration, g->electProdRate, g->thermProdRate);
1449 :
1450 11280 : if (g->eMSRequestOn) {
1451 0 : totalThermalPowerRequest_ += max(g->eMSPowerRequest, 0.0) * g->nominalThermElectRatio;
1452 0 : totalPowerRequest_ += max(g->eMSPowerRequest, 0.0);
1453 : } else {
1454 11280 : if (totalThermalPowerRequest_ < loadCenterThermalLoad && g->powerRequestThisTimestep > 0.0) {
1455 4 : Real64 excessThermalPowerRequest = totalThermalPowerRequest_ + g->maxPowerOut * g->nominalThermElectRatio - loadCenterThermalLoad;
1456 4 : if (excessThermalPowerRequest < 0.0) {
1457 2 : totalThermalPowerRequest_ += g->maxPowerOut * g->nominalThermElectRatio;
1458 2 : totalPowerRequest_ += g->maxPowerOut;
1459 : } else {
1460 2 : totalThermalPowerRequest_ = loadCenterThermalLoad;
1461 2 : if (g->nominalThermElectRatio > 0.0) {
1462 2 : totalPowerRequest_ += g->maxPowerOut - (excessThermalPowerRequest / g->nominalThermElectRatio);
1463 : }
1464 : }
1465 : }
1466 : }
1467 11280 : remainingThermalLoad -= g->thermProdRate; // Update remaining load to be met
1468 : // by this load center
1469 11280 : remainingWholePowerDemand -= g->electProdRate; // Update whole building remaining load
1470 11280 : }
1471 11280 : break;
1472 : }
1473 0 : case GeneratorOpScheme::ThermalFollowLimitElectrical: {
1474 : // Turn a thermal load into an electrical load for cogenerators controlled to follow heat loads.
1475 : // Add intitialization of RemainingThermalLoad as in the ThermalFollow operating scheme above.
1476 0 : Real64 remainingThermalLoad = calcLoadCenterThermalLoad(state);
1477 : // Total current electrical demand for the building is a secondary limit.
1478 0 : remainingLoad = remainingWholePowerDemand;
1479 0 : loadCenterElectricLoad = remainingWholePowerDemand;
1480 0 : Real64 loadCenterThermalLoad = remainingThermalLoad;
1481 0 : for (auto &g : elecGenCntrlObj) {
1482 0 : if ((g->availSched->getCurrentVal() > 0.0) && (remainingThermalLoad > 0.0) && (remainingLoad > 0.0)) {
1483 0 : if (g->nominalThermElectRatio > 0.0) {
1484 0 : remainingLoad = min(remainingWholePowerDemand, remainingThermalLoad / g->nominalThermElectRatio);
1485 0 : g->powerRequestThisTimestep = min(g->maxPowerOut, remainingLoad);
1486 0 : g->onThisTimestep = true;
1487 : // now handle EMS override
1488 0 : if (g->eMSRequestOn) {
1489 0 : g->powerRequestThisTimestep = max(g->eMSPowerRequest, 0.0);
1490 0 : if (g->powerRequestThisTimestep > 0.0) {
1491 0 : g->onThisTimestep = true;
1492 : } else {
1493 0 : g->onThisTimestep = false;
1494 : }
1495 : }
1496 : }
1497 : } else {
1498 0 : g->onThisTimestep = false;
1499 0 : g->powerRequestThisTimestep = 0.0;
1500 : // now handle EMS override
1501 0 : if (g->eMSRequestOn) {
1502 0 : g->powerRequestThisTimestep = max(g->eMSPowerRequest, 0.0);
1503 0 : if (g->powerRequestThisTimestep > 0.0) {
1504 0 : g->onThisTimestep = true;
1505 : } else {
1506 0 : g->onThisTimestep = false;
1507 : }
1508 : }
1509 : }
1510 : // Get generator's actual electrical and thermal power outputs
1511 0 : g->simGeneratorGetPowerOutput(
1512 0 : state, g->onThisTimestep, g->powerRequestThisTimestep, firstHVACIteration, g->electProdRate, g->thermProdRate);
1513 :
1514 0 : if (g->eMSRequestOn) {
1515 0 : totalThermalPowerRequest_ += max(g->eMSPowerRequest, 0.0) * g->nominalThermElectRatio;
1516 0 : totalPowerRequest_ += (max(g->eMSPowerRequest, 0.0));
1517 : } else {
1518 0 : if (totalThermalPowerRequest_ < loadCenterThermalLoad && g->powerRequestThisTimestep > 0.0) {
1519 0 : Real64 excessThermalPowerRequest = totalThermalPowerRequest_ + g->maxPowerOut * g->nominalThermElectRatio - loadCenterThermalLoad;
1520 0 : if (excessThermalPowerRequest < 0.0) {
1521 0 : totalThermalPowerRequest_ += g->maxPowerOut * g->nominalThermElectRatio;
1522 0 : totalPowerRequest_ += g->maxPowerOut;
1523 : } else {
1524 0 : totalThermalPowerRequest_ = loadCenterThermalLoad;
1525 0 : if (g->nominalThermElectRatio > 0.0) {
1526 0 : totalPowerRequest_ += g->maxPowerOut - (excessThermalPowerRequest / g->nominalThermElectRatio);
1527 : }
1528 : }
1529 0 : totalPowerRequest_ = min(loadCenterElectricLoad, totalPowerRequest_);
1530 : }
1531 : }
1532 0 : remainingThermalLoad -= g->thermProdRate; // Update remaining thermal load to
1533 : // be met by this load center
1534 0 : remainingWholePowerDemand -= g->electProdRate; // Update whole building remaining
1535 : // electric load
1536 0 : }
1537 0 : break;
1538 : }
1539 0 : case GeneratorOpScheme::Invalid: {
1540 : // do nothing
1541 0 : break;
1542 : }
1543 0 : default:
1544 0 : assert(false);
1545 : } // end switch
1546 :
1547 : // sum up generator production
1548 193566 : genElectProdRate = 0.0;
1549 193566 : genElectricProd = 0.0;
1550 980077 : for (auto const &g : elecGenCntrlObj) {
1551 786511 : genElectProdRate += g->electProdRate;
1552 786511 : g->electricityProd = g->electProdRate * (state.dataHVACGlobal->TimeStepSysSec);
1553 786511 : genElectricProd += g->electricityProd;
1554 193566 : }
1555 193566 : }
1556 :
1557 130975 : void ElectPowerLoadCenter::dispatchStorage(EnergyPlusData &state,
1558 : Real64 const originalFeedInRequest // whole building remaining electric demand for this load center
1559 : )
1560 : {
1561 :
1562 : // 1. resolve generator power rate into storage operation control volume, by buss type
1563 130975 : switch (bussType) {
1564 0 : case ElectricBussType::Invalid:
1565 : case ElectricBussType::ACBuss:
1566 : case ElectricBussType::DCBussInverter: {
1567 : // do nothing, no storage to manage
1568 0 : break;
1569 : }
1570 0 : case ElectricBussType::ACBussStorage: {
1571 0 : storOpCVGenRate = genElectProdRate;
1572 0 : break;
1573 : }
1574 130975 : case ElectricBussType::DCBussInverterDCStorage: {
1575 130975 : storOpCVGenRate = genElectProdRate;
1576 130975 : break;
1577 : }
1578 0 : case ElectricBussType::DCBussInverterACStorage: {
1579 : // TODO call inverter model here?
1580 0 : storOpCVGenRate = inverterObj->aCPowerOut();
1581 0 : break;
1582 : }
1583 0 : default:
1584 0 : assert(false);
1585 : } // end switch buss type
1586 :
1587 : // 2. determine subpanel feed in and draw requests based on storage operation control scheme
1588 130975 : Real64 subpanelFeedInRequest = 0.0;
1589 130975 : Real64 subpanelDrawRequest = 0.0;
1590 130975 : switch (storageScheme_) {
1591 0 : case StorageOpScheme::Invalid: {
1592 : // do nothing
1593 0 : break;
1594 : }
1595 61603 : case StorageOpScheme::FacilityDemandStoreExcessOnSite: {
1596 61603 : subpanelFeedInRequest = originalFeedInRequest; // legacy behavior, storage dispatched to meet building load
1597 61603 : subpanelDrawRequest = 0.0;
1598 61603 : break;
1599 : }
1600 0 : case StorageOpScheme::MeterDemandStoreExcessOnSite: {
1601 : // Get meter rate
1602 0 : subpanelFeedInRequest =
1603 0 : GetInstantMeterValue(state, trackStorageOpMeterIndex_, OutputProcessor::TimeStepType::Zone) / state.dataGlobal->TimeStepZoneSec +
1604 0 : GetInstantMeterValue(state, trackStorageOpMeterIndex_, OutputProcessor::TimeStepType::System) / (state.dataHVACGlobal->TimeStepSysSec);
1605 0 : subpanelDrawRequest = 0.0;
1606 0 : break;
1607 : }
1608 52029 : case StorageOpScheme::ChargeDischargeSchedules: {
1609 : // do not need to deal with subpanel rates here, charge or discharge is known from schedules and filled in below
1610 52029 : break;
1611 : }
1612 17343 : case StorageOpScheme::FacilityDemandLeveling: {
1613 17343 : Real64 demandTarget = facilityDemandTarget_ * facilityDemandTargetModSched_->getCurrentVal();
1614 : // compare target to
1615 17343 : Real64 deltaLoad = originalFeedInRequest - demandTarget;
1616 17343 : if (deltaLoad >= 0.0) {
1617 : // subpanel should feed main panel
1618 7814 : subpanelFeedInRequest = deltaLoad;
1619 7814 : subpanelDrawRequest = 0.0;
1620 : } else {
1621 : // subpanel should draw from main panel
1622 9529 : subpanelFeedInRequest = 0.0;
1623 9529 : subpanelDrawRequest = std::abs(deltaLoad);
1624 : }
1625 17343 : break;
1626 : }
1627 0 : default:
1628 0 : assert(false);
1629 : }
1630 :
1631 : // 3. adjust feed in and draw rates from subpanel to storage operation control volume
1632 130975 : Real64 adjustedFeedInRequest = 0.0; // account for any inverter or transformer losses
1633 130975 : Real64 adjustedDrawRequest = 0.0; // account for any converer or transformer losses
1634 :
1635 130975 : switch (bussType) {
1636 0 : case ElectricBussType::Invalid:
1637 : case ElectricBussType::ACBuss:
1638 : case ElectricBussType::DCBussInverter: {
1639 : // do nothing, no storage to manage
1640 0 : break;
1641 : }
1642 0 : case ElectricBussType::ACBussStorage:
1643 : case ElectricBussType::DCBussInverterACStorage: {
1644 0 : if (transformerObj == nullptr) {
1645 0 : adjustedFeedInRequest = subpanelFeedInRequest;
1646 0 : adjustedDrawRequest = subpanelDrawRequest;
1647 : } else {
1648 0 : adjustedFeedInRequest = subpanelFeedInRequest + transformerObj->getLossRateForOutputPower(state, subpanelFeedInRequest);
1649 0 : adjustedDrawRequest = subpanelDrawRequest - transformerObj->getLossRateForInputPower(state, subpanelDrawRequest);
1650 : }
1651 0 : break;
1652 : }
1653 130975 : case ElectricBussType::DCBussInverterDCStorage: {
1654 : // can we get updated power conditioning losses here?
1655 130975 : if (transformerObj == nullptr) {
1656 130975 : adjustedFeedInRequest = subpanelFeedInRequest + inverterObj->getLossRateForOutputPower(state, subpanelFeedInRequest);
1657 130975 : if (converterObj == nullptr) { // some operation schemes will never need a converter
1658 61603 : adjustedDrawRequest = subpanelDrawRequest;
1659 : } else {
1660 69372 : adjustedDrawRequest = subpanelDrawRequest - converterObj->getLossRateForInputPower(state, subpanelDrawRequest);
1661 : }
1662 : } else {
1663 0 : adjustedFeedInRequest = subpanelFeedInRequest + inverterObj->getLossRateForOutputPower(state, subpanelFeedInRequest) +
1664 0 : transformerObj->getLossRateForOutputPower(state, subpanelFeedInRequest);
1665 0 : if (converterObj == nullptr) {
1666 0 : adjustedDrawRequest = subpanelDrawRequest - transformerObj->getLossRateForInputPower(state, subpanelDrawRequest);
1667 : } else {
1668 0 : adjustedDrawRequest = subpanelDrawRequest - converterObj->getLossRateForInputPower(state, subpanelDrawRequest) -
1669 0 : transformerObj->getLossRateForInputPower(state, subpanelDrawRequest);
1670 : }
1671 : }
1672 130975 : break;
1673 : }
1674 0 : default:
1675 0 : assert(false);
1676 : } // end switch buss type
1677 :
1678 130975 : switch (storageScheme_) {
1679 0 : case StorageOpScheme::Invalid: {
1680 : // do nothing
1681 0 : break;
1682 : }
1683 61603 : case StorageOpScheme::FacilityDemandStoreExcessOnSite: // these are both the same because adjusted feed in request has already accounted for the
1684 : // difference
1685 : case StorageOpScheme::MeterDemandStoreExcessOnSite: {
1686 : // this is the legacy behavior but with more limits from storage control operation information
1687 :
1688 : // no draws from main panel
1689 61603 : storOpCVDrawRate = 0.0;
1690 :
1691 61603 : if (storOpCVGenRate < adjustedFeedInRequest) {
1692 : // draw from storage
1693 40431 : storOpCVDischargeRate = adjustedFeedInRequest - storOpCVGenRate;
1694 40431 : storOpCVChargeRate = 0.0;
1695 40431 : storOpIsDischarging = true;
1696 40431 : storOpIsCharging = false;
1697 :
1698 21172 : } else if (storOpCVGenRate > adjustedFeedInRequest) {
1699 : // add to storage
1700 12305 : storOpCVDischargeRate = 0.0;
1701 12305 : storOpCVChargeRate = storOpCVGenRate - adjustedFeedInRequest;
1702 12305 : storOpIsCharging = true;
1703 12305 : storOpIsDischarging = false;
1704 :
1705 8867 : } else if (storOpCVGenRate == adjustedFeedInRequest) {
1706 : // do nothing
1707 8867 : storOpCVDischargeRate = 0.0;
1708 8867 : storOpCVChargeRate = 0.0;
1709 8867 : storOpIsCharging = false;
1710 8867 : storOpIsDischarging = false;
1711 : }
1712 61603 : break;
1713 : }
1714 :
1715 52029 : case StorageOpScheme::ChargeDischargeSchedules: {
1716 52029 : storOpCVChargeRate = designStorageChargePower_ * storageChargeModSched_->getCurrentVal();
1717 52029 : storOpCVDischargeRate = designStorageDischargePower_ * storageDischargeModSched_->getCurrentVal();
1718 52029 : Real64 genAndStorSum = storOpCVGenRate + storOpCVDischargeRate - storOpCVChargeRate;
1719 52029 : if (genAndStorSum >= 0.0) { // power to feed toward main panel
1720 11760 : storOpCVDrawRate = 0.0;
1721 11760 : storOpCVFeedInRate = genAndStorSum;
1722 : } else { // shortfall, will need to draw from main panel (e.g. for grid charging)
1723 40269 : storOpCVFeedInRate = 0.0;
1724 40269 : storOpCVDrawRate = std::abs(genAndStorSum);
1725 : }
1726 52029 : if (storOpCVChargeRate > 0.0) {
1727 40269 : storOpIsCharging = true;
1728 : } else {
1729 11760 : storOpIsCharging = false;
1730 : }
1731 52029 : if (storOpCVDischargeRate > 0.0) {
1732 10080 : storOpIsDischarging = true;
1733 : } else {
1734 41949 : storOpIsDischarging = false;
1735 : }
1736 52029 : break;
1737 : }
1738 17343 : case StorageOpScheme::FacilityDemandLeveling: {
1739 :
1740 17343 : if (adjustedDrawRequest > 0.0) { // the only reason to draw instead of feed is to charge storage
1741 9529 : storOpCVFeedInRate = 0.0;
1742 9529 : storOpCVDrawRate = adjustedDrawRequest;
1743 9529 : storOpCVChargeRate = storOpCVDrawRate + storOpCVGenRate;
1744 9529 : storOpCVDischargeRate = 0.0;
1745 9529 : storOpIsCharging = true;
1746 9529 : storOpIsDischarging = false;
1747 : }
1748 17343 : if (adjustedFeedInRequest > 0.0) {
1749 7814 : storOpCVDrawRate = 0.0;
1750 7814 : storOpCVFeedInRate = adjustedFeedInRequest;
1751 7814 : if (storOpCVGenRate < adjustedFeedInRequest) {
1752 : // draw from storage
1753 7814 : storOpCVDischargeRate = adjustedFeedInRequest - storOpCVGenRate;
1754 7814 : storOpCVChargeRate = 0.0;
1755 7814 : storOpIsDischarging = true;
1756 7814 : storOpIsCharging = false;
1757 :
1758 0 : } else if (storOpCVGenRate > adjustedFeedInRequest) {
1759 : // add to storage
1760 0 : storOpCVDischargeRate = 0.0;
1761 0 : storOpCVChargeRate = storOpCVGenRate - adjustedFeedInRequest;
1762 0 : storOpIsCharging = true;
1763 0 : storOpIsDischarging = false;
1764 :
1765 0 : } else if (storOpCVGenRate == adjustedFeedInRequest) {
1766 : // do nothing
1767 0 : storOpCVDischargeRate = 0.0;
1768 0 : storOpCVChargeRate = 0.0;
1769 0 : storOpIsCharging = false;
1770 0 : storOpIsDischarging = false;
1771 : }
1772 : }
1773 17343 : break;
1774 : }
1775 130975 : default:
1776 : assert(true);
1777 : }
1778 :
1779 : // handle EMS overrides
1780 130975 : if (eMSOverridePelFromStorage_ || eMSOverridePelIntoStorage_) {
1781 32374 : if (eMSOverridePelFromStorage_ && !eMSOverridePelIntoStorage_) {
1782 : // EMS is calling for specific discharge rate
1783 0 : storOpCVDischargeRate = max(eMSValuePelFromStorage_, 0.0);
1784 0 : storOpCVChargeRate = 0.0;
1785 0 : storOpIsDischarging = true;
1786 0 : storOpIsCharging = false;
1787 32374 : } else if (!eMSOverridePelFromStorage_ && eMSOverridePelIntoStorage_) {
1788 : // EMS is calling for specific charge rate
1789 0 : storOpCVChargeRate = max(eMSValuePelIntoStorage_, 0.0);
1790 0 : storOpCVDischargeRate = 0.0;
1791 0 : storOpIsDischarging = false;
1792 0 : storOpIsCharging = true;
1793 32374 : } else if (eMSOverridePelFromStorage_ && eMSOverridePelIntoStorage_) {
1794 : // EMS is asking to override both
1795 32374 : if (eMSValuePelIntoStorage_ > eMSValuePelFromStorage_) {
1796 2757 : storOpCVChargeRate = eMSValuePelIntoStorage_ - eMSValuePelFromStorage_;
1797 2757 : storOpCVDischargeRate = 0.0;
1798 2757 : storOpIsDischarging = false;
1799 2757 : storOpIsCharging = true;
1800 29617 : } else if (eMSValuePelIntoStorage_ < eMSValuePelFromStorage_) {
1801 26252 : storOpCVDischargeRate = eMSValuePelFromStorage_ - eMSValuePelIntoStorage_;
1802 26252 : storOpCVChargeRate = 0.0;
1803 26252 : storOpIsDischarging = true;
1804 26252 : storOpIsCharging = false;
1805 : } else { // they equal just hold
1806 3365 : storOpCVDischargeRate = 0.0;
1807 3365 : storOpCVChargeRate = 0.0;
1808 3365 : storOpIsDischarging = false;
1809 3365 : storOpIsCharging = false;
1810 : }
1811 : }
1812 : }
1813 :
1814 : // check against the controller limits
1815 130975 : if (designStorageChargePowerWasSet_) {
1816 69372 : storOpCVChargeRate = min(storOpCVChargeRate, designStorageChargePower_);
1817 : }
1818 130975 : if (designStorageDischargePowerWasSet_) {
1819 69372 : storOpCVDischargeRate = min(storOpCVDischargeRate, designStorageDischargePower_);
1820 : }
1821 :
1822 : // dispatch final request to storage device, calculate, update, and report storage device, passing what controller wants for SOC limits
1823 :
1824 130975 : storageObj->simulate(
1825 130975 : state, storOpCVChargeRate, storOpCVDischargeRate, storOpIsCharging, storOpIsDischarging, maxStorageSOCFraction_, minStorageSOCFraction_);
1826 :
1827 : // rebalance with final charge and discharge rates
1828 130975 : Real64 genAndStorSum = storOpCVGenRate + storOpCVDischargeRate - storOpCVChargeRate;
1829 130975 : if (genAndStorSum >= 0.0) { // power to feed toward main panel
1830 103714 : storOpCVDrawRate = 0.0;
1831 103714 : storOpCVFeedInRate = genAndStorSum;
1832 : } else { // shortfall, will need to draw from main panel (e.g. for grid charging)
1833 27261 : storOpCVFeedInRate = 0.0;
1834 27261 : storOpCVDrawRate = std::abs(genAndStorSum);
1835 : }
1836 130975 : }
1837 :
1838 1468 : void ElectPowerLoadCenter::setupLoadCenterMeterIndices(EnergyPlusData &state)
1839 : {
1840 1468 : demandMeterPtr_ = EnergyPlus::GetMeterIndex(state, demandMeterName_);
1841 1468 : if ((demandMeterPtr_ == 0) && (genOperationScheme_ == GeneratorOpScheme::TrackMeter)) { // throw error
1842 0 : ShowFatalError(
1843 : state,
1844 0 : format("ElectPowerLoadCenter::setupLoadCenterMeterIndices Did not find Meter named: {} in ElectricLoadCenter:Distribution named {}",
1845 0 : demandMeterName_,
1846 0 : name_));
1847 : }
1848 :
1849 1468 : if (storageScheme_ == StorageOpScheme::MeterDemandStoreExcessOnSite) {
1850 0 : trackStorageOpMeterIndex_ = EnergyPlus::GetMeterIndex(state, trackSorageOpMeterName_);
1851 0 : if (trackStorageOpMeterIndex_ == 0) { //
1852 0 : ShowFatalError(
1853 : state,
1854 0 : format("ElectPowerLoadCenter::setupLoadCenterMeterIndices Did not find Meter named: {} in ElectricLoadCenter:Distribution named {}",
1855 0 : trackSorageOpMeterName_,
1856 0 : name_));
1857 : }
1858 : }
1859 1468 : }
1860 :
1861 6097 : void ElectPowerLoadCenter::reinitAtBeginEnvironment()
1862 : {
1863 6097 : genElectricProd = 0.0;
1864 6097 : genElectProdRate = 0.0;
1865 6097 : thermalProd = 0.0;
1866 6097 : thermalProdRate = 0.0;
1867 6097 : totalPowerRequest_ = 0.0;
1868 6097 : totalThermalPowerRequest_ = 0.0;
1869 6097 : subpanelFeedInRate = 0.0;
1870 6097 : subpanelDrawRate = 0.0;
1871 6097 : storOpCVDrawRate = 0.0;
1872 6097 : storOpCVFeedInRate = 0.0;
1873 6097 : storOpCVChargeRate = 0.0;
1874 6097 : storOpCVDischargeRate = 0.0;
1875 :
1876 6097 : if (generatorsPresent_ && numGenerators > 0) {
1877 820 : for (auto &g : elecGenCntrlObj) {
1878 661 : g->reinitAtBeginEnvironment();
1879 159 : }
1880 : }
1881 :
1882 6097 : if (transformerObj != nullptr) {
1883 0 : transformerObj->reinitAtBeginEnvironment();
1884 : }
1885 :
1886 6097 : if (storageObj != nullptr) {
1887 83 : storageObj->reinitAtBeginEnvironment();
1888 : }
1889 :
1890 6097 : if (inverterObj != nullptr) {
1891 104 : inverterObj->reinitAtBeginEnvironment();
1892 : }
1893 :
1894 6097 : if (converterObj != nullptr) {
1895 36 : converterObj->reinitAtBeginEnvironment();
1896 : }
1897 6097 : }
1898 :
1899 5362 : void ElectPowerLoadCenter::reinitZoneGainsAtBeginEnvironment()
1900 : {
1901 5362 : if (transformerObj != nullptr) {
1902 0 : transformerObj->reinitZoneGainsAtBeginEnvironment();
1903 : }
1904 :
1905 5362 : if (storageObj != nullptr) {
1906 74 : storageObj->reinitZoneGainsAtBeginEnvironment();
1907 : }
1908 :
1909 5362 : if (inverterObj != nullptr) {
1910 92 : inverterObj->reinitZoneGainsAtBeginEnvironment();
1911 : }
1912 :
1913 5362 : if (converterObj != nullptr) {
1914 32 : converterObj->reinitZoneGainsAtBeginEnvironment();
1915 : }
1916 5362 : }
1917 :
1918 21 : std::string const &ElectPowerLoadCenter::generatorListName() const
1919 : {
1920 21 : return generatorListName_;
1921 : }
1922 :
1923 18674454 : void ElectPowerLoadCenter::updateLoadCenterGeneratorRecords(EnergyPlusData &state)
1924 : {
1925 :
1926 18674454 : switch (bussType) {
1927 233462 : case ElectricBussType::ACBuss: {
1928 233462 : genElectProdRate = 0.0;
1929 233462 : genElectricProd = 0.0;
1930 649420 : for (auto const &gc : elecGenCntrlObj) {
1931 415958 : genElectProdRate += gc->electProdRate;
1932 415958 : genElectricProd += gc->electricityProd;
1933 233462 : }
1934 : // no inverter, no storage, so generator production equals subpanel feed in
1935 233462 : subpanelFeedInRate = genElectProdRate;
1936 233462 : if (transformerObj != nullptr) {
1937 0 : subpanelFeedInRate -= transformerObj->getLossRateForInputPower(state, genElectProdRate);
1938 : }
1939 233462 : subpanelDrawRate = 0.0;
1940 :
1941 233462 : break;
1942 : }
1943 0 : case ElectricBussType::ACBussStorage: {
1944 0 : genElectProdRate = 0.0;
1945 0 : genElectricProd = 0.0;
1946 0 : for (auto const &gc : elecGenCntrlObj) {
1947 0 : genElectProdRate += gc->electProdRate;
1948 0 : genElectricProd += gc->electricityProd;
1949 0 : }
1950 0 : if (storageObj != nullptr) {
1951 0 : subpanelFeedInRate = genElectProdRate + storOpCVDischargeRate - storOpCVChargeRate;
1952 : } else {
1953 0 : subpanelFeedInRate = genElectProdRate;
1954 : }
1955 0 : if (transformerObj != nullptr) {
1956 0 : subpanelFeedInRate -= transformerObj->getLossRateForInputPower(state, subpanelFeedInRate);
1957 : }
1958 0 : subpanelDrawRate = 0.0;
1959 0 : break;
1960 : }
1961 30464 : case ElectricBussType::DCBussInverter: {
1962 30464 : genElectProdRate = 0.0;
1963 30464 : genElectricProd = 0.0;
1964 276018 : for (auto const &gc : elecGenCntrlObj) {
1965 245554 : genElectProdRate += gc->electProdRate;
1966 245554 : genElectricProd += gc->electricityProd;
1967 30464 : }
1968 :
1969 30464 : if (inverterObj != nullptr) {
1970 30464 : subpanelFeedInRate = inverterObj->aCPowerOut();
1971 : }
1972 30464 : if (transformerObj != nullptr) {
1973 0 : subpanelFeedInRate -= transformerObj->getLossRateForInputPower(state, subpanelFeedInRate);
1974 : }
1975 30464 : subpanelDrawRate = 0.0;
1976 30464 : break;
1977 : }
1978 :
1979 261950 : case ElectricBussType::DCBussInverterDCStorage: {
1980 261950 : genElectProdRate = 0.0;
1981 261950 : genElectricProd = 0.0;
1982 1173460 : for (auto const &gc : elecGenCntrlObj) {
1983 911510 : genElectProdRate += gc->electProdRate;
1984 911510 : genElectricProd += gc->electricityProd;
1985 261950 : }
1986 261950 : if (inverterObj != nullptr) {
1987 261950 : subpanelFeedInRate = inverterObj->aCPowerOut();
1988 : }
1989 :
1990 261950 : if (converterObj != nullptr) {
1991 138744 : subpanelDrawRate = converterObj->aCPowerIn();
1992 : }
1993 261950 : if (transformerObj != nullptr) {
1994 0 : subpanelFeedInRate -= transformerObj->getLossRateForInputPower(state, subpanelFeedInRate);
1995 0 : subpanelDrawRate += transformerObj->getLossRateForOutputPower(state, subpanelDrawRate);
1996 : }
1997 261950 : break;
1998 : }
1999 0 : case ElectricBussType::DCBussInverterACStorage: {
2000 0 : genElectProdRate = 0.0;
2001 0 : genElectricProd = 0.0;
2002 0 : for (auto const &gc : elecGenCntrlObj) {
2003 0 : genElectProdRate += gc->electProdRate;
2004 0 : genElectricProd += gc->electricityProd;
2005 0 : }
2006 0 : if (inverterObj != nullptr && storagePresent_) {
2007 0 : subpanelFeedInRate = inverterObj->aCPowerOut() + storOpCVDischargeRate - storOpCVChargeRate;
2008 : }
2009 :
2010 0 : subpanelDrawRate = storOpCVDrawRate; // no converter for AC storage
2011 0 : if (transformerObj != nullptr) {
2012 0 : subpanelFeedInRate -= transformerObj->getLossRateForInputPower(state, subpanelFeedInRate);
2013 0 : subpanelDrawRate += transformerObj->getLossRateForOutputPower(state, subpanelDrawRate);
2014 : }
2015 0 : break;
2016 : }
2017 18148578 : case ElectricBussType::Invalid: {
2018 : // do nothing
2019 18148578 : break;
2020 : }
2021 0 : default:
2022 0 : assert(false);
2023 : } // end switch
2024 18674454 : thermalProdRate = 0.0;
2025 18674454 : thermalProd = 0.0;
2026 20247476 : for (auto const &gc : elecGenCntrlObj) {
2027 1573022 : thermalProdRate += gc->thermProdRate;
2028 1573022 : thermalProd += gc->thermalProd;
2029 18674454 : }
2030 18674454 : }
2031 :
2032 11280 : Real64 ElectPowerLoadCenter::calcLoadCenterThermalLoad(EnergyPlusData &state)
2033 : {
2034 11280 : if (myCoGenSetupFlag_) {
2035 2 : for (auto &g : elecGenCntrlObj) {
2036 1 : bool plantNotFound = false;
2037 1 : PlantUtilities::ScanPlantLoopsForObject(state, g->compPlantName, g->compPlantType, g->cogenLocation, plantNotFound, _, _, _, _, _);
2038 1 : if (!plantNotFound) {
2039 1 : g->plantInfoFound = true;
2040 : }
2041 1 : }
2042 1 : myCoGenSetupFlag_ = false;
2043 : } // cogen setup
2044 :
2045 : // sum up "MyLoad" for all generators on this load center from plant structure
2046 11280 : Real64 thermalLoad = 0.0;
2047 22560 : for (auto &g : elecGenCntrlObj) {
2048 11280 : if (g->plantInfoFound) {
2049 11280 : thermalLoad += state.dataPlnt->PlantLoop(g->cogenLocation.loopNum)
2050 11280 : .LoopSide(g->cogenLocation.loopSideNum)
2051 11280 : .Branch(g->cogenLocation.branchNum)
2052 11280 : .Comp(g->cogenLocation.compNum)
2053 11280 : .MyLoad;
2054 : }
2055 11280 : }
2056 11280 : return thermalLoad;
2057 : }
2058 :
2059 : // TODO: Constructors should not do this much work
2060 85 : GeneratorController::GeneratorController(EnergyPlusData &state,
2061 : std::string const &objectName,
2062 : std::string const &objectType,
2063 : Real64 ratedElecPowerOutput,
2064 : std::string const &availSchedName,
2065 85 : Real64 thermalToElectRatio)
2066 170 : : generatorType(GeneratorType::Invalid), compPlantType(DataPlant::PlantEquipmentType::Invalid), generatorIndex(0), maxPowerOut(0.0),
2067 85 : powerRequestThisTimestep(0.0), onThisTimestep(false), eMSPowerRequest(0.0), eMSRequestOn(false), plantInfoFound(false),
2068 170 : cogenLocation(PlantLocation(0, DataPlant::LoopSideLocation::Invalid, 0, 0)), nominalThermElectRatio(0.0), dCElectricityProd(0.0),
2069 85 : dCElectProdRate(0.0), electricityProd(0.0), electProdRate(0.0), thermalProd(0.0), thermProdRate(0.0), pvwattsGenerator(nullptr),
2070 85 : errCountNegElectProd_(0)
2071 : {
2072 :
2073 : static constexpr std::string_view routineName = "GeneratorController constructor ";
2074 :
2075 85 : auto &s_ipsc = state.dataIPShortCut;
2076 :
2077 85 : ErrorObjectHeader eoh{routineName, objectType, objectName};
2078 :
2079 85 : name = objectName;
2080 :
2081 85 : generatorType = static_cast<GeneratorType>(getEnumValue(generatorTypeNamesUC, Util::makeUPPER(objectType)));
2082 85 : switch (generatorType) {
2083 5 : case GeneratorType::ICEngine: {
2084 5 : compPlantType = DataPlant::PlantEquipmentType::Generator_ICEngine;
2085 5 : compPlantName = name;
2086 5 : break;
2087 : }
2088 6 : case GeneratorType::CombTurbine: {
2089 6 : compPlantType = DataPlant::PlantEquipmentType::Generator_CTurbine;
2090 6 : compPlantName = name;
2091 6 : break;
2092 : }
2093 6 : case GeneratorType::Microturbine: {
2094 6 : compPlantType = DataPlant::PlantEquipmentType::Generator_MicroTurbine;
2095 6 : compPlantName = name;
2096 6 : break;
2097 : }
2098 58 : case GeneratorType::PV: {
2099 58 : compPlantType = DataPlant::PlantEquipmentType::PVTSolarCollectorFlatPlate;
2100 58 : compPlantName = name;
2101 58 : break;
2102 : }
2103 3 : case GeneratorType::PVWatts: {
2104 3 : compPlantType = DataPlant::PlantEquipmentType::Invalid;
2105 :
2106 3 : int ObjNum = state.dataInputProcessing->inputProcessor->getObjectItemNum(state, "Generator:PVWatts", Util::makeUPPER(objectName));
2107 3 : assert(ObjNum >= 0);
2108 3 : if (ObjNum == 0) {
2109 0 : ShowFatalError(state, format("Cannot find Generator:PVWatts {}", objectName));
2110 : }
2111 3 : pvwattsGenerator = PVWatts::PVWattsGenerator::createFromIdfObj(state, ObjNum);
2112 3 : pvwattsGenerator->setupOutputVariables(state);
2113 3 : break;
2114 : }
2115 2 : case GeneratorType::FuelCell: {
2116 : // fuel cell has two possible plant component types, stack cooler and exhaust gas HX.
2117 : // exhaust gas HX is required and it assumed that it has more thermal capacity and is used for control
2118 2 : compPlantType = DataPlant::PlantEquipmentType::Generator_FCExhaust;
2119 : // and the name of plant component is not the same as the generator because of child object references, so fetch that name
2120 2 : auto *thisFC = FuelCellElectricGenerator::FCDataStruct::factory(state, name);
2121 2 : compPlantName = dynamic_cast<FuelCellElectricGenerator::FCDataStruct *>(thisFC)->ExhaustHX.Name;
2122 2 : break;
2123 : }
2124 2 : case GeneratorType::MicroCHP: {
2125 2 : compPlantType = DataPlant::PlantEquipmentType::Generator_MicroCHP;
2126 2 : compPlantName = name;
2127 2 : break;
2128 : }
2129 3 : case GeneratorType::WindTurbine: {
2130 3 : compPlantType = DataPlant::PlantEquipmentType::Invalid;
2131 3 : break;
2132 : }
2133 0 : default: {
2134 0 : ShowSevereError(state, format("{}{} invalid entry.", routineName, s_ipsc->cCurrentModuleObject));
2135 0 : ShowContinueError(state, format("Invalid {} associated with generator = {}", objectType, objectName));
2136 0 : break;
2137 : }
2138 : }
2139 :
2140 85 : if (availSchedName.empty()) {
2141 48 : availSched = Sched::GetScheduleAlwaysOn(state);
2142 37 : } else if ((availSched = Sched::GetSchedule(state, availSchedName)) == nullptr) {
2143 0 : ShowSevereItemNotFound(state, eoh, "Availability Schedule Name", availSchedName);
2144 37 : } else if (generatorType == GeneratorType::PVWatts) {
2145 0 : ShowWarningCustom(state, eoh, "Availability Schedule will be ignored (runs all the time).");
2146 37 : } else if (generatorType == GeneratorType::PV) {
2147 : // It should only warn if Performance type is SimplePV (DataPhotovoltaics::iSimplePVModel).
2148 : // Except you need GetPVInput to have run already etc
2149 : // Note: you can't use s_ipsc->cAlphaArgs etc or it'll override what will still need to be processed in
2150 : // ElectPowerLoadCenter::ElectPowerLoadCenter after this function is called
2151 13 : int PVNum = state.dataInputProcessing->inputProcessor->getObjectItemNum(state, objectType, Util::makeUPPER(objectName));
2152 : int NumAlphas; // Number of PV Array parameter alpha names being passed
2153 : int NumNums; // Number of PV Array numeric parameters are being passed
2154 : int IOStat;
2155 13 : Array1D_string Alphas(5); // Alpha items for object
2156 13 : Array1D<Real64> Numbers(2); // Numeric items for object
2157 13 : state.dataInputProcessing->inputProcessor->getObjectItem(state, objectType, PVNum, Alphas, NumAlphas, Numbers, NumNums, IOStat);
2158 13 : if (Util::SameString(Alphas(3), "PhotovoltaicPerformance:Simple")) {
2159 0 : ShowWarningCustom(state, eoh, "Availability Schedule will be ignored (runs all the time).");
2160 0 : ShowContinueError(state, "To limit this Generator:Photovoltaic's output, please use the Inverter's availability schedule instead.");
2161 : }
2162 13 : }
2163 :
2164 85 : maxPowerOut = ratedElecPowerOutput, nominalThermElectRatio = thermalToElectRatio;
2165 :
2166 170 : SetupOutputVariable(state,
2167 : "Generator Requested Electricity Rate",
2168 : Constant::Units::W,
2169 85 : powerRequestThisTimestep,
2170 : OutputProcessor::TimeStepType::System,
2171 : OutputProcessor::StoreType::Average,
2172 : objectName);
2173 85 : if (state.dataGlobal->AnyEnergyManagementSystemInModel) {
2174 0 : SetupEMSInternalVariable(state, "Generator Nominal Maximum Power", objectName, "[W]", maxPowerOut);
2175 0 : SetupEMSInternalVariable(state, "Generator Nominal Thermal To Electric Ratio", objectName, "[ratio]", nominalThermElectRatio);
2176 0 : SetupEMSActuator(state, "On-Site Generator Control", objectName, "Requested Power", "[W]", eMSRequestOn, eMSPowerRequest);
2177 : }
2178 85 : }
2179 :
2180 661 : void GeneratorController::reinitAtBeginEnvironment()
2181 : {
2182 661 : onThisTimestep = false;
2183 661 : dCElectricityProd = 0.0;
2184 661 : dCElectProdRate = 0.0;
2185 661 : electricityProd = 0.0;
2186 661 : electProdRate = 0.0;
2187 661 : thermalProd = 0.0;
2188 661 : thermProdRate = 0.0;
2189 661 : }
2190 :
2191 786511 : void GeneratorController::simGeneratorGetPowerOutput(EnergyPlusData &state,
2192 : bool const runFlag,
2193 : Real64 const myElecLoadRequest,
2194 : bool const FirstHVACIteration, // Unused 2010 JANUARY
2195 : Real64 &electricPowerOutput, // Actual generator electric power output
2196 : Real64 &thermalPowerOutput // Actual generator thermal power output
2197 : )
2198 : {
2199 : // Select and call models and also collect results for load center power conditioning and reporting
2200 786511 : switch (generatorType) {
2201 44136 : case GeneratorType::ICEngine: {
2202 :
2203 44136 : auto *thisICE = ICEngineElectricGenerator::ICEngineGeneratorSpecs::factory(state, name);
2204 :
2205 : // dummy vars
2206 44136 : PlantLocation L(0, DataPlant::LoopSideLocation::Invalid, 0, 0);
2207 44136 : Real64 tempLoad = myElecLoadRequest;
2208 :
2209 : // simulate
2210 44136 : dynamic_cast<ICEngineElectricGenerator::ICEngineGeneratorSpecs *>(thisICE)->InitICEngineGenerators(state, runFlag, FirstHVACIteration);
2211 44136 : dynamic_cast<ICEngineElectricGenerator::ICEngineGeneratorSpecs *>(thisICE)->CalcICEngineGeneratorModel(state, runFlag, tempLoad);
2212 44136 : dynamic_cast<ICEngineElectricGenerator::ICEngineGeneratorSpecs *>(thisICE)->update(state);
2213 44136 : electProdRate = dynamic_cast<ICEngineElectricGenerator::ICEngineGeneratorSpecs *>(thisICE)->ElecPowerGenerated;
2214 44136 : electricityProd = dynamic_cast<ICEngineElectricGenerator::ICEngineGeneratorSpecs *>(thisICE)->ElecEnergyGenerated;
2215 44136 : thermProdRate = dynamic_cast<ICEngineElectricGenerator::ICEngineGeneratorSpecs *>(thisICE)->QTotalHeatRecovered;
2216 44136 : thermalProd = dynamic_cast<ICEngineElectricGenerator::ICEngineGeneratorSpecs *>(thisICE)->TotalHeatEnergyRec;
2217 44136 : electricPowerOutput = electProdRate;
2218 44136 : thermalPowerOutput = thermProdRate;
2219 44136 : break;
2220 : }
2221 55416 : case GeneratorType::CombTurbine: {
2222 :
2223 55416 : auto *thisCTE = CTElectricGenerator::CTGeneratorData::factory(state, name);
2224 : // dummy vars
2225 55416 : PlantLocation L(0, DataPlant::LoopSideLocation::Invalid, 0, 0);
2226 55416 : Real64 tempLoad = myElecLoadRequest;
2227 :
2228 : // simulate
2229 55416 : thisCTE->simulate(state, L, FirstHVACIteration, tempLoad, runFlag);
2230 55416 : dynamic_cast<CTElectricGenerator::CTGeneratorData *>(thisCTE)->InitCTGenerators(state, runFlag, FirstHVACIteration);
2231 55416 : dynamic_cast<CTElectricGenerator::CTGeneratorData *>(thisCTE)->CalcCTGeneratorModel(state, runFlag, tempLoad, FirstHVACIteration);
2232 55416 : electProdRate = dynamic_cast<CTElectricGenerator::CTGeneratorData *>(thisCTE)->ElecPowerGenerated;
2233 55416 : electricityProd = dynamic_cast<CTElectricGenerator::CTGeneratorData *>(thisCTE)->ElecEnergyGenerated;
2234 55416 : thermProdRate = dynamic_cast<CTElectricGenerator::CTGeneratorData *>(thisCTE)->QTotalHeatRecovered;
2235 55416 : thermalProd = dynamic_cast<CTElectricGenerator::CTGeneratorData *>(thisCTE)->TotalHeatEnergyRec;
2236 55416 : electricPowerOutput = electProdRate;
2237 55416 : thermalPowerOutput = thermProdRate;
2238 55416 : break;
2239 : }
2240 571944 : case GeneratorType::PV: {
2241 571944 : Photovoltaics::SimPVGenerator(state, GeneratorType::PV, name, generatorIndex, runFlag, myElecLoadRequest);
2242 571944 : Photovoltaics::GetPVGeneratorResults(
2243 571944 : state, GeneratorType::PV, generatorIndex, dCElectProdRate, dCElectricityProd, thermProdRate, thermalProd);
2244 571944 : electricPowerOutput = dCElectProdRate;
2245 571944 : thermalPowerOutput = thermProdRate;
2246 571944 : break;
2247 : }
2248 6588 : case GeneratorType::PVWatts: {
2249 6588 : pvwattsGenerator->calc(state);
2250 6588 : pvwattsGenerator->getResults(dCElectProdRate, dCElectricityProd, thermProdRate, thermalProd);
2251 6588 : electricPowerOutput = dCElectProdRate;
2252 6588 : thermalPowerOutput = thermProdRate;
2253 6588 : break;
2254 : }
2255 23433 : case GeneratorType::FuelCell: {
2256 23433 : auto *thisFC = FuelCellElectricGenerator::FCDataStruct::factory(state, name);
2257 23433 : dynamic_cast<FuelCellElectricGenerator::FCDataStruct *>(thisFC)->SimFuelCellGenerator(state, runFlag, myElecLoadRequest, FirstHVACIteration);
2258 23433 : electProdRate = dynamic_cast<FuelCellElectricGenerator::FCDataStruct *>(thisFC)->Report.ACPowerGen;
2259 23433 : electricityProd = dynamic_cast<FuelCellElectricGenerator::FCDataStruct *>(thisFC)->Report.ACEnergyGen;
2260 23433 : thermProdRate = dynamic_cast<FuelCellElectricGenerator::FCDataStruct *>(thisFC)->Report.qHX;
2261 23433 : thermalProd = dynamic_cast<FuelCellElectricGenerator::FCDataStruct *>(thisFC)->Report.HXenergy;
2262 23433 : electricPowerOutput = electProdRate;
2263 23433 : thermalPowerOutput = thermProdRate;
2264 23433 : break;
2265 : }
2266 17415 : case GeneratorType::MicroCHP: {
2267 17415 : auto *thisMCHP = MicroCHPElectricGenerator::MicroCHPDataStruct::factory(state, name);
2268 :
2269 : // simulate
2270 17415 : dynamic_cast<MicroCHPElectricGenerator::MicroCHPDataStruct *>(thisMCHP)->InitMicroCHPNoNormalizeGenerators(state);
2271 :
2272 17415 : if (!state.dataPlnt->PlantFirstSizeCompleted) {
2273 2087 : break;
2274 : }
2275 :
2276 15328 : dynamic_cast<MicroCHPElectricGenerator::MicroCHPDataStruct *>(thisMCHP)->CalcMicroCHPNoNormalizeGeneratorModel(
2277 : state, runFlag, false, myElecLoadRequest, DataPrecisionGlobals::constant_zero);
2278 15328 : dynamic_cast<MicroCHPElectricGenerator::MicroCHPDataStruct *>(thisMCHP)->CalcUpdateHeatRecovery(state);
2279 15328 : dynamic_cast<MicroCHPElectricGenerator::MicroCHPDataStruct *>(thisMCHP)->UpdateMicroCHPGeneratorRecords(state);
2280 :
2281 15328 : electProdRate = dynamic_cast<MicroCHPElectricGenerator::MicroCHPDataStruct *>(thisMCHP)->A42Model.ACPowerGen;
2282 15328 : electricityProd = dynamic_cast<MicroCHPElectricGenerator::MicroCHPDataStruct *>(thisMCHP)->A42Model.ACEnergyGen;
2283 15328 : thermProdRate = dynamic_cast<MicroCHPElectricGenerator::MicroCHPDataStruct *>(thisMCHP)->A42Model.QdotHR;
2284 15328 : thermalProd = dynamic_cast<MicroCHPElectricGenerator::MicroCHPDataStruct *>(thisMCHP)->A42Model.TotalHeatEnergyRec;
2285 15328 : electricPowerOutput = electProdRate;
2286 15328 : thermalPowerOutput = thermProdRate;
2287 15328 : break;
2288 : }
2289 51343 : case GeneratorType::Microturbine: {
2290 51343 : auto *thisMTG = MicroturbineElectricGenerator::MTGeneratorSpecs::factory(state, name);
2291 :
2292 : // dummy vars
2293 51343 : PlantLocation L(0, DataPlant::LoopSideLocation::Invalid, 0, 0);
2294 51343 : Real64 tempLoad = myElecLoadRequest;
2295 :
2296 : // simulate
2297 51343 : dynamic_cast<MicroturbineElectricGenerator::MTGeneratorSpecs *>(thisMTG)->InitMTGenerators(state, runFlag, tempLoad, FirstHVACIteration);
2298 51343 : dynamic_cast<MicroturbineElectricGenerator::MTGeneratorSpecs *>(thisMTG)->CalcMTGeneratorModel(state, runFlag, tempLoad);
2299 51343 : dynamic_cast<MicroturbineElectricGenerator::MTGeneratorSpecs *>(thisMTG)->UpdateMTGeneratorRecords(state);
2300 51343 : electProdRate = dynamic_cast<MicroturbineElectricGenerator::MTGeneratorSpecs *>(thisMTG)->ElecPowerGenerated;
2301 51343 : electricityProd = dynamic_cast<MicroturbineElectricGenerator::MTGeneratorSpecs *>(thisMTG)->EnergyGen;
2302 51343 : thermProdRate = dynamic_cast<MicroturbineElectricGenerator::MTGeneratorSpecs *>(thisMTG)->QHeatRecovered;
2303 51343 : thermalProd = dynamic_cast<MicroturbineElectricGenerator::MTGeneratorSpecs *>(thisMTG)->ExhaustEnergyRec;
2304 51343 : electricPowerOutput = electProdRate;
2305 51343 : thermalPowerOutput = thermProdRate;
2306 51343 : break;
2307 : }
2308 16236 : case GeneratorType::WindTurbine: {
2309 16236 : WindTurbine::SimWindTurbine(state, GeneratorType::WindTurbine, name, generatorIndex, runFlag, myElecLoadRequest);
2310 16236 : WindTurbine::GetWTGeneratorResults(
2311 16236 : state, GeneratorType::WindTurbine, generatorIndex, electProdRate, electricityProd, thermProdRate, thermalProd);
2312 16236 : electricPowerOutput = electProdRate;
2313 16236 : thermalPowerOutput = thermProdRate;
2314 16236 : break;
2315 : }
2316 0 : case GeneratorType::Invalid:
2317 : case GeneratorType::Num: {
2318 : // do nothing
2319 0 : break;
2320 : }
2321 : } // end switch
2322 :
2323 : // check if generator production has gone wrong and is negative, reset to zero and warn
2324 786511 : if (electricPowerOutput < 0.0) {
2325 0 : if (errCountNegElectProd_ == 0) {
2326 0 : ShowWarningMessage(
2327 : state,
2328 0 : format("{} named {} is producing negative electric power, check generator inputs.", generatorTypeNames[(int)generatorType], name));
2329 0 : ShowContinueError(state, format("Electric power production rate ={:.4R}", electricPowerOutput));
2330 0 : ShowContinueError(state, "The power will be set to zero, and the simulation continues... ");
2331 : }
2332 0 : ShowRecurringWarningErrorAtEnd(state,
2333 0 : format("{} named {} is producing negative electric power ", generatorTypeNames[(int)generatorType], name),
2334 0 : errCountNegElectProd_,
2335 : electricPowerOutput,
2336 : electricPowerOutput);
2337 0 : electricPowerOutput = 0.0;
2338 : }
2339 786511 : }
2340 :
2341 12 : DCtoACInverter::DCtoACInverter(EnergyPlusData &state, std::string const &objectName)
2342 12 : : aCPowerOut_(0.0), aCEnergyOut_(0.0), efficiency_(0.0), dCPowerIn_(0.0), dCEnergyIn_(0.0), conversionLossPower_(0.0), conversionLossEnergy_(0.0),
2343 12 : conversionLossEnergyDecrement_(0.0), thermLossRate_(0.0), thermLossEnergy_(0.0), qdotConvZone_(0.0), qdotRadZone_(0.0), ancillACuseRate_(0.0),
2344 12 : ancillACuseEnergy_(0.0), modelType_(InverterModelType::Invalid), heatLossesDestination_(ThermalLossDestination::Invalid), zoneNum_(0),
2345 12 : zoneRadFract_(0.0), nominalVoltage_(0.0), nomVoltEfficiencyARR_(6, 0.0), ratedPower_(0.0), minPower_(0.0), maxPower_(0.0), minEfficiency_(0.0),
2346 12 : maxEfficiency_(0.0), standbyPower_(0.0)
2347 : {
2348 : // initialize
2349 12 : nomVoltEfficiencyARR_.resize(6, 0.0);
2350 :
2351 : static constexpr std::string_view routineName = "DCtoACInverter constructor ";
2352 :
2353 12 : auto &s_ipsc = state.dataIPShortCut;
2354 :
2355 12 : bool errorsFound = false;
2356 : // if/when add object class name to input object this can be simplified. for now search all possible types
2357 12 : bool foundInverter = false;
2358 12 : int testInvertIndex = 0;
2359 12 : int invertIDFObjectNum = 0;
2360 :
2361 12 : testInvertIndex = state.dataInputProcessing->inputProcessor->getObjectItemNum(state, "ElectricLoadCenter:Inverter:LookUpTable", objectName);
2362 12 : if (testInvertIndex > 0) {
2363 9 : foundInverter = true;
2364 9 : invertIDFObjectNum = testInvertIndex;
2365 9 : s_ipsc->cCurrentModuleObject = "ElectricLoadCenter:Inverter:LookUpTable";
2366 9 : modelType_ = InverterModelType::CECLookUpTableModel;
2367 : }
2368 12 : testInvertIndex = state.dataInputProcessing->inputProcessor->getObjectItemNum(state, "ElectricLoadCenter:Inverter:FunctionOfPower", objectName);
2369 12 : if (testInvertIndex > 0) {
2370 1 : foundInverter = true;
2371 1 : invertIDFObjectNum = testInvertIndex;
2372 1 : s_ipsc->cCurrentModuleObject = "ElectricLoadCenter:Inverter:FunctionOfPower";
2373 1 : modelType_ = InverterModelType::CurveFuncOfPower;
2374 : }
2375 12 : testInvertIndex = state.dataInputProcessing->inputProcessor->getObjectItemNum(state, "ElectricLoadCenter:Inverter:Simple", objectName);
2376 12 : if (testInvertIndex > 0) {
2377 1 : foundInverter = true;
2378 1 : invertIDFObjectNum = testInvertIndex;
2379 1 : s_ipsc->cCurrentModuleObject = "ElectricLoadCenter:Inverter:Simple";
2380 1 : modelType_ = InverterModelType::SimpleConstantEff;
2381 : }
2382 12 : testInvertIndex = state.dataInputProcessing->inputProcessor->getObjectItemNum(state, "ElectricLoadCenter:Inverter:PVWatts", objectName);
2383 12 : if (testInvertIndex > 0) {
2384 1 : foundInverter = true;
2385 1 : invertIDFObjectNum = testInvertIndex;
2386 1 : s_ipsc->cCurrentModuleObject = "ElectricLoadCenter:Inverter:PVWatts";
2387 1 : modelType_ = InverterModelType::PVWatts;
2388 : }
2389 :
2390 12 : if (foundInverter) {
2391 : int NumAlphas; // Number of elements in the alpha array
2392 : int NumNums; // Number of elements in the numeric array
2393 : int IOStat; // IO Status when calling get input subroutine
2394 :
2395 24 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
2396 12 : s_ipsc->cCurrentModuleObject,
2397 : invertIDFObjectNum,
2398 12 : s_ipsc->cAlphaArgs,
2399 : NumAlphas,
2400 12 : s_ipsc->rNumericArgs,
2401 : NumNums,
2402 : IOStat,
2403 12 : s_ipsc->lNumericFieldBlanks,
2404 12 : s_ipsc->lAlphaFieldBlanks,
2405 12 : s_ipsc->cAlphaFieldNames,
2406 12 : s_ipsc->cNumericFieldNames);
2407 :
2408 12 : ErrorObjectHeader eoh{routineName, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)};
2409 12 : name_ = s_ipsc->cAlphaArgs(1);
2410 : // how to verify names are unique across objects? add to GlobalNames?
2411 :
2412 12 : if (modelType_ == InverterModelType::PVWatts) {
2413 1 : availSched_ = Sched::GetScheduleAlwaysOn(state);
2414 1 : zoneNum_ = 0;
2415 1 : heatLossesDestination_ = ThermalLossDestination::LostToOutside;
2416 1 : zoneRadFract_ = 0;
2417 11 : } else if (s_ipsc->lAlphaFieldBlanks(2)) {
2418 0 : availSched_ = Sched::GetScheduleAlwaysOn(state);
2419 : } else {
2420 11 : if ((availSched_ = Sched::GetSchedule(state, s_ipsc->cAlphaArgs(2))) == nullptr) {
2421 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(2), s_ipsc->cAlphaArgs(2));
2422 0 : errorsFound = true;
2423 : }
2424 :
2425 11 : if (s_ipsc->lAlphaFieldBlanks(3)) {
2426 9 : heatLossesDestination_ = ThermalLossDestination::LostToOutside;
2427 2 : } else if ((zoneNum_ = Util::FindItemInList(s_ipsc->cAlphaArgs(3), state.dataHeatBal->Zone)) == 0) {
2428 0 : heatLossesDestination_ = ThermalLossDestination::LostToOutside;
2429 0 : ShowWarningItemNotFound(
2430 0 : state, eoh, s_ipsc->cAlphaFieldNames(3), s_ipsc->cAlphaArgs(3), "Inverter heat losses will not be added to a zone");
2431 : // continue with simulation but inverter losses not sent to a zone.
2432 : } else {
2433 2 : heatLossesDestination_ = ThermalLossDestination::ZoneGains;
2434 : }
2435 11 : zoneRadFract_ = s_ipsc->rNumericArgs(1);
2436 : }
2437 :
2438 : // now the input objects differ depending on class type
2439 12 : switch (modelType_) {
2440 9 : case InverterModelType::CECLookUpTableModel: {
2441 9 : ratedPower_ = s_ipsc->rNumericArgs(2);
2442 9 : standbyPower_ = s_ipsc->rNumericArgs(3);
2443 :
2444 9 : nominalVoltage_ = s_ipsc->rNumericArgs(4);
2445 9 : nomVoltEfficiencyARR_[0] = s_ipsc->rNumericArgs(5);
2446 9 : nomVoltEfficiencyARR_[1] = s_ipsc->rNumericArgs(6);
2447 9 : nomVoltEfficiencyARR_[2] = s_ipsc->rNumericArgs(7);
2448 9 : nomVoltEfficiencyARR_[3] = s_ipsc->rNumericArgs(8);
2449 9 : nomVoltEfficiencyARR_[4] = s_ipsc->rNumericArgs(9);
2450 9 : nomVoltEfficiencyARR_[5] = s_ipsc->rNumericArgs(10);
2451 9 : break;
2452 : }
2453 1 : case InverterModelType::CurveFuncOfPower: {
2454 1 : if (s_ipsc->lAlphaFieldBlanks(4)) {
2455 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(4));
2456 0 : errorsFound = true;
2457 1 : } else if ((effCurve_ = Curve::GetCurve(state, s_ipsc->cAlphaArgs(4))) == nullptr) {
2458 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(4), s_ipsc->cAlphaArgs(4));
2459 0 : errorsFound = true;
2460 : }
2461 :
2462 1 : ratedPower_ = s_ipsc->rNumericArgs(2);
2463 1 : minEfficiency_ = s_ipsc->rNumericArgs(3);
2464 1 : maxEfficiency_ = s_ipsc->rNumericArgs(4);
2465 1 : minPower_ = s_ipsc->rNumericArgs(5);
2466 1 : maxPower_ = s_ipsc->rNumericArgs(6);
2467 1 : standbyPower_ = s_ipsc->rNumericArgs(7);
2468 1 : break;
2469 : }
2470 1 : case InverterModelType::SimpleConstantEff: {
2471 1 : efficiency_ = s_ipsc->rNumericArgs(2);
2472 1 : break;
2473 : }
2474 0 : case InverterModelType::Invalid: {
2475 : // do nothing
2476 0 : break;
2477 : }
2478 1 : case InverterModelType::PVWatts: {
2479 1 : pvWattsDCtoACSizeRatio_ = s_ipsc->rNumericArgs(1);
2480 1 : pvWattsInverterEfficiency_ = s_ipsc->rNumericArgs(2);
2481 1 : break;
2482 : }
2483 0 : default:
2484 0 : assert(false);
2485 : } // end switch modelType
2486 :
2487 24 : SetupOutputVariable(state,
2488 : "Inverter DC to AC Efficiency",
2489 : Constant::Units::None,
2490 12 : efficiency_,
2491 : OutputProcessor::TimeStepType::System,
2492 : OutputProcessor::StoreType::Average,
2493 12 : name_);
2494 24 : SetupOutputVariable(state,
2495 : "Inverter DC Input Electricity Rate",
2496 : Constant::Units::W,
2497 12 : dCPowerIn_,
2498 : OutputProcessor::TimeStepType::System,
2499 : OutputProcessor::StoreType::Average,
2500 12 : name_);
2501 24 : SetupOutputVariable(state,
2502 : "Inverter DC Input Electricity Energy",
2503 : Constant::Units::J,
2504 12 : dCEnergyIn_,
2505 : OutputProcessor::TimeStepType::System,
2506 : OutputProcessor::StoreType::Sum,
2507 12 : name_);
2508 24 : SetupOutputVariable(state,
2509 : "Inverter AC Output Electricity Rate",
2510 : Constant::Units::W,
2511 12 : aCPowerOut_,
2512 : OutputProcessor::TimeStepType::System,
2513 : OutputProcessor::StoreType::Average,
2514 12 : name_);
2515 24 : SetupOutputVariable(state,
2516 : "Inverter AC Output Electricity Energy",
2517 : Constant::Units::J,
2518 12 : aCEnergyOut_,
2519 : OutputProcessor::TimeStepType::System,
2520 : OutputProcessor::StoreType::Sum,
2521 12 : name_);
2522 24 : SetupOutputVariable(state,
2523 : "Inverter Conversion Loss Power",
2524 : Constant::Units::W,
2525 12 : conversionLossPower_,
2526 : OutputProcessor::TimeStepType::System,
2527 : OutputProcessor::StoreType::Average,
2528 12 : name_);
2529 24 : SetupOutputVariable(state,
2530 : "Inverter Conversion Loss Energy",
2531 : Constant::Units::J,
2532 12 : conversionLossEnergy_,
2533 : OutputProcessor::TimeStepType::System,
2534 : OutputProcessor::StoreType::Sum,
2535 12 : name_);
2536 24 : SetupOutputVariable(state,
2537 : "Inverter Conversion Loss Decrement Energy",
2538 : Constant::Units::J,
2539 12 : conversionLossEnergyDecrement_,
2540 : OutputProcessor::TimeStepType::System,
2541 : OutputProcessor::StoreType::Sum,
2542 12 : name_,
2543 : Constant::eResource::ElectricityProduced,
2544 : OutputProcessor::Group::Plant,
2545 : OutputProcessor::EndUseCat::PowerConversion);
2546 24 : SetupOutputVariable(state,
2547 : "Inverter Thermal Loss Rate",
2548 : Constant::Units::W,
2549 12 : thermLossRate_,
2550 : OutputProcessor::TimeStepType::System,
2551 : OutputProcessor::StoreType::Average,
2552 12 : name_);
2553 24 : SetupOutputVariable(state,
2554 : "Inverter Thermal Loss Energy",
2555 : Constant::Units::J,
2556 12 : thermLossEnergy_,
2557 : OutputProcessor::TimeStepType::System,
2558 : OutputProcessor::StoreType::Sum,
2559 12 : name_);
2560 24 : SetupOutputVariable(state,
2561 : "Inverter Ancillary AC Electricity Rate",
2562 : Constant::Units::W,
2563 12 : ancillACuseRate_,
2564 : OutputProcessor::TimeStepType::System,
2565 : OutputProcessor::StoreType::Average,
2566 12 : name_);
2567 24 : SetupOutputVariable(state,
2568 : "Inverter Ancillary AC Electricity Energy",
2569 : Constant::Units::J,
2570 12 : ancillACuseEnergy_,
2571 : OutputProcessor::TimeStepType::System,
2572 : OutputProcessor::StoreType::Sum,
2573 12 : name_,
2574 : Constant::eResource::Electricity,
2575 : OutputProcessor::Group::Plant, // called cogeneration for end use table
2576 : OutputProcessor::EndUseCat::Cogeneration,
2577 : "DCtoACInverter Ancillary");
2578 12 : if (zoneNum_ > 0) {
2579 2 : switch (modelType_) {
2580 0 : case InverterModelType::SimpleConstantEff: {
2581 0 : SetupZoneInternalGain(
2582 : state, zoneNum_, name_, DataHeatBalance::IntGainType::ElectricLoadCenterInverterSimple, &qdotConvZone_, nullptr, &qdotRadZone_);
2583 0 : break;
2584 : }
2585 0 : case InverterModelType::CurveFuncOfPower: {
2586 0 : SetupZoneInternalGain(state,
2587 : zoneNum_,
2588 : name_,
2589 : DataHeatBalance::IntGainType::ElectricLoadCenterInverterFunctionOfPower,
2590 : &qdotConvZone_,
2591 : nullptr,
2592 : &qdotRadZone_);
2593 0 : break;
2594 : }
2595 2 : case InverterModelType::CECLookUpTableModel: {
2596 2 : SetupZoneInternalGain(state,
2597 : zoneNum_,
2598 : name_,
2599 : DataHeatBalance::IntGainType::ElectricLoadCenterInverterLookUpTable,
2600 : &qdotConvZone_,
2601 : nullptr,
2602 : &qdotRadZone_);
2603 2 : break;
2604 : }
2605 0 : case InverterModelType::Invalid: {
2606 : // do nothing
2607 0 : break;
2608 : }
2609 0 : case InverterModelType::PVWatts: {
2610 0 : break;
2611 : }
2612 0 : default:
2613 0 : assert(false);
2614 : } // end switch modelType
2615 : }
2616 : } else {
2617 0 : ShowSevereError(state, format("{} did not find inverter name = {}", routineName, objectName));
2618 0 : errorsFound = true;
2619 : }
2620 :
2621 12 : if (errorsFound) {
2622 0 : ShowFatalError(state, format("{}Preceding errors terminate program.", routineName));
2623 : }
2624 12 : }
2625 :
2626 104 : void DCtoACInverter::reinitAtBeginEnvironment()
2627 : {
2628 104 : ancillACuseRate_ = 0.0;
2629 104 : ancillACuseEnergy_ = 0.0;
2630 104 : qdotConvZone_ = 0.0;
2631 104 : qdotRadZone_ = 0.0;
2632 104 : }
2633 :
2634 92 : void DCtoACInverter::reinitZoneGainsAtBeginEnvironment()
2635 : {
2636 92 : qdotConvZone_ = 0.0;
2637 92 : qdotRadZone_ = 0.0;
2638 92 : }
2639 :
2640 1 : void DCtoACInverter::setPVWattsDCCapacity(EnergyPlusData &state, const Real64 dcCapacity)
2641 : {
2642 1 : if (modelType_ != InverterModelType::PVWatts) {
2643 0 : ShowFatalError(state, "Setting the DC Capacity for the inverter only works with PVWatts Inverters.");
2644 : }
2645 1 : ratedPower_ = dcCapacity / pvWattsDCtoACSizeRatio_;
2646 1 : }
2647 :
2648 0 : Real64 DCtoACInverter::pvWattsDCCapacity()
2649 : {
2650 0 : return ratedPower_ * pvWattsDCtoACSizeRatio_;
2651 : }
2652 :
2653 3 : Real64 DCtoACInverter::pvWattsInverterEfficiency()
2654 : {
2655 3 : return pvWattsInverterEfficiency_;
2656 : }
2657 :
2658 3 : Real64 DCtoACInverter::pvWattsDCtoACSizeRatio()
2659 : {
2660 3 : return pvWattsDCtoACSizeRatio_;
2661 : }
2662 :
2663 292414 : Real64 DCtoACInverter::aCPowerOut() const
2664 : {
2665 292414 : return aCPowerOut_;
2666 : }
2667 :
2668 12 : DCtoACInverter::InverterModelType DCtoACInverter::modelType() const
2669 : {
2670 12 : return modelType_;
2671 : }
2672 :
2673 12 : std::string const &DCtoACInverter::name() const
2674 : {
2675 12 : return name_;
2676 : }
2677 :
2678 130975 : Real64 DCtoACInverter::getLossRateForOutputPower(EnergyPlusData &state, Real64 const powerOutOfInverter)
2679 : {
2680 :
2681 : // need to invert, find a dCPowerIn that produces the desired AC power out
2682 : // use last efficiency for initial guess
2683 130975 : if (efficiency_ > 0.0) {
2684 130966 : dCPowerIn_ = powerOutOfInverter / efficiency_;
2685 : } else {
2686 9 : dCPowerIn_ = powerOutOfInverter;
2687 9 : calcEfficiency(state);
2688 9 : dCPowerIn_ = powerOutOfInverter / efficiency_;
2689 : }
2690 :
2691 130975 : calcEfficiency(state);
2692 : // one more update is close enough.
2693 130975 : if (efficiency_ > 0.0) {
2694 130975 : dCPowerIn_ = powerOutOfInverter / efficiency_;
2695 : }
2696 130975 : calcEfficiency(state);
2697 130975 : return (1.0 - efficiency_) * dCPowerIn_;
2698 : }
2699 :
2700 408166 : void DCtoACInverter::calcEfficiency(EnergyPlusData &state)
2701 : {
2702 408166 : switch (modelType_) {
2703 392934 : case InverterModelType::CECLookUpTableModel: {
2704 : // we don't model voltage, so use nominal voltage
2705 392934 : Real64 normalizedPower = dCPowerIn_ / ratedPower_;
2706 :
2707 : // get efficiency
2708 392934 : if (normalizedPower <= 0.1) {
2709 : // extrapolate or fix at 10% value? fix it for now
2710 228299 : efficiency_ = nomVoltEfficiencyARR_[0];
2711 164635 : } else if ((normalizedPower > 0.1) && (normalizedPower < 0.20)) {
2712 6088 : efficiency_ = nomVoltEfficiencyARR_[0] + ((normalizedPower - 0.1) / (0.2 - 0.1)) * (nomVoltEfficiencyARR_[1] - nomVoltEfficiencyARR_[0]);
2713 158547 : } else if (normalizedPower == 0.2) {
2714 0 : efficiency_ = nomVoltEfficiencyARR_[1];
2715 158547 : } else if ((normalizedPower > 0.2) && (normalizedPower < 0.30)) {
2716 5175 : efficiency_ = nomVoltEfficiencyARR_[1] + ((normalizedPower - 0.2) / (0.3 - 0.2)) * (nomVoltEfficiencyARR_[2] - nomVoltEfficiencyARR_[1]);
2717 153372 : } else if (normalizedPower == 0.3) {
2718 0 : efficiency_ = nomVoltEfficiencyARR_[2];
2719 153372 : } else if ((normalizedPower > 0.3) && (normalizedPower < 0.50)) {
2720 7156 : efficiency_ = nomVoltEfficiencyARR_[2] + ((normalizedPower - 0.3) / (0.5 - 0.3)) * (nomVoltEfficiencyARR_[3] - nomVoltEfficiencyARR_[2]);
2721 146216 : } else if (normalizedPower == 0.5) {
2722 0 : efficiency_ = nomVoltEfficiencyARR_[3];
2723 146216 : } else if ((normalizedPower > 0.5) && (normalizedPower < 0.75)) {
2724 7536 : efficiency_ = nomVoltEfficiencyARR_[3] + ((normalizedPower - 0.5) / (0.75 - 0.5)) * (nomVoltEfficiencyARR_[4] - nomVoltEfficiencyARR_[3]);
2725 138680 : } else if (normalizedPower == 0.75) {
2726 0 : efficiency_ = nomVoltEfficiencyARR_[4];
2727 138680 : } else if ((normalizedPower > 0.75) && (normalizedPower < 1.0)) {
2728 4483 : efficiency_ =
2729 4483 : nomVoltEfficiencyARR_[4] + ((normalizedPower - 0.75) / (1.0 - 0.75)) * (nomVoltEfficiencyARR_[5] - nomVoltEfficiencyARR_[4]);
2730 134197 : } else if (normalizedPower >= 1.0) {
2731 134197 : efficiency_ = nomVoltEfficiencyARR_[5];
2732 : } else {
2733 0 : assert(false);
2734 : }
2735 :
2736 392934 : efficiency_ = max(efficiency_, 0.0);
2737 392934 : efficiency_ = min(efficiency_, 1.0);
2738 392934 : } break;
2739 :
2740 8293 : case InverterModelType::CurveFuncOfPower: {
2741 8293 : Real64 normalizedPower = dCPowerIn_ / ratedPower_;
2742 8293 : efficiency_ = effCurve_->value(state, normalizedPower);
2743 8293 : efficiency_ = max(efficiency_, minEfficiency_);
2744 8293 : efficiency_ = min(efficiency_, maxEfficiency_);
2745 8293 : } break;
2746 :
2747 2196 : case InverterModelType::PVWatts: {
2748 : // This code is lifted from ssc cmod_pvwatts5.cpp:powerout() method.
2749 : // It was easier to do this calculation here because we have a many to one relationship between inverter
2750 : // and generator whereas theirs is one to one.
2751 2196 : Real64 constexpr etaref = 0.9637;
2752 2196 : Real64 constexpr A = -0.0162;
2753 2196 : Real64 constexpr B = -0.0059;
2754 2196 : Real64 constexpr C = 0.9858;
2755 2196 : Real64 const pdc0 = ratedPower_ / pvWattsInverterEfficiency_;
2756 2196 : Real64 const plr = dCPowerIn_ / pdc0;
2757 2196 : Real64 ac = 0;
2758 :
2759 2196 : if (plr > 0) {
2760 : // normal operation
2761 990 : Real64 eta = (A * plr + B / plr + C) * pvWattsInverterEfficiency_ / etaref;
2762 990 : ac = dCPowerIn_ * eta;
2763 990 : if (ac > ratedPower_) {
2764 : // clipping
2765 0 : ac = ratedPower_;
2766 : }
2767 : // make sure no negative AC values (no parasitic nighttime losses calculated)
2768 990 : if (ac < 0) {
2769 90 : ac = 0;
2770 : }
2771 990 : efficiency_ = ac / dCPowerIn_;
2772 : } else {
2773 1206 : efficiency_ = 1.0; // Set to a non-zero reasonable value (to avoid divide by zero error)
2774 : }
2775 2196 : break;
2776 : }
2777 4743 : case InverterModelType::SimpleConstantEff:
2778 : case InverterModelType::Invalid: {
2779 : // do nothing
2780 4743 : break;
2781 : }
2782 0 : default:
2783 0 : assert(false);
2784 : } // end switch
2785 408166 : }
2786 :
2787 146207 : void DCtoACInverter::simulate(EnergyPlusData &state, Real64 const powerIntoInverter)
2788 : {
2789 146207 : dCPowerIn_ = powerIntoInverter;
2790 146207 : dCEnergyIn_ = dCPowerIn_ * (state.dataHVACGlobal->TimeStepSysSec);
2791 : // check availability schedule
2792 146207 : if (availSched_->getCurrentVal() > 0.0) {
2793 :
2794 : // now calculate Inverter based on model type
2795 146207 : calcEfficiency(state);
2796 146207 : aCPowerOut_ = efficiency_ * dCPowerIn_;
2797 146207 : aCEnergyOut_ = aCPowerOut_ * (state.dataHVACGlobal->TimeStepSysSec);
2798 :
2799 146207 : if (aCPowerOut_ == 0.0) {
2800 64540 : ancillACuseEnergy_ = standbyPower_ * (state.dataHVACGlobal->TimeStepSysSec);
2801 64540 : ancillACuseRate_ = standbyPower_;
2802 : } else {
2803 81667 : ancillACuseRate_ = 0.0;
2804 81667 : ancillACuseEnergy_ = 0.0;
2805 : }
2806 : } else { // not available per schedule, inverter is dead.
2807 : // assume thermal shunt for DC in, but no standby electricity
2808 0 : aCPowerOut_ = 0.0;
2809 0 : aCEnergyOut_ = 0.0;
2810 0 : ancillACuseRate_ = 0.0;
2811 0 : ancillACuseEnergy_ = 0.0;
2812 : }
2813 : // update report variables
2814 146207 : conversionLossPower_ = dCPowerIn_ - aCPowerOut_;
2815 146207 : conversionLossEnergy_ = conversionLossPower_ * (state.dataHVACGlobal->TimeStepSysSec);
2816 146207 : conversionLossEnergyDecrement_ = -1.0 * conversionLossEnergy_;
2817 146207 : thermLossRate_ = dCPowerIn_ - aCPowerOut_ + ancillACuseRate_;
2818 146207 : thermLossEnergy_ = thermLossRate_ * (state.dataHVACGlobal->TimeStepSysSec);
2819 146207 : qdotConvZone_ = thermLossRate_ * (1.0 - zoneRadFract_);
2820 146207 : qdotRadZone_ = thermLossRate_ * zoneRadFract_;
2821 146207 : }
2822 :
2823 4 : ACtoDCConverter::ACtoDCConverter(EnergyPlusData &state, std::string const &objectName)
2824 4 : : efficiency_(0.0), aCPowerIn_(0.0), aCEnergyIn_(0.0), dCPowerOut_(0.0), dCEnergyOut_(0.0), conversionLossPower_(0.0), conversionLossEnergy_(0.0),
2825 4 : conversionLossEnergyDecrement_(0.0), thermLossRate_(0.0), thermLossEnergy_(0.0), qdotConvZone_(0.0), qdotRadZone_(0.0), ancillACuseRate_(0.0),
2826 4 : ancillACuseEnergy_(0.0), modelType_(ConverterModelType::Invalid), heatLossesDestination_(ThermalLossDestination::Invalid), zoneNum_(0),
2827 4 : zoneRadFract_(0.0), // radiative fraction for thermal losses to zone
2828 4 : standbyPower_(0.0), maxPower_(0.0)
2829 : {
2830 :
2831 : static constexpr std::string_view routineName = "ACtoDCConverter constructor ";
2832 :
2833 4 : auto &s_ipsc = state.dataIPShortCut;
2834 :
2835 4 : bool errorsFound = false;
2836 : // if/when add object class name to input object this can be simplified. for now search all possible types
2837 :
2838 4 : int testConvertIndex = state.dataInputProcessing->inputProcessor->getObjectItemNum(state, "ElectricLoadCenter:Storage:Converter", objectName);
2839 :
2840 4 : if (testConvertIndex > 0) {
2841 : int NumAlphas; // Number of elements in the alpha array
2842 : int NumNums; // Number of elements in the numeric array
2843 : int IOStat; // IO Status when calling get input subroutine
2844 4 : s_ipsc->cCurrentModuleObject = "ElectricLoadCenter:Storage:Converter";
2845 :
2846 8 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
2847 4 : s_ipsc->cCurrentModuleObject,
2848 : testConvertIndex,
2849 4 : s_ipsc->cAlphaArgs,
2850 : NumAlphas,
2851 4 : s_ipsc->rNumericArgs,
2852 : NumNums,
2853 : IOStat,
2854 4 : s_ipsc->lNumericFieldBlanks,
2855 4 : s_ipsc->lAlphaFieldBlanks,
2856 4 : s_ipsc->cAlphaFieldNames,
2857 4 : s_ipsc->cNumericFieldNames);
2858 :
2859 4 : ErrorObjectHeader eoh{routineName, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)};
2860 :
2861 4 : name_ = s_ipsc->cAlphaArgs(1);
2862 : // need a new general approach for verify names are unique across objects, next gen GlobalNames
2863 :
2864 4 : if (s_ipsc->lAlphaFieldBlanks(2)) {
2865 0 : availSched_ = Sched::GetScheduleAlwaysOn(state);
2866 4 : } else if ((availSched_ = Sched::GetSchedule(state, s_ipsc->cAlphaArgs(2))) == nullptr) {
2867 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(2), s_ipsc->cAlphaArgs(2));
2868 0 : errorsFound = true;
2869 : }
2870 :
2871 4 : modelType_ = static_cast<ConverterModelType>(getEnumValue(converterModelTypeNamesUC, s_ipsc->cAlphaArgs(3)));
2872 4 : if (modelType_ == ConverterModelType::Invalid) {
2873 0 : ShowSevereInvalidKey(state, eoh, s_ipsc->cAlphaFieldNames(3), s_ipsc->cAlphaArgs(3));
2874 0 : errorsFound = true;
2875 : }
2876 :
2877 4 : switch (modelType_) {
2878 4 : case ConverterModelType::SimpleConstantEff: {
2879 4 : efficiency_ = s_ipsc->rNumericArgs(1);
2880 4 : } break;
2881 :
2882 0 : case ConverterModelType::CurveFuncOfPower: {
2883 0 : maxPower_ = s_ipsc->rNumericArgs(2);
2884 :
2885 0 : if (s_ipsc->lAlphaFieldBlanks(4)) {
2886 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(4));
2887 0 : errorsFound = true;
2888 0 : } else if ((effCurve_ = Curve::GetCurve(state, s_ipsc->cAlphaArgs(4))) == nullptr) {
2889 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(4), s_ipsc->cAlphaArgs(4));
2890 0 : errorsFound = true;
2891 : }
2892 0 : } break;
2893 :
2894 0 : case ConverterModelType::Invalid: {
2895 : // do nothing
2896 0 : } break;
2897 :
2898 0 : default:
2899 0 : assert(false);
2900 : } // end switch
2901 :
2902 4 : standbyPower_ = s_ipsc->rNumericArgs(3);
2903 :
2904 4 : if (s_ipsc->lAlphaFieldBlanks(5)) {
2905 4 : heatLossesDestination_ = ThermalLossDestination::LostToOutside;
2906 0 : } else if ((zoneNum_ = Util::FindItemInList(s_ipsc->cAlphaArgs(5), state.dataHeatBal->Zone)) == 0) {
2907 0 : heatLossesDestination_ = ThermalLossDestination::LostToOutside;
2908 0 : ShowWarningItemNotFound(
2909 0 : state, eoh, s_ipsc->cAlphaFieldNames(5), s_ipsc->cAlphaArgs(5), "Inverter heat losses will not be added to a zone");
2910 : // continue with simulation but inverter losses not sent to a zone.
2911 : } else {
2912 0 : heatLossesDestination_ = ThermalLossDestination::ZoneGains;
2913 : }
2914 :
2915 4 : zoneRadFract_ = s_ipsc->rNumericArgs(4);
2916 :
2917 8 : SetupOutputVariable(state,
2918 : "Converter AC to DC Efficiency",
2919 : Constant::Units::None,
2920 4 : efficiency_,
2921 : OutputProcessor::TimeStepType::System,
2922 : OutputProcessor::StoreType::Average,
2923 4 : name_);
2924 8 : SetupOutputVariable(state,
2925 : "Converter AC Input Electricity Rate",
2926 : Constant::Units::W,
2927 4 : aCPowerIn_,
2928 : OutputProcessor::TimeStepType::System,
2929 : OutputProcessor::StoreType::Average,
2930 4 : name_);
2931 8 : SetupOutputVariable(state,
2932 : "Converter AC Input Electricity Energy",
2933 : Constant::Units::J,
2934 4 : aCEnergyIn_,
2935 : OutputProcessor::TimeStepType::System,
2936 : OutputProcessor::StoreType::Sum,
2937 4 : name_);
2938 8 : SetupOutputVariable(state,
2939 : "Converter DC Output Electricity Rate",
2940 : Constant::Units::W,
2941 4 : dCPowerOut_,
2942 : OutputProcessor::TimeStepType::System,
2943 : OutputProcessor::StoreType::Average,
2944 4 : name_);
2945 8 : SetupOutputVariable(state,
2946 : "Converter DC Output Electricity Energy",
2947 : Constant::Units::J,
2948 4 : dCEnergyOut_,
2949 : OutputProcessor::TimeStepType::System,
2950 : OutputProcessor::StoreType::Sum,
2951 4 : name_);
2952 8 : SetupOutputVariable(state,
2953 : "Converter Electricity Loss Rate",
2954 : Constant::Units::W,
2955 4 : conversionLossPower_,
2956 : OutputProcessor::TimeStepType::System,
2957 : OutputProcessor::StoreType::Average,
2958 4 : name_);
2959 8 : SetupOutputVariable(state,
2960 : "Converter Electricity Loss Energy",
2961 : Constant::Units::J,
2962 4 : conversionLossEnergy_,
2963 : OutputProcessor::TimeStepType::System,
2964 : OutputProcessor::StoreType::Sum,
2965 4 : name_);
2966 8 : SetupOutputVariable(state,
2967 : "Converter Electricity Loss Decrement Energy",
2968 : Constant::Units::J,
2969 4 : conversionLossEnergyDecrement_,
2970 : OutputProcessor::TimeStepType::System,
2971 : OutputProcessor::StoreType::Sum,
2972 4 : name_,
2973 : Constant::eResource::ElectricityProduced,
2974 : OutputProcessor::Group::Plant,
2975 : OutputProcessor::EndUseCat::PowerConversion);
2976 8 : SetupOutputVariable(state,
2977 : "Converter Thermal Loss Rate",
2978 : Constant::Units::W,
2979 4 : thermLossRate_,
2980 : OutputProcessor::TimeStepType::System,
2981 : OutputProcessor::StoreType::Average,
2982 4 : name_);
2983 8 : SetupOutputVariable(state,
2984 : "Converter Thermal Loss Energy",
2985 : Constant::Units::J,
2986 4 : thermLossEnergy_,
2987 : OutputProcessor::TimeStepType::System,
2988 : OutputProcessor::StoreType::Sum,
2989 4 : name_);
2990 8 : SetupOutputVariable(state,
2991 : "Converter Ancillary AC Electricity Rate",
2992 : Constant::Units::W,
2993 4 : ancillACuseRate_,
2994 : OutputProcessor::TimeStepType::System,
2995 : OutputProcessor::StoreType::Average,
2996 4 : name_);
2997 8 : SetupOutputVariable(state,
2998 : "Converter Ancillary AC Electricity Energy",
2999 : Constant::Units::J,
3000 4 : ancillACuseEnergy_,
3001 : OutputProcessor::TimeStepType::System,
3002 : OutputProcessor::StoreType::Sum,
3003 4 : name_,
3004 : Constant::eResource::Electricity,
3005 : OutputProcessor::Group::Plant, // called cogeneration for end use table
3006 : OutputProcessor::EndUseCat::Cogeneration,
3007 : "ACtoDCConverter Ancillary");
3008 4 : if (zoneNum_ > 0) {
3009 0 : SetupZoneInternalGain(
3010 : state, zoneNum_, name_, DataHeatBalance::IntGainType::ElectricLoadCenterConverter, &qdotConvZone_, nullptr, &qdotRadZone_);
3011 : }
3012 : } else {
3013 0 : ShowSevereError(state, format("{} did not find power converter name = {}", routineName, objectName));
3014 0 : errorsFound = true;
3015 : }
3016 :
3017 4 : if (errorsFound) {
3018 0 : ShowFatalError(state, format("{}Preceding errors terminate program.", routineName));
3019 : }
3020 4 : }
3021 :
3022 36 : void ACtoDCConverter::reinitAtBeginEnvironment()
3023 : {
3024 36 : ancillACuseRate_ = 0.0;
3025 36 : ancillACuseEnergy_ = 0.0;
3026 36 : qdotConvZone_ = 0.0;
3027 36 : qdotRadZone_ = 0.0;
3028 36 : }
3029 :
3030 32 : void ACtoDCConverter::reinitZoneGainsAtBeginEnvironment()
3031 : {
3032 32 : qdotConvZone_ = 0.0;
3033 32 : qdotRadZone_ = 0.0;
3034 32 : }
3035 :
3036 138744 : Real64 ACtoDCConverter::aCPowerIn() const
3037 : {
3038 138744 : return aCPowerIn_;
3039 : }
3040 :
3041 69372 : Real64 ACtoDCConverter::getLossRateForInputPower(EnergyPlusData &state, Real64 const powerIntoConverter)
3042 : {
3043 69372 : aCPowerIn_ = powerIntoConverter;
3044 69372 : calcEfficiency(state);
3045 69372 : return (1.0 - efficiency_) * aCPowerIn_;
3046 : }
3047 :
3048 4 : std::string const &ACtoDCConverter::name() const
3049 : {
3050 4 : return name_;
3051 : }
3052 :
3053 208116 : void ACtoDCConverter::calcEfficiency(EnergyPlusData &state)
3054 : {
3055 208116 : switch (modelType_) {
3056 208116 : case ConverterModelType::Invalid:
3057 : case ConverterModelType::SimpleConstantEff: {
3058 208116 : break;
3059 : }
3060 0 : case ConverterModelType::CurveFuncOfPower: {
3061 0 : Real64 normalizedPower = aCPowerIn_ / maxPower_;
3062 0 : efficiency_ = effCurve_->value(state, normalizedPower);
3063 0 : break;
3064 : }
3065 0 : default:
3066 0 : assert(false);
3067 : } // end switch
3068 208116 : }
3069 :
3070 69372 : void ACtoDCConverter::simulate(EnergyPlusData &state, Real64 const powerOutFromConverter)
3071 : {
3072 : // need to invert, find an aCPowerIn that produces the desired DC power out
3073 :
3074 : // use last efficiency for initial guess
3075 69372 : if (availSched_->getCurrentVal() > 0.0) {
3076 :
3077 69372 : aCPowerIn_ = powerOutFromConverter / efficiency_;
3078 69372 : calcEfficiency(state), aCPowerIn_ = powerOutFromConverter / efficiency_;
3079 69372 : calcEfficiency(state),
3080 :
3081 69372 : dCPowerOut_ = aCPowerIn_ * efficiency_;
3082 :
3083 69372 : if (dCPowerOut_ == 0.0) {
3084 42135 : ancillACuseEnergy_ = standbyPower_ * (state.dataHVACGlobal->TimeStepSysSec);
3085 42135 : ancillACuseRate_ = standbyPower_;
3086 : } else {
3087 27237 : ancillACuseRate_ = 0.0;
3088 27237 : ancillACuseEnergy_ = 0.0;
3089 : }
3090 :
3091 : } else { // not available
3092 0 : aCPowerIn_ = 0.0;
3093 0 : dCPowerOut_ = 0.0;
3094 0 : ancillACuseRate_ = 0.0;
3095 0 : ancillACuseEnergy_ = 0.0;
3096 : }
3097 :
3098 : // update and report
3099 69372 : aCEnergyIn_ = aCPowerIn_ * (state.dataHVACGlobal->TimeStepSysSec);
3100 69372 : dCEnergyOut_ = dCPowerOut_ * (state.dataHVACGlobal->TimeStepSysSec);
3101 69372 : conversionLossPower_ = aCPowerIn_ - dCPowerOut_;
3102 69372 : conversionLossEnergy_ = conversionLossPower_ * (state.dataHVACGlobal->TimeStepSysSec);
3103 69372 : conversionLossEnergyDecrement_ = -1.0 * conversionLossEnergy_;
3104 69372 : thermLossRate_ = aCPowerIn_ - dCPowerOut_ + ancillACuseRate_;
3105 69372 : thermLossEnergy_ = thermLossRate_ * (state.dataHVACGlobal->TimeStepSysSec);
3106 69372 : qdotConvZone_ = thermLossRate_ * (1.0 - zoneRadFract_);
3107 69372 : qdotRadZone_ = thermLossRate_ * zoneRadFract_;
3108 69372 : }
3109 :
3110 9 : ElectricStorage::ElectricStorage( // main constructor
3111 : EnergyPlusData &state,
3112 9 : std::string const &objectName)
3113 9 : : storedPower_(0.0), storedEnergy_(0.0), drawnPower_(0.0), drawnEnergy_(0.0), decrementedEnergyStored_(0.0), maxRainflowArrayBounds_(100),
3114 9 : myWarmUpFlag_(false), storageModelMode_(StorageModelType::Invalid), heatLossesDestination_(ThermalLossDestination::Invalid), zoneNum_(0),
3115 9 : zoneRadFract_(0.0), startingEnergyStored_(0.0), energeticEfficCharge_(0.0), energeticEfficDischarge_(0.0), maxPowerDraw_(0.0),
3116 9 : maxPowerStore_(0.0), maxEnergyCapacity_(0.0), parallelNum_(0), seriesNum_(0), numBattery_(0), cycleBinNum_(0), startingSOC_(0.0),
3117 9 : maxAhCapacity_(0.0), availableFrac_(0.0), chargeConversionRate_(0.0), chargedOCV_(0.0), dischargedOCV_(0.0), internalR_(0.0),
3118 9 : maxDischargeI_(0.0), cutoffV_(0.0), maxChargeRate_(0.0), lifeCalculation_(false), liIon_dcToDcChargingEff_(0.0), liIon_mass_(0.0),
3119 9 : liIon_surfaceArea_(0.0), liIon_Cp_(0.0), liIon_heatTransferCoef_(0.0), liIon_Vfull_(0.0), liIon_Vexp_(0.0), liIon_Vnom_(0.0),
3120 9 : liIon_Vnom_default_(0.0), liIon_Qfull_(0.0), liIon_Qexp_(0.0), liIon_Qnom_(0.0), liIon_C_rate_(0.0), thisTimeStepStateOfCharge_(0.0),
3121 9 : lastTimeStepStateOfCharge_(0.0), pelNeedFromStorage_(0.0), pelFromStorage_(0.0), pelIntoStorage_(0.0), qdotConvZone_(0.0), qdotRadZone_(0.0),
3122 9 : timeElapsed_(0.0), thisTimeStepAvailable_(0.0), thisTimeStepBound_(0.0), lastTimeStepAvailable_(0.0), lastTimeStepBound_(0.0),
3123 27 : lastTwoTimeStepAvailable_(0.0), lastTwoTimeStepBound_(0.0), count0_(0), electEnergyinStorage_(0.0), thermLossRate_(0.0), thermLossEnergy_(0.0),
3124 9 : storageMode_(0), absoluteSOC_(0.0), fractionSOC_(0.0), batteryCurrent_(0.0), batteryVoltage_(0.0), batteryDamage_(0.0), batteryTemperature_(0.0)
3125 : {
3126 :
3127 : static constexpr std::string_view routineName = "ElectricStorage constructor ";
3128 :
3129 9 : auto &s_ipsc = state.dataIPShortCut;
3130 :
3131 9 : bool errorsFound = false;
3132 : // if/when add object class name to input object this can be simplified. for now search all possible types
3133 9 : bool foundStorage = false;
3134 9 : int storageIDFObjectNum = 0;
3135 :
3136 : const std::array<std::pair<std::string, StorageModelType>, 3> storageTypes{
3137 0 : {{"ElectricLoadCenter:Storage:Simple", StorageModelType::SimpleBucketStorage},
3138 0 : {"ElectricLoadCenter:Storage:Battery", StorageModelType::KIBaMBattery},
3139 9 : {"ElectricLoadCenter:Storage:LiIonNMCBattery", StorageModelType::LiIonNmcBattery}}};
3140 :
3141 12 : for (auto &item : storageTypes) {
3142 12 : int testStorageIndex = state.dataInputProcessing->inputProcessor->getObjectItemNum(state, item.first, objectName);
3143 12 : if (testStorageIndex > 0) {
3144 9 : foundStorage = true;
3145 9 : storageIDFObjectNum = testStorageIndex;
3146 9 : s_ipsc->cCurrentModuleObject = item.first;
3147 9 : storageModelMode_ = item.second;
3148 9 : break;
3149 : }
3150 : }
3151 :
3152 9 : if (foundStorage) {
3153 : int numAlphas; // Number of elements in the alpha array
3154 : int numNums; // Number of elements in the numeric array
3155 : int iOStat; // IO Status when calling get input subroutine
3156 18 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
3157 9 : s_ipsc->cCurrentModuleObject,
3158 : storageIDFObjectNum,
3159 9 : s_ipsc->cAlphaArgs,
3160 : numAlphas,
3161 9 : s_ipsc->rNumericArgs,
3162 : numNums,
3163 : iOStat,
3164 9 : s_ipsc->lNumericFieldBlanks,
3165 9 : s_ipsc->lAlphaFieldBlanks,
3166 9 : s_ipsc->cAlphaFieldNames,
3167 9 : s_ipsc->cNumericFieldNames);
3168 :
3169 9 : ErrorObjectHeader eoh{routineName, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)};
3170 :
3171 9 : name_ = s_ipsc->cAlphaArgs(1);
3172 : // how to verify names are unique across objects? add to GlobalNames?
3173 :
3174 9 : if (s_ipsc->lAlphaFieldBlanks(2)) {
3175 0 : availSched_ = Sched::GetScheduleAlwaysOn(state);
3176 9 : } else if ((availSched_ = Sched::GetSchedule(state, s_ipsc->cAlphaArgs(2))) == nullptr) {
3177 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(2), s_ipsc->cAlphaArgs(2));
3178 0 : errorsFound = true;
3179 : }
3180 :
3181 9 : if (s_ipsc->lAlphaFieldBlanks(3)) {
3182 7 : heatLossesDestination_ = ThermalLossDestination::LostToOutside;
3183 2 : } else if ((zoneNum_ = Util::FindItemInList(s_ipsc->cAlphaArgs(3), state.dataHeatBal->Zone)) == 0) {
3184 0 : heatLossesDestination_ = ThermalLossDestination::LostToOutside;
3185 0 : ShowWarningItemNotFound(
3186 0 : state, eoh, s_ipsc->cAlphaFieldNames(3), s_ipsc->cAlphaArgs(3), "Storage heat losses will not be added to a zone");
3187 : // continue with simulation but storage losses not sent to a zone.
3188 : } else {
3189 2 : heatLossesDestination_ = ThermalLossDestination::ZoneGains;
3190 : }
3191 :
3192 9 : zoneRadFract_ = s_ipsc->rNumericArgs(1);
3193 :
3194 9 : switch (storageModelMode_) {
3195 :
3196 7 : case StorageModelType::SimpleBucketStorage: {
3197 7 : energeticEfficCharge_ = checkUserEfficiencyInput(state, s_ipsc->rNumericArgs(2), true, name_, errorsFound);
3198 7 : energeticEfficDischarge_ = checkUserEfficiencyInput(state, s_ipsc->rNumericArgs(3), false, name_, errorsFound);
3199 7 : maxEnergyCapacity_ = s_ipsc->rNumericArgs(4);
3200 7 : maxPowerDraw_ = s_ipsc->rNumericArgs(5);
3201 7 : maxPowerStore_ = s_ipsc->rNumericArgs(6);
3202 7 : startingEnergyStored_ = s_ipsc->rNumericArgs(7);
3203 14 : SetupOutputVariable(state,
3204 : "Electric Storage Simple Charge State",
3205 : Constant::Units::J,
3206 7 : electEnergyinStorage_,
3207 : OutputProcessor::TimeStepType::System,
3208 : OutputProcessor::StoreType::Average,
3209 7 : name_); // issue #4921
3210 7 : break;
3211 : }
3212 :
3213 1 : case StorageModelType::KIBaMBattery: {
3214 1 : if (s_ipsc->lAlphaFieldBlanks(4)) {
3215 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(4));
3216 0 : errorsFound = true;
3217 1 : } else if ((chargeCurve_ = Curve::GetCurve(state, s_ipsc->cAlphaArgs(4))) == nullptr) { // voltage calculation for charging
3218 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(4), s_ipsc->cAlphaArgs(4));
3219 0 : errorsFound = true;
3220 1 : } else if (chargeCurve_->numDims != 1) {
3221 0 : Curve::ShowSevereCurveDims(state, eoh, s_ipsc->cAlphaFieldNames(4), s_ipsc->cAlphaArgs(4), "1", chargeCurve_->numDims);
3222 0 : errorsFound = true;
3223 : }
3224 :
3225 1 : if (s_ipsc->lAlphaFieldBlanks(5)) {
3226 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(5));
3227 0 : errorsFound = true;
3228 1 : } else if ((dischargeCurve_ = Curve::GetCurve(state, s_ipsc->cAlphaArgs(5))) == nullptr) { // voltage calculation for discharging
3229 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(5), s_ipsc->cAlphaArgs(5));
3230 0 : errorsFound = true;
3231 1 : } else if (dischargeCurve_->numDims != 1) {
3232 0 : Curve::ShowSevereCurveDims(state, eoh, s_ipsc->cAlphaFieldNames(5), s_ipsc->cAlphaArgs(5), "1", dischargeCurve_->numDims);
3233 0 : errorsFound = true;
3234 : }
3235 :
3236 1 : if (s_ipsc->lAlphaFieldBlanks(6)) {
3237 0 : lifeCalculation_ = false;
3238 1 : } else if (BooleanSwitch bs = getYesNoValue(s_ipsc->cAlphaArgs(6)); bs != BooleanSwitch::Invalid) {
3239 1 : lifeCalculation_ = static_cast<bool>(bs);
3240 : } else {
3241 0 : ShowWarningInvalidBool(state, eoh, s_ipsc->cAlphaFieldNames(6), s_ipsc->cAlphaArgs(6), "No");
3242 0 : lifeCalculation_ = false;
3243 : }
3244 :
3245 1 : if (lifeCalculation_) {
3246 1 : if (s_ipsc->lAlphaFieldBlanks(7)) {
3247 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(7), s_ipsc->cAlphaFieldNames(6), s_ipsc->cAlphaArgs(6));
3248 0 : errorsFound = true;
3249 1 : } else if ((lifeCurve_ = Curve::GetCurve(state, s_ipsc->cAlphaArgs(7))) == nullptr) { // Battery life calculation
3250 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(7), s_ipsc->cAlphaArgs(7));
3251 0 : errorsFound = true;
3252 1 : } else if (lifeCurve_->numDims != 1) {
3253 0 : Curve::ShowSevereCurveDims(state, eoh, s_ipsc->cAlphaFieldNames(7), s_ipsc->cAlphaArgs(7), "1", lifeCurve_->numDims);
3254 0 : errorsFound = true;
3255 : }
3256 :
3257 1 : cycleBinNum_ = s_ipsc->rNumericArgs(14);
3258 :
3259 1 : if (!errorsFound) { // life cycle calculation for this battery, allocate arrays for degradation calculation
3260 : // std::vector is zero base instead of 1, so first index is now 0.
3261 1 : b10_.resize(maxRainflowArrayBounds_ + 1, 0.0);
3262 1 : x0_.resize(maxRainflowArrayBounds_ + 1, 0);
3263 1 : nmb0_.resize(cycleBinNum_, 0.0);
3264 1 : oneNmb0_.resize(cycleBinNum_, 0.0);
3265 : }
3266 : }
3267 :
3268 1 : parallelNum_ = s_ipsc->rNumericArgs(2);
3269 1 : seriesNum_ = state.dataIPShortCut->rNumericArgs(3);
3270 1 : numBattery_ = parallelNum_ * seriesNum_;
3271 1 : maxAhCapacity_ = state.dataIPShortCut->rNumericArgs(4);
3272 1 : startingSOC_ = state.dataIPShortCut->rNumericArgs(5);
3273 1 : availableFrac_ = state.dataIPShortCut->rNumericArgs(6);
3274 1 : chargeConversionRate_ = state.dataIPShortCut->rNumericArgs(7);
3275 1 : chargedOCV_ = state.dataIPShortCut->rNumericArgs(8);
3276 1 : dischargedOCV_ = state.dataIPShortCut->rNumericArgs(9);
3277 1 : internalR_ = state.dataIPShortCut->rNumericArgs(10);
3278 1 : maxDischargeI_ = state.dataIPShortCut->rNumericArgs(11);
3279 1 : cutoffV_ = state.dataIPShortCut->rNumericArgs(12);
3280 1 : maxChargeRate_ = state.dataIPShortCut->rNumericArgs(13);
3281 :
3282 : // Check charging and discharging curves to make sure charging curve always gives a higher voltage (#8817)
3283 1 : if (!errorsFound && chargeCurve_ != nullptr && dischargeCurve_ != nullptr) {
3284 1 : checkChargeDischargeVoltageCurves(state, name_, chargedOCV_, dischargedOCV_, chargeCurve_, dischargeCurve_);
3285 : }
3286 :
3287 1 : break;
3288 : }
3289 1 : case StorageModelType::LiIonNmcBattery: {
3290 1 : lifeCalculation_ = (Util::SameString(state.dataIPShortCut->cAlphaArgs(4), "KandlerSmith") || state.dataIPShortCut->lAlphaFieldBlanks(4));
3291 1 : seriesNum_ = static_cast<int>(state.dataIPShortCut->rNumericArgs(2));
3292 1 : parallelNum_ = static_cast<int>(state.dataIPShortCut->rNumericArgs(3));
3293 1 : startingSOC_ = state.dataIPShortCut->lNumericFieldBlanks(4) ? 0.5 : state.dataIPShortCut->rNumericArgs(4);
3294 1 : liIon_dcToDcChargingEff_ = state.dataIPShortCut->lNumericFieldBlanks(5) ? 0.95 : state.dataIPShortCut->rNumericArgs(5);
3295 1 : liIon_mass_ = state.dataIPShortCut->rNumericArgs(6);
3296 1 : liIon_surfaceArea_ = state.dataIPShortCut->rNumericArgs(7);
3297 1 : liIon_Cp_ = state.dataIPShortCut->lNumericFieldBlanks(8) ? 1500.0 : state.dataIPShortCut->rNumericArgs(8);
3298 1 : liIon_heatTransferCoef_ = state.dataIPShortCut->lNumericFieldBlanks(9) ? 7.5 : state.dataIPShortCut->rNumericArgs(9);
3299 1 : liIon_Vfull_ = state.dataIPShortCut->lNumericFieldBlanks(10) ? 4.2 : state.dataIPShortCut->rNumericArgs(10);
3300 1 : liIon_Vexp_ = state.dataIPShortCut->lNumericFieldBlanks(11) ? 3.53 : state.dataIPShortCut->rNumericArgs(11);
3301 1 : liIon_Vnom_ = state.dataIPShortCut->lNumericFieldBlanks(12) ? 3.342 : state.dataIPShortCut->rNumericArgs(12);
3302 1 : if (liIon_Vfull_ < liIon_Vexp_ || liIon_Vexp_ < liIon_Vnom_) {
3303 0 : ShowSevereError(
3304 : state,
3305 0 : format(
3306 0 : "{}{}=\"{}\", invalid entry.", routineName, state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
3307 0 : ShowContinueError(state,
3308 0 : format("{} must be greater than {},",
3309 0 : state.dataIPShortCut->cNumericFieldNames(10),
3310 0 : state.dataIPShortCut->cNumericFieldNames(11)));
3311 0 : ShowContinueError(state, format("which must be greater than {}.", state.dataIPShortCut->cNumericFieldNames(12)));
3312 0 : for (int i = 10; i <= 12; ++i) {
3313 0 : ShowContinueError(state,
3314 0 : format("{} = {:.3R}", state.dataIPShortCut->cNumericFieldNames(i), state.dataIPShortCut->rNumericArgs(i)));
3315 : }
3316 0 : errorsFound = true;
3317 : }
3318 1 : liIon_Vnom_default_ = state.dataIPShortCut->lNumericFieldBlanks(13) ? 3.342 : state.dataIPShortCut->rNumericArgs(13);
3319 1 : liIon_Qfull_ = state.dataIPShortCut->lNumericFieldBlanks(14) ? 3.2 : state.dataIPShortCut->rNumericArgs(14);
3320 1 : liIon_Qexp_ =
3321 1 : state.dataIPShortCut->lNumericFieldBlanks(15) ? 0.8075 * liIon_Qfull_ : state.dataIPShortCut->rNumericArgs(15) * liIon_Qfull_;
3322 1 : liIon_Qnom_ =
3323 1 : state.dataIPShortCut->lNumericFieldBlanks(16) ? 0.976875 * liIon_Qfull_ : state.dataIPShortCut->rNumericArgs(16) * liIon_Qfull_;
3324 1 : if (liIon_Qexp_ >= liIon_Qnom_) {
3325 0 : ShowSevereError(
3326 : state,
3327 0 : format(
3328 0 : "{}{}=\"{}\", invalid entry.", routineName, state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
3329 0 : ShowContinueError(state,
3330 0 : format("{} must be greater than {}.",
3331 0 : state.dataIPShortCut->cNumericFieldNames(16),
3332 0 : state.dataIPShortCut->cNumericFieldNames(15)));
3333 0 : for (int i = 15; i <= 16; ++i) {
3334 0 : ShowContinueError(state,
3335 0 : format("{} = {:.3R}", state.dataIPShortCut->cNumericFieldNames(i), state.dataIPShortCut->rNumericArgs(i)));
3336 : }
3337 0 : errorsFound = true;
3338 : }
3339 1 : liIon_C_rate_ = state.dataIPShortCut->lNumericFieldBlanks(17) ? 1.0 : state.dataIPShortCut->rNumericArgs(17);
3340 1 : internalR_ = state.dataIPShortCut->lNumericFieldBlanks(18) ? 0.09 : state.dataIPShortCut->rNumericArgs(18);
3341 :
3342 1 : maxAhCapacity_ = liIon_Qfull_ * parallelNum_;
3343 :
3344 1 : if (!errorsFound) {
3345 : // Set the Lifetime model in SSC
3346 : // I'm using a raw pointer here because the the battery_t constructor expects it.
3347 : // The pointer is then passed into the battery_t where it is converted into a unique_ptr and persists along with that object.
3348 : // Therefore I am not deleting this pointer here because that will be handled by the battery_t class.
3349 : lifetime_t *battLifetime;
3350 1 : if (lifeCalculation_) {
3351 1 : battLifetime = new lifetime_nmc_t(state.dataHVACGlobal->TimeStepSys);
3352 : } else {
3353 : // This sets a lifetime model where the capacity is always 100%.
3354 0 : std::vector<double> tblVals{{20, 0, 100, 20, 5000, 100, 20, 10000, 100, 80, 0, 100, 80, 1000, 100, 80, 2000, 100}};
3355 0 : util::matrix_t<double> battLifetimeMatrix(6, 3, &tblVals);
3356 0 : battLifetime = new lifetime_calendar_cycle_t(battLifetimeMatrix, state.dataHVACGlobal->TimeStepSys);
3357 0 : }
3358 :
3359 : // Create the SSC battery object
3360 2 : ssc_battery_ = std::unique_ptr<battery_t>(
3361 1 : new battery_t(state.dataHVACGlobal->TimeStepSys,
3362 : battery_params::CHEM::LITHIUM_ION,
3363 : new capacity_lithium_ion_t(maxAhCapacity_, // Capacity of the whole battery
3364 1 : startingSOC_ * 100.0,
3365 : 100.0, // Reset later
3366 : 0.0, // Reset later
3367 1 : state.dataHVACGlobal->TimeStepSys),
3368 : new voltage_dynamic_t(seriesNum_,
3369 : parallelNum_,
3370 : liIon_Vnom_default_,
3371 : liIon_Vfull_,
3372 : liIon_Vexp_,
3373 : liIon_Vnom_,
3374 : liIon_Qfull_, // Capacity of one cell
3375 : liIon_Qexp_,
3376 : liIon_Qnom_,
3377 : liIon_C_rate_,
3378 : internalR_,
3379 1 : state.dataHVACGlobal->TimeStepSys),
3380 : battLifetime,
3381 1 : new thermal_t(state.dataHVACGlobal->TimeStepSys,
3382 : liIon_mass_,
3383 : liIon_surfaceArea_,
3384 1 : internalR_ * seriesNum_ / parallelNum_, // Electric resistance of the whole battery
3385 : liIon_Cp_,
3386 : liIon_heatTransferCoef_,
3387 : 20.0 // Picking a temperature for now, will reset before each run.
3388 1 : ),
3389 5 : nullptr));
3390 1 : ssc_lastBatteryState_ = std::make_unique<battery_state>(ssc_battery_->get_state());
3391 1 : ssc_lastBatteryTimeStep_ = ssc_battery_->get_params().dt_hr;
3392 1 : ssc_initBatteryState_ = std::make_unique<battery_state>(ssc_battery_->get_state());
3393 : }
3394 :
3395 1 : break;
3396 : }
3397 0 : case StorageModelType::Invalid: {
3398 : // do nothing
3399 0 : break;
3400 : }
3401 0 : default:
3402 0 : assert(false);
3403 : } // switch storage model type
3404 :
3405 9 : if (storageModelMode_ == StorageModelType::KIBaMBattery || storageModelMode_ == StorageModelType::LiIonNmcBattery) {
3406 2 : SetupOutputVariable(state,
3407 : "Electric Storage Operating Mode Index",
3408 : Constant::Units::None,
3409 2 : storageMode_,
3410 : OutputProcessor::TimeStepType::System,
3411 : OutputProcessor::StoreType::Average,
3412 2 : name_);
3413 4 : SetupOutputVariable(state,
3414 : "Electric Storage Battery Charge State",
3415 : Constant::Units::Ah,
3416 2 : absoluteSOC_,
3417 : OutputProcessor::TimeStepType::System,
3418 : OutputProcessor::StoreType::Average,
3419 2 : name_); // issue #4921
3420 4 : SetupOutputVariable(state,
3421 : "Electric Storage Charge Fraction",
3422 : Constant::Units::None,
3423 2 : fractionSOC_,
3424 : OutputProcessor::TimeStepType::System,
3425 : OutputProcessor::StoreType::Average,
3426 2 : name_);
3427 4 : SetupOutputVariable(state,
3428 : "Electric Storage Total Current",
3429 : Constant::Units::A,
3430 2 : batteryCurrent_,
3431 : OutputProcessor::TimeStepType::System,
3432 : OutputProcessor::StoreType::Average,
3433 2 : name_);
3434 4 : SetupOutputVariable(state,
3435 : "Electric Storage Total Voltage",
3436 : Constant::Units::V,
3437 2 : batteryVoltage_,
3438 : OutputProcessor::TimeStepType::System,
3439 : OutputProcessor::StoreType::Average,
3440 2 : name_);
3441 :
3442 2 : if (lifeCalculation_) {
3443 4 : SetupOutputVariable(state,
3444 : "Electric Storage Degradation Fraction",
3445 : Constant::Units::None,
3446 2 : batteryDamage_,
3447 : OutputProcessor::TimeStepType::System,
3448 : OutputProcessor::StoreType::Average,
3449 2 : name_);
3450 : }
3451 : }
3452 :
3453 18 : SetupOutputVariable(state,
3454 : "Electric Storage Charge Power",
3455 : Constant::Units::W,
3456 9 : storedPower_,
3457 : OutputProcessor::TimeStepType::System,
3458 : OutputProcessor::StoreType::Average,
3459 9 : name_);
3460 18 : SetupOutputVariable(state,
3461 : "Electric Storage Charge Energy",
3462 : Constant::Units::J,
3463 9 : storedEnergy_,
3464 : OutputProcessor::TimeStepType::System,
3465 : OutputProcessor::StoreType::Sum,
3466 9 : name_);
3467 18 : SetupOutputVariable(state,
3468 : "Electric Storage Production Decrement Energy",
3469 : Constant::Units::J,
3470 9 : decrementedEnergyStored_,
3471 : OutputProcessor::TimeStepType::System,
3472 : OutputProcessor::StoreType::Sum,
3473 9 : name_,
3474 : Constant::eResource::ElectricityProduced,
3475 : OutputProcessor::Group::Plant,
3476 : OutputProcessor::EndUseCat::ElectricStorage);
3477 18 : SetupOutputVariable(state,
3478 : "Electric Storage Discharge Power",
3479 : Constant::Units::W,
3480 9 : drawnPower_,
3481 : OutputProcessor::TimeStepType::System,
3482 : OutputProcessor::StoreType::Average,
3483 9 : name_);
3484 18 : SetupOutputVariable(state,
3485 : "Electric Storage Discharge Energy",
3486 : Constant::Units::J,
3487 9 : drawnEnergy_,
3488 : OutputProcessor::TimeStepType::System,
3489 : OutputProcessor::StoreType::Sum,
3490 9 : name_,
3491 : Constant::eResource::ElectricityProduced,
3492 : OutputProcessor::Group::Plant,
3493 : OutputProcessor::EndUseCat::ElectricStorage);
3494 18 : SetupOutputVariable(state,
3495 : "Electric Storage Thermal Loss Rate",
3496 : Constant::Units::W,
3497 9 : thermLossRate_,
3498 : OutputProcessor::TimeStepType::System,
3499 : OutputProcessor::StoreType::Average,
3500 9 : name_);
3501 18 : SetupOutputVariable(state,
3502 : "Electric Storage Thermal Loss Energy",
3503 : Constant::Units::J,
3504 9 : thermLossEnergy_,
3505 : OutputProcessor::TimeStepType::System,
3506 : OutputProcessor::StoreType::Sum,
3507 9 : name_);
3508 9 : if (state.dataGlobal->AnyEnergyManagementSystemInModel) {
3509 2 : if (storageModelMode_ == StorageModelType::SimpleBucketStorage) {
3510 2 : SetupEMSInternalVariable(state, "Electrical Storage Simple Maximum Capacity", name_, "[J]", maxEnergyCapacity_);
3511 0 : } else if (storageModelMode_ == StorageModelType::KIBaMBattery) {
3512 0 : SetupEMSInternalVariable(state, "Electrical Storage Battery Maximum Capacity", name_, "[Ah]", maxAhCapacity_);
3513 : }
3514 : }
3515 9 : if (storageModelMode_ == StorageModelType::LiIonNmcBattery) {
3516 2 : SetupOutputVariable(state,
3517 : "Electric Storage Battery Temperature",
3518 : Constant::Units::C,
3519 1 : batteryTemperature_,
3520 : OutputProcessor::TimeStepType::System,
3521 : OutputProcessor::StoreType::Average,
3522 1 : name_);
3523 : }
3524 :
3525 9 : if (zoneNum_ > 0) {
3526 2 : switch (storageModelMode_) {
3527 2 : case StorageModelType::SimpleBucketStorage: {
3528 2 : SetupZoneInternalGain(
3529 : state, zoneNum_, name_, DataHeatBalance::IntGainType::ElectricLoadCenterStorageSimple, &qdotConvZone_, nullptr, &qdotRadZone_);
3530 2 : break;
3531 : }
3532 0 : case StorageModelType::KIBaMBattery: {
3533 0 : SetupZoneInternalGain(
3534 : state, zoneNum_, name_, DataHeatBalance::IntGainType::ElectricLoadCenterStorageBattery, &qdotConvZone_, nullptr, &qdotRadZone_);
3535 0 : break;
3536 : }
3537 0 : case StorageModelType::LiIonNmcBattery: {
3538 0 : SetupZoneInternalGain(state,
3539 : zoneNum_,
3540 : name_,
3541 : DataHeatBalance::IntGainType::ElectricLoadCenterStorageLiIonNmcBattery,
3542 : &qdotConvZone_,
3543 : nullptr,
3544 : &qdotRadZone_);
3545 0 : break;
3546 : }
3547 0 : case StorageModelType::Invalid: {
3548 : // do nothing
3549 0 : break;
3550 : }
3551 0 : default:
3552 0 : assert(false);
3553 : } // switch storage model type
3554 : }
3555 : } else { // storage not found
3556 0 : ShowSevereError(state, format("{} did not find storage name = {}", routineName, objectName));
3557 0 : errorsFound = true;
3558 : }
3559 9 : if (errorsFound) {
3560 0 : ShowFatalError(state, format("{}Preceding errors terminate program.", routineName));
3561 : }
3562 9 : }
3563 :
3564 14 : Real64 checkUserEfficiencyInput(EnergyPlusData &state, Real64 userInputValue, bool isCharging, std::string const &deviceName, bool &errorsFound)
3565 : {
3566 14 : Real64 constexpr minChargeEfficiency = 0.001;
3567 14 : Real64 constexpr minDischargeEfficiency = 0.001;
3568 :
3569 : // Fix for Defect #8867. Do not allow either efficiency to be zero as it will lead to a divide by zero (NaN).
3570 14 : if (isCharging) {
3571 7 : if (userInputValue < minChargeEfficiency) {
3572 0 : ShowSevereError(state,
3573 0 : format("ElectricStorage charge efficiency was too low. This occurred for electric storage unit named {}", deviceName));
3574 0 : ShowContinueError(state, "Please check your input value for this electric storage unit and fix the charge efficiency.");
3575 0 : errorsFound = true;
3576 0 : return minChargeEfficiency;
3577 : } else {
3578 7 : return userInputValue;
3579 : }
3580 : } else { // discharging
3581 7 : if (userInputValue < minDischargeEfficiency) {
3582 0 : ShowSevereError(
3583 0 : state, format("ElectricStorage discharge efficiency was too low. This occurred for electric storage unit named {}", deviceName));
3584 0 : ShowContinueError(state, "Please check your input value for this electric storage unit and fix the discharge efficiency.");
3585 0 : errorsFound = true;
3586 0 : return minDischargeEfficiency;
3587 : } else {
3588 7 : return userInputValue;
3589 : }
3590 : }
3591 : }
3592 :
3593 1 : void checkChargeDischargeVoltageCurves(
3594 : EnergyPlusData &state, std::string_view nameBatt, Real64 const E0c, Real64 const E0d, Curve::Curve *chargeCurve, Curve::Curve *dischargeCurve)
3595 : {
3596 1 : int constexpr numChecks = 50; // number of divisions for checking the functions from 0 to 1 for fraction charged/discharged
3597 1 : int constexpr numReports = 10; // number of divisions for reporting
3598 1 : bool gotErrs = false;
3599 :
3600 : // Fix for Defect #8817. When the charging curve results in a lower voltage than the discharging curve, the battery
3601 : // will give the appearance that the energy in Joules being removed from the battery exceeds what was stored. This
3602 : // checks to make sure that voltage for charging is always higher than discharging at the same fraction charged.
3603 52 : for (int loop = 1; loop <= numChecks + 1; ++loop) {
3604 51 : Real64 xfc = float(loop - 1) / float(numChecks); // = q0/qmax
3605 51 : Real64 xfd = 1.0 - xfc; // = (qmax-q0)/qmax = 1 - xfc
3606 51 : Real64 chargeVoltage = E0d + chargeCurve->value(state, xfc); // E0d+Ac*xfc+Cc*xfc/(Dc-xfc)
3607 51 : Real64 dischargeVoltage = E0c + dischargeCurve->value(state, xfd); // E0c+Ad*xfd+Cd*xfd/(Dd-xfd)
3608 51 : if (dischargeVoltage > chargeVoltage) {
3609 0 : gotErrs = true;
3610 0 : break;
3611 : }
3612 : }
3613 1 : if (gotErrs) {
3614 0 : ShowWarningMessage(state, format("Kinetic Battery Model: {} has a charging/discharging voltage curve conflict.", nameBatt));
3615 0 : ShowContinueError(state,
3616 : "Discharging voltage is higher than charging voltage which may potentially lead to an imbalance in the stored energy.");
3617 0 : ShowContinueError(state, "Check the charging and discharging curves to make sure that the charging voltage is greater than discharging.");
3618 0 : ShowContinueError(state, "Also check the charging and discharging energy outputs to find any discrepancies.");
3619 0 : for (int loop = 1; loop <= numReports + 1; ++loop) {
3620 0 : Real64 xfc = float(loop - 1) / float(numReports); // = q0/qmax
3621 0 : Real64 xfd = 1.0 - xfc; // = (qmax-q0)/qmax = 1 - xfc
3622 0 : Real64 chargeVoltage = E0d + chargeCurve->value(state, xfc); // E0d+Ac*xfc+Cc*xfc/(Dc-xfc)
3623 0 : Real64 dischargeVoltage = E0c + dischargeCurve->value(state, xfd); // E0c+Ad*xfd+Cd*xfd/(Dd-xfd)
3624 0 : ShowContinueError(
3625 : state,
3626 0 : format(
3627 : "Charged fraction = {:.1R}, Charging voltage = {:.3R} V, Discharging voltage = {:.3R} V", xfc, chargeVoltage, dischargeVoltage));
3628 : }
3629 : }
3630 1 : }
3631 :
3632 83 : void ElectricStorage::reinitAtBeginEnvironment()
3633 : {
3634 83 : pelNeedFromStorage_ = 0.0;
3635 83 : pelFromStorage_ = 0.0;
3636 83 : pelIntoStorage_ = 0.0;
3637 83 : qdotConvZone_ = 0.0;
3638 83 : qdotRadZone_ = 0.0;
3639 83 : timeElapsed_ = 0.0;
3640 83 : electEnergyinStorage_ = 0.0;
3641 83 : storedPower_ = 0.0;
3642 83 : storedEnergy_ = 0.0;
3643 83 : decrementedEnergyStored_ = 0.0;
3644 83 : drawnPower_ = 0.0;
3645 83 : drawnEnergy_ = 0.0;
3646 83 : thermLossRate_ = 0.0;
3647 83 : thermLossEnergy_ = 0.0;
3648 83 : lastTimeStepStateOfCharge_ = startingEnergyStored_;
3649 83 : thisTimeStepStateOfCharge_ = startingEnergyStored_;
3650 :
3651 83 : if (storageModelMode_ == StorageModelType::KIBaMBattery) {
3652 9 : Real64 initialCharge = maxAhCapacity_ * startingSOC_;
3653 9 : lastTwoTimeStepAvailable_ = initialCharge * availableFrac_;
3654 9 : lastTwoTimeStepBound_ = initialCharge * (1.0 - availableFrac_);
3655 9 : lastTimeStepAvailable_ = initialCharge * availableFrac_;
3656 9 : lastTimeStepBound_ = initialCharge * (1.0 - availableFrac_);
3657 9 : thisTimeStepAvailable_ = initialCharge * availableFrac_;
3658 9 : thisTimeStepBound_ = initialCharge * (1.0 - availableFrac_);
3659 9 : if (lifeCalculation_) {
3660 9 : count0_ = 1; // Index 0 is for initial SOC, so new input starts from index 1.
3661 9 : b10_[0] = startingSOC_; // the initial fractional SOC is stored as the reference
3662 9 : x0_[0] = 0.0;
3663 909 : for (int loop = 1; loop < maxRainflowArrayBounds_ + 1; ++loop) {
3664 900 : b10_[loop] = 0.0;
3665 900 : x0_[loop] = 0.0;
3666 : }
3667 54 : for (int loop = 0; loop < cycleBinNum_; ++loop) {
3668 45 : oneNmb0_[loop] = 0.0;
3669 45 : nmb0_[loop] = 0.0;
3670 : }
3671 9 : batteryDamage_ = 0.0;
3672 : }
3673 74 : } else if (storageModelMode_ == StorageModelType::LiIonNmcBattery) {
3674 : // Copy the initial battery state to the last battery state
3675 9 : *ssc_lastBatteryState_ = *ssc_initBatteryState_;
3676 9 : ssc_lastBatteryTimeStep_ = ssc_initBatteryTimeStep_;
3677 9 : ssc_battery_->set_state(*ssc_lastBatteryState_, ssc_initBatteryTimeStep_);
3678 : }
3679 83 : myWarmUpFlag_ = true;
3680 83 : }
3681 :
3682 74 : void ElectricStorage::reinitZoneGainsAtBeginEnvironment()
3683 : {
3684 74 : qdotConvZone_ = 0.0;
3685 74 : qdotRadZone_ = 0.0;
3686 74 : }
3687 :
3688 36 : void ElectricStorage::reinitAtEndWarmup()
3689 : {
3690 : // need to reset initial state of charge at beginning of environment but after warm up is complete
3691 36 : lastTimeStepStateOfCharge_ = startingEnergyStored_;
3692 36 : thisTimeStepStateOfCharge_ = startingEnergyStored_;
3693 36 : if (storageModelMode_ == StorageModelType::KIBaMBattery) {
3694 4 : Real64 initialCharge = maxAhCapacity_ * startingSOC_;
3695 4 : lastTwoTimeStepAvailable_ = initialCharge * availableFrac_;
3696 4 : lastTwoTimeStepBound_ = initialCharge * (1.0 - availableFrac_);
3697 4 : lastTimeStepAvailable_ = initialCharge * availableFrac_;
3698 4 : lastTimeStepBound_ = initialCharge * (1.0 - availableFrac_);
3699 4 : thisTimeStepAvailable_ = initialCharge * availableFrac_;
3700 4 : thisTimeStepBound_ = initialCharge * (1.0 - availableFrac_);
3701 4 : if (lifeCalculation_) {
3702 4 : count0_ = 1; // Index 0 is for initial SOC, so new input starts from index 1.
3703 4 : b10_[0] = startingSOC_; // the initial fractional SOC is stored as the reference
3704 4 : x0_[0] = 0.0;
3705 404 : for (int loop = 1; loop < maxRainflowArrayBounds_ + 1; ++loop) {
3706 400 : b10_[loop] = 0.0;
3707 400 : x0_[loop] = 0.0;
3708 : }
3709 24 : for (int loop = 0; loop < cycleBinNum_; ++loop) {
3710 20 : oneNmb0_[loop] = 0.0;
3711 20 : nmb0_[loop] = 0.0;
3712 : }
3713 4 : batteryDamage_ = 0.0;
3714 : }
3715 32 : } else if (storageModelMode_ == StorageModelType::LiIonNmcBattery) {
3716 : // Copy the initial battery state to the last battery state
3717 4 : *ssc_lastBatteryState_ = *ssc_initBatteryState_;
3718 4 : ssc_lastBatteryTimeStep_ = ssc_initBatteryTimeStep_;
3719 4 : ssc_battery_->set_state(*ssc_lastBatteryState_, ssc_lastBatteryTimeStep_);
3720 : }
3721 36 : myWarmUpFlag_ = false;
3722 36 : }
3723 :
3724 130975 : void ElectricStorage::timeCheckAndUpdate(EnergyPlusData &state)
3725 : {
3726 :
3727 130975 : if (myWarmUpFlag_ && !state.dataGlobal->WarmupFlag) {
3728 36 : reinitAtEndWarmup();
3729 : }
3730 :
3731 : Real64 timeElapsedLoc =
3732 130975 : state.dataGlobal->HourOfDay + state.dataGlobal->TimeStep * state.dataGlobal->TimeStepZone + state.dataHVACGlobal->SysTimeElapsed;
3733 130975 : if (timeElapsed_ != timeElapsedLoc) { // time changed, update last with "current" result from previous time
3734 52310 : if (storageModelMode_ == StorageModelType::KIBaMBattery && lifeCalculation_) {
3735 : // At this point, the current values, last time step values and last two time step values have not been updated, hence:
3736 : // "ThisTimeStep*" actually points to the previous one time step
3737 : // "LastTimeStep*" actually points to the previous two time steps
3738 : // "LastTwoTimeStep" actually points to the previous three time steps
3739 :
3740 : // Calculate the fractional SOC change between the "current" time step and the "previous one" time step
3741 4483 : Real64 deltaSOC1 = thisTimeStepAvailable_ + thisTimeStepBound_ - lastTimeStepAvailable_ - lastTimeStepBound_;
3742 4483 : deltaSOC1 /= maxAhCapacity_;
3743 :
3744 : // Calculate the fractional SOC change between the "previous one" time step and the "previous two" time steps
3745 4483 : Real64 deltaSOC2 = lastTimeStepAvailable_ + lastTimeStepBound_ - lastTwoTimeStepAvailable_ - lastTwoTimeStepBound_;
3746 4483 : deltaSOC2 /= maxAhCapacity_;
3747 :
3748 : // DeltaSOC2 = 0 may occur at the begining of each simulation environment.
3749 : // DeltaSOC1 * DeltaSOC2 means that the SOC from "LastTimeStep" is a peak or valley. Only peak or valley needs
3750 : // to call the rain flow algorithm
3751 4483 : if ((deltaSOC2 == 0) || ((deltaSOC1 * deltaSOC2) < 0)) {
3752 : // Because we cannot determine whehter "ThisTimeStep" is a peak or valley (next time step is unknown yet), we
3753 : // use the "LastTimeStep" value for battery life calculation.
3754 1471 : Real64 input0 = (lastTimeStepAvailable_ + lastTimeStepBound_) / maxAhCapacity_;
3755 1471 : b10_[count0_] = input0;
3756 :
3757 : // The array size needs to be increased when count = MaxRainflowArrayBounds. Please note that (MaxRainflowArrayBounds +1)
3758 : // is the index used in the subroutine RainFlow. So we cannot reallocate array size until count = MaxRainflowArrayBounds +1.
3759 :
3760 1471 : int constexpr maxRainflowArrayInc_ = 100;
3761 :
3762 1471 : if (count0_ == maxRainflowArrayBounds_) {
3763 0 : b10_.resize(maxRainflowArrayBounds_ + 1 + maxRainflowArrayInc_, 0.0);
3764 0 : x0_.resize(maxRainflowArrayBounds_ + 1 + maxRainflowArrayInc_, 0.0);
3765 0 : maxRainflowArrayBounds_ += maxRainflowArrayInc_;
3766 : }
3767 :
3768 1471 : rainflow(cycleBinNum_, input0, b10_, x0_, count0_, nmb0_, oneNmb0_);
3769 :
3770 1471 : batteryDamage_ = 0.0;
3771 :
3772 8826 : for (int binNum = 0; binNum < cycleBinNum_; ++binNum) {
3773 : // Battery damage is calculated by accumulating the impact from each cycle.
3774 7355 : batteryDamage_ += oneNmb0_[binNum] / lifeCurve_->value(state, (double(binNum) / double(cycleBinNum_)));
3775 : }
3776 : }
3777 52310 : } else if (storageModelMode_ == StorageModelType::LiIonNmcBattery) {
3778 4483 : *ssc_lastBatteryState_ = ssc_battery_->get_state();
3779 4483 : ssc_lastBatteryTimeStep_ = ssc_battery_->get_params().dt_hr;
3780 : }
3781 :
3782 52310 : lastTimeStepStateOfCharge_ = thisTimeStepStateOfCharge_;
3783 52310 : lastTwoTimeStepAvailable_ = lastTimeStepAvailable_;
3784 52310 : lastTwoTimeStepBound_ = lastTimeStepBound_;
3785 52310 : lastTimeStepAvailable_ = thisTimeStepAvailable_;
3786 52310 : lastTimeStepBound_ = thisTimeStepBound_;
3787 52310 : timeElapsed_ = timeElapsedLoc;
3788 :
3789 : } // end if time changed
3790 130975 : }
3791 :
3792 130975 : void ElectricStorage::simulate(EnergyPlusData &state,
3793 : Real64 &powerCharge,
3794 : Real64 &powerDischarge,
3795 : bool &charging,
3796 : bool &discharging,
3797 : Real64 const controlSOCMaxFracLimit,
3798 : Real64 const controlSOCMinFracLimit)
3799 : {
3800 : // pass thru to constrain function depending on storage model type
3801 130975 : if (availSched_->getCurrentVal() == 0.0) { // storage not available
3802 0 : discharging = false;
3803 0 : powerDischarge = 0.0;
3804 0 : charging = false;
3805 0 : powerCharge = 0.0;
3806 : }
3807 :
3808 130975 : if (storageModelMode_ == StorageModelType::SimpleBucketStorage) {
3809 109605 : simulateSimpleBucketModel(state, powerCharge, powerDischarge, charging, discharging, controlSOCMaxFracLimit, controlSOCMinFracLimit);
3810 21370 : } else if (storageModelMode_ == StorageModelType::KIBaMBattery) {
3811 10685 : simulateKineticBatteryModel(state, powerCharge, powerDischarge, charging, discharging, controlSOCMaxFracLimit, controlSOCMinFracLimit);
3812 10685 : } else if (storageModelMode_ == StorageModelType::LiIonNmcBattery) {
3813 10685 : simulateLiIonNmcBatteryModel(state, powerCharge, powerDischarge, charging, discharging, controlSOCMaxFracLimit, controlSOCMinFracLimit);
3814 : }
3815 130975 : }
3816 :
3817 9 : std::string const &ElectricStorage::name() const
3818 : {
3819 9 : return name_;
3820 : }
3821 :
3822 109605 : void ElectricStorage::simulateSimpleBucketModel(EnergyPlusData &state,
3823 : Real64 &powerCharge,
3824 : Real64 &powerDischarge,
3825 : bool &charging,
3826 : bool &discharging,
3827 : Real64 const controlSOCMaxFracLimit,
3828 : Real64 const controlSOCMinFracLimit)
3829 : {
3830 :
3831 : // given arguments for how the storage operation would like to run storage charge or discharge
3832 : // apply model constraints and adjust arguments accordingly
3833 :
3834 109605 : if (charging) {
3835 :
3836 35274 : if (lastTimeStepStateOfCharge_ >= (maxEnergyCapacity_ * controlSOCMaxFracLimit)) {
3837 : // storage full! no more allowed!
3838 0 : powerCharge = 0.0;
3839 0 : charging = false;
3840 : }
3841 35274 : if (powerCharge > maxPowerStore_) {
3842 14713 : powerCharge = maxPowerStore_;
3843 : }
3844 :
3845 : // now check to see if charge would exceed capacity, and modify to just fill physical storage cap
3846 35274 : if ((lastTimeStepStateOfCharge_ + powerCharge * state.dataHVACGlobal->TimeStepSysSec * energeticEfficCharge_) >=
3847 35274 : (maxEnergyCapacity_ * controlSOCMaxFracLimit)) {
3848 0 : powerCharge = ((maxEnergyCapacity_ * controlSOCMaxFracLimit) - lastTimeStepStateOfCharge_) /
3849 0 : (state.dataHVACGlobal->TimeStepSysSec * energeticEfficCharge_);
3850 : }
3851 : } // charging
3852 :
3853 109605 : if (discharging) {
3854 :
3855 64561 : if (lastTimeStepStateOfCharge_ <= (maxEnergyCapacity_ * controlSOCMinFracLimit)) {
3856 : // storage empty no more allowed!
3857 2286 : powerDischarge = 0.0;
3858 2286 : discharging = false;
3859 : }
3860 64561 : if (powerDischarge > maxPowerDraw_) {
3861 11787 : powerDischarge = maxPowerDraw_;
3862 : }
3863 : // now check if will empty this timestep, power draw is amplified by energetic effic
3864 64561 : if ((lastTimeStepStateOfCharge_ - powerDischarge * state.dataHVACGlobal->TimeStepSysSec / energeticEfficDischarge_) <=
3865 64561 : (maxEnergyCapacity_ * controlSOCMinFracLimit)) {
3866 4578 : powerDischarge = (lastTimeStepStateOfCharge_ - (maxEnergyCapacity_ * controlSOCMinFracLimit)) * energeticEfficDischarge_ /
3867 2289 : (state.dataHVACGlobal->TimeStepSysSec);
3868 : }
3869 : }
3870 :
3871 109605 : if ((!charging) && (!discharging)) {
3872 12056 : thisTimeStepStateOfCharge_ = lastTimeStepStateOfCharge_;
3873 12056 : pelIntoStorage_ = 0.0;
3874 12056 : pelFromStorage_ = 0.0;
3875 : }
3876 109605 : if (charging) {
3877 35274 : pelIntoStorage_ = powerCharge;
3878 35274 : pelFromStorage_ = 0.0;
3879 35274 : thisTimeStepStateOfCharge_ = lastTimeStepStateOfCharge_ + powerCharge * state.dataHVACGlobal->TimeStepSysSec * energeticEfficCharge_;
3880 : }
3881 109605 : if (discharging) {
3882 62275 : pelIntoStorage_ = 0.0;
3883 62275 : pelFromStorage_ = powerDischarge;
3884 62275 : thisTimeStepStateOfCharge_ = lastTimeStepStateOfCharge_ - powerDischarge * state.dataHVACGlobal->TimeStepSysSec / energeticEfficDischarge_;
3885 62275 : thisTimeStepStateOfCharge_ = max(thisTimeStepStateOfCharge_, 0.0);
3886 : }
3887 :
3888 : // updates and reports
3889 109605 : electEnergyinStorage_ = thisTimeStepStateOfCharge_; //[J]
3890 109605 : storedPower_ = pelIntoStorage_;
3891 109605 : storedEnergy_ = pelIntoStorage_ * state.dataHVACGlobal->TimeStepSysSec;
3892 109605 : decrementedEnergyStored_ = -1.0 * storedEnergy_;
3893 109605 : drawnPower_ = pelFromStorage_;
3894 109605 : drawnEnergy_ = pelFromStorage_ * state.dataHVACGlobal->TimeStepSysSec;
3895 109605 : thermLossRate_ = max(storedPower_ * (1.0 - energeticEfficCharge_), drawnPower_ * (1.0 - energeticEfficDischarge_));
3896 109605 : thermLossEnergy_ = thermLossRate_ * state.dataHVACGlobal->TimeStepSysSec;
3897 :
3898 109605 : if (zoneNum_ > 0) { // set values for zone heat gains
3899 29548 : qdotConvZone_ = (1.0 - zoneRadFract_) * thermLossRate_;
3900 29548 : qdotRadZone_ = (zoneRadFract_)*thermLossRate_;
3901 : }
3902 109605 : }
3903 :
3904 10685 : void ElectricStorage::simulateKineticBatteryModel(EnergyPlusData &state,
3905 : Real64 &powerCharge,
3906 : Real64 &powerDischarge,
3907 : bool &charging,
3908 : bool &discharging,
3909 : Real64 const controlSOCMaxFracLimit,
3910 : Real64 const controlSOCMinFracLimit)
3911 : {
3912 :
3913 : // initialize locals
3914 10685 : Real64 I0 = 0.0;
3915 10685 : Real64 Volt = 0.0;
3916 :
3917 10685 : Real64 T0 = 0.0;
3918 10685 : Real64 qmaxf = 0.0;
3919 10685 : Real64 Ef = 0.0;
3920 10685 : Real64 q0 = 0.0;
3921 :
3922 10685 : Real64 qmax = maxAhCapacity_;
3923 10685 : Real64 E0c = chargedOCV_;
3924 10685 : Real64 E0d = dischargedOCV_;
3925 10685 : Real64 k = chargeConversionRate_;
3926 10685 : Real64 c = availableFrac_;
3927 :
3928 10685 : if (charging) {
3929 :
3930 : //*************************************************
3931 : // The sign of power and current is negative in charging
3932 : //*************************************************
3933 2134 : Real64 Pw = -powerCharge / numBattery_;
3934 2134 : q0 = lastTimeStepAvailable_ + lastTimeStepBound_;
3935 2134 : if (q0 > qmax * controlSOCMaxFracLimit) {
3936 : // stop charging with controller signal for max state of charge
3937 558 : Pw = 0.0;
3938 558 : powerCharge = 0.0;
3939 558 : charging = false;
3940 558 : storageMode_ = 0;
3941 558 : storedPower_ = 0.0;
3942 558 : storedEnergy_ = 0.0;
3943 558 : decrementedEnergyStored_ = 0.0;
3944 558 : drawnPower_ = 0.0;
3945 558 : drawnEnergy_ = 0.0;
3946 5676 : return;
3947 : }
3948 :
3949 1576 : I0 = 1.0; // Initial assumption
3950 1576 : T0 = std::abs(qmax / I0); // Initial Assumption
3951 1576 : qmaxf = qmax * k * c * T0 / (1.0 - std::exp(-k * T0) + c * (k * T0 - 1.0 + std::exp(-k * T0))); // Initial calculation of a function qmax(I)
3952 1576 : Real64 Xf = q0 / qmaxf;
3953 1576 : Ef = E0d + chargeCurve_->value(state, Xf); // E0d+Ac*Xf+Cc*Xf/(Dc-Xf) (use curve)
3954 1576 : Volt = Ef - I0 * internalR_;
3955 1576 : Real64 Inew = 0.0;
3956 1576 : if (Volt != 0.0) {
3957 1576 : Inew = Pw / Volt;
3958 : }
3959 1576 : Real64 Tnew = 0.0;
3960 1576 : if (Inew != 0.0) {
3961 1576 : Tnew = qmaxf / std::abs(Inew);
3962 : }
3963 1576 : Real64 error = 1.0;
3964 :
3965 8717 : while (error > 0.0001) { // Iteration process to get converged current(I)
3966 7141 : I0 = Inew;
3967 7141 : T0 = Tnew;
3968 7141 : qmaxf = qmax * k * c * T0 / (1.0 - std::exp(-k * T0) + c * (k * T0 - 1.0 + std::exp(-k * T0)));
3969 7141 : Xf = q0 / qmaxf;
3970 7141 : Ef = E0d + chargeCurve_->value(state, Xf); // E0d+Ac*Xf+Cc*Xf/(Dc-Xf) (use curve)
3971 7141 : Volt = Ef - I0 * internalR_;
3972 7141 : Inew = Pw / Volt;
3973 7141 : Tnew = std::abs(qmaxf / Inew); // ***Always positive here
3974 7141 : error = std::abs(Inew - I0);
3975 : }
3976 :
3977 1576 : Real64 dividend = -k * c * qmax + k * lastTimeStepAvailable_ * std::exp(-k * state.dataHVACGlobal->TimeStepSys) +
3978 1576 : q0 * k * c * (1.0 - std::exp(-k * state.dataHVACGlobal->TimeStepSys));
3979 1576 : Real64 divisor = 1.0 - std::exp(-k * state.dataHVACGlobal->TimeStepSys) +
3980 1576 : c * (k * state.dataHVACGlobal->TimeStepSys - 1 + std::exp(-k * state.dataHVACGlobal->TimeStepSys));
3981 1576 : Real64 Imax = dividend / divisor;
3982 : // Below: This is the limit of charging current from Charge Rate Limit (input)
3983 1576 : Imax = max(Imax, -(qmax - q0) * maxChargeRate_);
3984 :
3985 1576 : if (std::abs(I0) <= std::abs(Imax)) {
3986 653 : I0 = Pw / Volt;
3987 : // Pactual = I0 * Volt;
3988 : } else {
3989 923 : I0 = Imax;
3990 923 : qmaxf = 80.0; // Initial assumption to solve the equation using iterative method
3991 923 : error = 10.0; // Initial assumption ...
3992 5724 : while (error > 0.001) {
3993 : // *** I0(current) should be positive for this calculation
3994 4801 : Real64 RHS = (qmax * k * c * qmaxf / std::abs(I0)) /
3995 4801 : (1.0 - std::exp(-k * qmaxf / std::abs(I0)) + c * (k * qmaxf / std::abs(I0) - 1.0 + std::exp(-k * qmaxf / std::abs(I0))));
3996 4801 : error = std::abs(qmaxf - RHS);
3997 4801 : qmaxf = RHS;
3998 : }
3999 : }
4000 : }
4001 :
4002 10127 : if (discharging) {
4003 : //**********************************************
4004 : // The sign of power and current is positive in discharging
4005 : //**********************************************
4006 :
4007 7068 : Real64 Pw = powerDischarge / numBattery_;
4008 7068 : q0 = lastTimeStepAvailable_ + lastTimeStepBound_;
4009 :
4010 7068 : if (q0 < qmax * controlSOCMinFracLimit) {
4011 : // stop discharging with controller signal for min state of charge
4012 5118 : Pw = 0.0;
4013 5118 : discharging = false;
4014 5118 : powerDischarge = 0.0;
4015 5118 : storageMode_ = 0;
4016 5118 : storedPower_ = 0.0;
4017 5118 : storedEnergy_ = 0.0;
4018 5118 : decrementedEnergyStored_ = 0.0;
4019 5118 : drawnPower_ = 0.0;
4020 5118 : drawnEnergy_ = 0.0;
4021 5118 : return;
4022 : }
4023 :
4024 1950 : bool const ok = determineCurrentForBatteryDischarge(state, I0, T0, Volt, Pw, q0, dischargeCurve_, k, c, qmax, E0c, internalR_);
4025 1950 : if (!ok) {
4026 0 : ShowFatalError(state,
4027 0 : format("ElectricLoadCenter:Storage:Battery named=\"{}\". Battery discharge current could not be estimated due to "
4028 : "iteration limit reached. ",
4029 0 : name_));
4030 : // issue #5301, need more diagnostics for this.
4031 : }
4032 :
4033 1950 : Real64 dividend = k * lastTimeStepAvailable_ * std::exp(-k * state.dataHVACGlobal->TimeStepSys) +
4034 1950 : q0 * k * c * (1.0 - std::exp(-k * state.dataHVACGlobal->TimeStepSys));
4035 1950 : Real64 divisor = 1.0 - std::exp(-k * state.dataHVACGlobal->TimeStepSys) +
4036 1950 : c * (k * state.dataHVACGlobal->TimeStepSys - 1.0 + std::exp(-k * state.dataHVACGlobal->TimeStepSys));
4037 1950 : Real64 Imax = dividend / divisor;
4038 1950 : Imax = min(Imax, maxDischargeI_);
4039 1950 : if (std::abs(I0) <= Imax) {
4040 1757 : I0 = Pw / Volt;
4041 : // Pactual = I0 * Volt;
4042 : } else {
4043 193 : I0 = Imax;
4044 193 : qmaxf = 10.0; // Initial assumption to solve the equation using iterative method
4045 193 : Real64 error = 10.0; // Initial assumption ...
4046 1862 : while (error > 0.001) {
4047 1669 : Real64 RHS = (qmax * k * c * qmaxf / I0) / (1.0 - std::exp(-k * qmaxf / I0) + c * (k * qmaxf / I0 - 1 + std::exp(-k * qmaxf / I0)));
4048 1669 : error = std::abs(qmaxf - RHS);
4049 1669 : qmaxf = RHS;
4050 : }
4051 193 : Real64 Xf = (qmax - q0) / qmaxf;
4052 193 : Ef = E0c + dischargeCurve_->value(state, Xf);
4053 193 : Volt = Ef - I0 * internalR_;
4054 : }
4055 1950 : if (Volt < cutoffV_) {
4056 2 : I0 = 0.0;
4057 : }
4058 : } // if discharging
4059 :
4060 5009 : if ((!charging) && (!discharging)) {
4061 1483 : thisTimeStepAvailable_ = lastTimeStepAvailable_;
4062 1483 : thisTimeStepBound_ = lastTimeStepBound_;
4063 1483 : I0 = 0.0;
4064 1483 : Volt = 0.0;
4065 1483 : q0 = lastTimeStepAvailable_ + lastTimeStepBound_;
4066 : } else {
4067 3526 : Real64 newAvailable = lastTimeStepAvailable_ * std::exp(-k * state.dataHVACGlobal->TimeStepSys) +
4068 3526 : (q0 * k * c - I0) * (1.0 - std::exp(-k * state.dataHVACGlobal->TimeStepSys)) / k -
4069 3526 : I0 * c * (k * state.dataHVACGlobal->TimeStepSys - 1.0 + std::exp(-k * state.dataHVACGlobal->TimeStepSys)) / k;
4070 3526 : Real64 newBound = lastTimeStepBound_ * std::exp(-k * state.dataHVACGlobal->TimeStepSys) +
4071 3526 : q0 * (1.0 - c) * (1.0 - std::exp(-k * state.dataHVACGlobal->TimeStepSys)) -
4072 3526 : I0 * (1.0 - c) * (k * state.dataHVACGlobal->TimeStepSys - 1.0 + std::exp(-k * state.dataHVACGlobal->TimeStepSys)) / k;
4073 3526 : thisTimeStepAvailable_ = max(0.0, newAvailable);
4074 3526 : thisTimeStepBound_ = max(0.0, newBound);
4075 : }
4076 :
4077 : // Pactual = I0 * Volt;
4078 5009 : Real64 TotalSOC = thisTimeStepAvailable_ + thisTimeStepBound_;
4079 :
4080 : // output1
4081 5009 : if (TotalSOC > q0) {
4082 1578 : storageMode_ = 2;
4083 1578 : storedPower_ = -1.0 * Volt * I0 * numBattery_; // Issue #5303, fix sign issue
4084 1578 : storedEnergy_ = -1.0 * Volt * I0 * numBattery_ * state.dataHVACGlobal->TimeStepSysSec;
4085 1578 : decrementedEnergyStored_ = -1.0 * storedEnergy_;
4086 1578 : drawnPower_ = 0.0;
4087 1578 : drawnEnergy_ = 0.0;
4088 :
4089 3431 : } else if (TotalSOC < q0) {
4090 1948 : storageMode_ = 1;
4091 1948 : storedPower_ = 0.0;
4092 1948 : storedEnergy_ = 0.0;
4093 1948 : decrementedEnergyStored_ = 0.0;
4094 1948 : drawnPower_ = Volt * I0 * numBattery_;
4095 1948 : drawnEnergy_ = Volt * I0 * numBattery_ * state.dataHVACGlobal->TimeStepSysSec;
4096 :
4097 : } else {
4098 1483 : storageMode_ = 0;
4099 1483 : storedPower_ = 0.0;
4100 1483 : storedEnergy_ = 0.0;
4101 1483 : decrementedEnergyStored_ = 0.0;
4102 1483 : drawnPower_ = 0.0;
4103 1483 : drawnEnergy_ = 0.0;
4104 : }
4105 :
4106 5009 : absoluteSOC_ = TotalSOC * numBattery_;
4107 5009 : fractionSOC_ = TotalSOC / qmax;
4108 5009 : batteryCurrent_ = I0 * parallelNum_;
4109 5009 : batteryVoltage_ = Volt * seriesNum_;
4110 5009 : thermLossRate_ = internalR_ * pow_2(I0) * numBattery_;
4111 5009 : thermLossEnergy_ = internalR_ * pow_2(I0) * state.dataHVACGlobal->TimeStepSysSec * numBattery_;
4112 :
4113 5009 : if (zoneNum_ > 0) { // set values for zone heat gains
4114 0 : qdotConvZone_ = ((1.0 - zoneRadFract_) * thermLossRate_) * numBattery_;
4115 0 : qdotRadZone_ = ((zoneRadFract_)*thermLossRate_) * numBattery_;
4116 : }
4117 :
4118 5009 : powerCharge = storedPower_;
4119 5009 : powerDischarge = drawnPower_;
4120 : }
4121 :
4122 10685 : void ElectricStorage::simulateLiIonNmcBatteryModel(EnergyPlusData &state,
4123 : Real64 &powerCharge,
4124 : Real64 &powerDischarge,
4125 : bool &charging,
4126 : bool &discharging,
4127 : Real64 const controlSOCMaxFracLimit,
4128 : Real64 const controlSOCMinFracLimit)
4129 : {
4130 :
4131 : // Disable floating point exceptions around SSC battery calculations, which uses quiet_NaN in particular
4132 : #ifdef DEBUG_ARITHM_GCC_OR_CLANG
4133 10685 : int old_excepts = fegetexcept();
4134 10685 : fedisableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
4135 : #endif
4136 :
4137 : #ifdef DEBUG_ARITHM_MSVC
4138 : unsigned int ori_fpcntrl = 0;
4139 : _controlfp_s(&ori_fpcntrl, 0, 0);
4140 :
4141 : // Temporarily disable traps, as MSVC does it reversed compared to GCC,
4142 : // you need to turn ON the bits for the exceptions you want to mask
4143 : unsigned int fpcntrl = ori_fpcntrl | (_EM_ZERODIVIDE | _EM_INVALID | _EM_OVERFLOW);
4144 : _controlfp_s(&fpcntrl, fpcntrl, _MCW_EM);
4145 : #endif
4146 :
4147 : // Copy the battery state from the end of last timestep
4148 10685 : battery_state battState = *ssc_lastBatteryState_;
4149 10685 : ssc_battery_->set_state(battState, ssc_lastBatteryTimeStep_);
4150 10685 : if (std::lround(ssc_battery_->get_params().dt_hr * 60.0) != std::lround(state.dataHVACGlobal->TimeStepSys * 60.0)) {
4151 1673 : ssc_battery_->ChangeTimestep(state.dataHVACGlobal->TimeStepSys);
4152 : }
4153 :
4154 : // Set the temperature the battery sees
4155 10685 : if (zoneNum_ > 0) {
4156 : // If in a zone, use the zone temperature
4157 0 : battState.thermal->T_room = state.dataZoneTempPredictorCorrector->zoneHeatBalance(zoneNum_).ZT;
4158 : } else {
4159 : // If outside, use outdoor temperature
4160 10685 : battState.thermal->T_room = state.dataEnvrn->OutDryBulbTemp;
4161 : }
4162 :
4163 : // Set the SOC limits
4164 10685 : ssc_battery_->changeSOCLimits(controlSOCMinFracLimit * 100.0, controlSOCMaxFracLimit * 100.0);
4165 :
4166 : // Set the current timestep length
4167 :
4168 : // Run the battery
4169 : // SAM uses negative values for charging, positive for discharging
4170 : // E+ power/energy outputs are positive
4171 10685 : double power{0.0}; // Using double instead of Real64 because SSC is expecting a double
4172 10685 : if (charging) {
4173 2134 : power = -powerCharge;
4174 8551 : } else if (discharging) {
4175 7068 : power = powerDischarge;
4176 : }
4177 10685 : power *= 0.001; // Convert to kW
4178 10685 : ssc_battery_->runPower(power);
4179 :
4180 : // Store outputs
4181 10685 : const battery_state &battState2{ssc_battery_->get_state()};
4182 10685 : if (battState2.P < 0.0) { // negative for charging
4183 539 : storageMode_ = 2;
4184 539 : powerCharge = fabs(battState2.P) * 1000.0; // kW -> W
4185 539 : powerDischarge = 0.0;
4186 539 : charging = true;
4187 539 : discharging = false;
4188 10146 : } else if (battState2.P > 0.0) { // positive for discharging
4189 1261 : storageMode_ = 1;
4190 1261 : powerCharge = 0.0;
4191 1261 : powerDischarge = fabs(battState2.P) * 1000.0; // kW -> W
4192 1261 : charging = false;
4193 1261 : discharging = true;
4194 : } else {
4195 8885 : storageMode_ = 0;
4196 8885 : powerCharge = 0.0;
4197 8885 : powerDischarge = 0.0;
4198 8885 : charging = false;
4199 8885 : discharging = false;
4200 : }
4201 10685 : absoluteSOC_ = ssc_battery_->charge_total();
4202 10685 : fractionSOC_ = ssc_battery_->SOC() * 0.01; // % -> fraction
4203 10685 : batteryCurrent_ = ssc_battery_->I();
4204 10685 : batteryVoltage_ = ssc_battery_->V();
4205 10685 : batteryDamage_ = 1.0 - (ssc_battery_->charge_maximum_lifetime() / maxAhCapacity_);
4206 10685 : storedPower_ = powerCharge;
4207 10685 : storedEnergy_ = storedPower_ * state.dataHVACGlobal->TimeStepSysSec;
4208 10685 : drawnPower_ = powerDischarge;
4209 10685 : drawnEnergy_ = drawnPower_ * state.dataHVACGlobal->TimeStepSysSec;
4210 10685 : decrementedEnergyStored_ = -storedEnergy_;
4211 10685 : thermLossRate_ = battState2.thermal->heat_dissipated * 1000.0; // kW -> W
4212 10685 : thermLossEnergy_ = thermLossRate_ * state.dataHVACGlobal->TimeStepSysSec;
4213 10685 : batteryTemperature_ = battState2.thermal->T_batt;
4214 :
4215 : // Zone Heat Gains
4216 10685 : if (zoneNum_ > 0) { // set values for zone heat gains
4217 0 : qdotConvZone_ = (1.0 - zoneRadFract_) * thermLossRate_;
4218 0 : qdotRadZone_ = (zoneRadFract_)*thermLossRate_;
4219 : }
4220 :
4221 : #ifdef DEBUG_ARITHM_GCC_OR_CLANG
4222 10685 : feenableexcept(old_excepts);
4223 : #endif
4224 :
4225 : #ifdef DEBUG_ARITHM_MSVC
4226 : _controlfp_s(nullptr, ori_fpcntrl, _MCW_EM);
4227 : #endif
4228 10685 : }
4229 :
4230 0 : Real64 ElectricStorage::drawnPower() const
4231 : {
4232 0 : return drawnPower_;
4233 : }
4234 :
4235 0 : Real64 ElectricStorage::storedPower() const
4236 : {
4237 0 : return storedPower_;
4238 : }
4239 :
4240 0 : Real64 ElectricStorage::drawnEnergy() const
4241 : {
4242 0 : return drawnEnergy_;
4243 : }
4244 :
4245 0 : Real64 ElectricStorage::storedEnergy() const
4246 : {
4247 0 : return storedEnergy_;
4248 : }
4249 :
4250 0 : Real64 ElectricStorage::stateOfChargeFraction() const
4251 : {
4252 0 : return fractionSOC_;
4253 : }
4254 :
4255 0 : Real64 ElectricStorage::batteryTemperature() const
4256 : {
4257 0 : assert(storageModelMode_ == StorageModelType::LiIonNmcBattery);
4258 0 : return batteryTemperature_;
4259 : }
4260 :
4261 1950 : bool ElectricStorage::determineCurrentForBatteryDischarge(EnergyPlusData &state,
4262 : Real64 &curI0,
4263 : Real64 &curT0,
4264 : Real64 &curVolt,
4265 : Real64 const Pw, // Power withdraw from each module,
4266 : Real64 const q0, // available charge last timestep, sum of available and bound
4267 : Curve::Curve *curve,
4268 : Real64 const k,
4269 : Real64 const c,
4270 : Real64 const qmax,
4271 : Real64 const E0c,
4272 : Real64 const InternalR)
4273 : {
4274 1950 : curI0 = 10.0; // Initial assumption
4275 1950 : curT0 = qmax / curI0; // Initial Assumption
4276 1950 : Real64 qmaxf = qmax * k * c * curT0 /
4277 1950 : (1.0 - std::exp(-k * curT0) + c * (k * curT0 - 1.0 + std::exp(-k * curT0))); // Initial calculation of a function qmax(I)
4278 1950 : Real64 Xf = (qmax - q0) / qmaxf;
4279 1950 : Real64 Ef = E0c + curve->value(state, Xf); // E0d+Ac*Xf+Cc*X/(Dc-Xf)
4280 1950 : curVolt = Ef - curI0 * InternalR;
4281 1950 : Real64 Inew = Pw / curVolt;
4282 1950 : Real64 Tnew = qmaxf / Inew;
4283 1950 : Real64 error = 1.0;
4284 1950 : int countForIteration = 0;
4285 1950 : bool exceedIterationLimit = false;
4286 :
4287 11130 : while (error > 0.0001) { // Iteration process to get converged current(I)
4288 9180 : curI0 = Inew;
4289 9180 : curT0 = Tnew;
4290 9180 : qmaxf = qmax * k * c * curT0 / (1.0 - std::exp(-k * curT0) + c * (k * curT0 - 1.0 + std::exp(-k * curT0)));
4291 : // add div by zero protection #5301
4292 9180 : if (qmaxf != 0.0) {
4293 9180 : Xf = (qmax - q0) / qmaxf;
4294 : } else {
4295 0 : Xf = 1.0;
4296 : }
4297 :
4298 9180 : Ef = E0c + curve->value(state, Xf); // E0c+Ad*Xf+Cd*X/(Dd-Xf)
4299 9180 : curVolt = Ef - curI0 * InternalR;
4300 : // add div by zero protection #5301
4301 9180 : if (curVolt != 0.0) {
4302 9180 : Inew = Pw / curVolt;
4303 : } else {
4304 0 : Inew = 1.0;
4305 : }
4306 :
4307 : // add div by zero protection #5301
4308 9180 : if (Inew != 0.0) {
4309 9180 : Tnew = qmaxf / Inew;
4310 : } else {
4311 0 : Tnew = 1.0;
4312 : }
4313 :
4314 9180 : error = std::abs(Inew - curI0);
4315 9180 : ++countForIteration;
4316 9180 : if (countForIteration > 1000) {
4317 0 : exceedIterationLimit = true;
4318 : // Issue #5301 need more diagnostics for this case
4319 0 : ShowWarningError(
4320 : state, "ElectricStorage::determineCurrentForBatteryDischarge, iteration limit exceeded, failed to solve for discharge current.");
4321 0 : ShowContinueError(state, format("Last timestep charge available, q0 = {:.5R}", q0));
4322 0 : ShowContinueError(state, format("New Current, Inew = {:.5R} [Amps]", Inew));
4323 0 : ShowContinueError(state, format("Power discharge per module cell, Pw = {:.5R} ", Pw));
4324 0 : ShowContinueError(
4325 0 : state, format("Charge Conversion Rate, [1/h] change rate from bound charge energy to available charge, parameter k = {:.5R}", k));
4326 0 : ShowContinueError(state, format("parameter c = {:.5R}", c));
4327 0 : ShowContinueError(state, format("parameter qmax = {:.5R}", qmax));
4328 0 : ShowContinueError(state, format("Fully charged open circuit voltage, parameter E0c = {:.5R}", E0c));
4329 0 : ShowContinueError(state, format("parameter InternalR = {:.5R}", InternalR));
4330 0 : if (qmaxf == 0.0) {
4331 0 : ShowContinueError(state, "qmaxf was zero, would have divided by zero.");
4332 : }
4333 0 : if (Inew == 0.0) {
4334 0 : ShowContinueError(state, "Inew was zero, would have divided by zero. ");
4335 : }
4336 0 : if (curVolt == 0.0) {
4337 0 : ShowContinueError(state, "curVolt was zero, would have divided by zero. ");
4338 : }
4339 :
4340 0 : ShowContinueErrorTimeStamp(state, "ElectricStorage::determineCurrentForBatteryDischarge ");
4341 0 : break;
4342 : }
4343 : }
4344 1950 : return (!exceedIterationLimit);
4345 : }
4346 :
4347 1471 : void ElectricStorage::rainflow(int const numbin, // numbin = constant value
4348 : Real64 const input, // input = input value from other object (battery model)
4349 : std::vector<Real64> &B1, // stores values of points, calculated here - stored for next timestep
4350 : std::vector<Real64> &X, // stores values of two data point difference, calculated here - stored for next timestep
4351 : int &count, // calculated here - stored for next timestep in main loop
4352 : std::vector<Real64> &Nmb, // calculated here - stored for next timestep in main loop
4353 : std::vector<Real64> &OneNmb // calculated here - stored for next timestep in main loop
4354 : )
4355 : {
4356 : // SUBROUTINE INFORMATION:
4357 : // AUTHOR Y. KyungTae & W. Wang
4358 : // DATE WRITTEN July-August, 2011
4359 : // MODIFIED na
4360 : // RE-ENGINEERED na
4361 :
4362 : // PURPOSE OF THIS SUBROUTINE:
4363 : // Rainflow cycle counting for battery life calculation
4364 :
4365 : // METHODOLOGY EMPLOYED:
4366 : // <description>
4367 :
4368 : // REFERENCES:
4369 : // Ariduru S. 2004. Fatigue life calculation by rainflow cycle counting method.
4370 : // Master Thesis, Middle East Technical University.
4371 :
4372 : // Locals
4373 : // SUBROUTINE ARGUMENT DEFINITIONS:
4374 : // Array B1 stores the value of points
4375 : // Array X stores the value of two data points' difference.
4376 :
4377 : int num;
4378 :
4379 1471 : X[count] = input - B1[count - 1]; // calculate the difference between two data (current and previous)
4380 :
4381 : // Get rid of the data if it is not peak nor valley
4382 : // The value of count means the number of peak or valley points added to the arrary B10/B1, not including the
4383 : // first point B10(0)/B1(0). Therefore, even if count =2, B1(count-2) is still valid.
4384 1471 : if (count >= 3) {
4385 : // The following check on peak or valley may be not necessary in most times because the same check is made in the
4386 : // upper-level subroutine. However, it does not hurt to leave it here.
4387 1461 : if (X[count] * X[count - 1] >= 0) {
4388 1433 : X[count - 1] = B1[count] - B1[count - 2];
4389 1433 : shift(B1, count - 1, count, B1); // Get rid of (count-1) row in B1
4390 1433 : shift(X, count, count, X);
4391 1433 : --count; // If the value keep increasing or decreasing, get rid of the middle point.
4392 : } // Only valley and peak will be stored in the matrix, B1
4393 :
4394 1461 : if ((count == 3) && (std::abs(X[2]) <= std::abs(X[3]))) {
4395 : // This means the starting point is included in X(2), a half cycle is counted according to the rain flow
4396 : // algorithm specified in the reference (Ariduru S. 2004)
4397 4 : num = nint((std::abs(X[2]) * numbin * 10 + 5) / 10); // Count half cycle
4398 4 : Nmb[num] += 0.5;
4399 : // B1 = eoshift( B1, 1 ); // Once counting a half cycle, get rid of the value.
4400 4 : B1.erase(B1.begin());
4401 4 : B1.push_back(0.0);
4402 : // X = eoshift( X, 1 );
4403 4 : X.erase(X.begin());
4404 4 : X.push_back(0.0);
4405 4 : --count; // The number of matrix, B1 and X1 decrease.
4406 : }
4407 : } // Counting cyle end
4408 : //*** Note: The value of "count" changes in the upper "IF LOOP"
4409 :
4410 1471 : if (count >= 4) { // count 1 cycle
4411 56 : while (std::abs(X[count]) > std::abs(X[count - 1])) {
4412 : // This means that the starting point is not included in X(count-1). a cycle is counted according to the rain flow
4413 : // algorithm specified in the reference (Ariduru S. 2004)
4414 10 : num = nint((std::abs(X[count - 1]) * numbin * 10 + 5) / 10);
4415 10 : ++Nmb[num];
4416 :
4417 : // X(count-2) = ABS(X(count))-ABS(X(count-1))+ABS(X(count-2))
4418 10 : X[count - 2] = B1[count] - B1[count - 3]; // Updating X needs to be done before shift operation below
4419 :
4420 10 : shift(B1, count - 1, count, B1); // Get rid of two data points one by one
4421 10 : shift(B1, count - 2, count, B1); // Delete one point
4422 :
4423 10 : shift(X, count, count, X); // Get rid of two data points one by one
4424 10 : shift(X, count - 1, count, X); // Delete one point
4425 :
4426 10 : count -= 2; // If one cycle is counted, two data points are deleted.
4427 10 : if (count < 4) {
4428 6 : break; // When only three data points exists, one cycle cannot be counted.
4429 : }
4430 : }
4431 : }
4432 :
4433 1471 : ++count;
4434 :
4435 : // Check the rest of the half cycles every time step
4436 1471 : OneNmb = Nmb; // Array Nmb (Bins) will be used for the next time step later.
4437 : // OneNmb is used to show the current output only.
4438 1471 : }
4439 :
4440 2906 : void ElectricStorage::shift(std::vector<Real64> &A, int const m, int const n, std::vector<Real64> &B)
4441 : {
4442 : // SUBROUTINE INFORMATION:
4443 : // AUTHOR Y. KyungTae & W. Wang
4444 : // DATE WRITTEN July-August, 2011
4445 : // MODIFIED na
4446 : // RE-ENGINEERED na
4447 :
4448 : // PURPOSE OF THIS SUBROUTINE:
4449 : // Utility subroutine for rainflow cycle counting
4450 :
4451 : int ShiftNum; // Loop variable
4452 :
4453 7461 : for (ShiftNum = 1; ShiftNum <= m - 1; ++ShiftNum) {
4454 4555 : B[ShiftNum] = A[ShiftNum];
4455 : }
4456 :
4457 7285 : for (ShiftNum = m; ShiftNum <= n; ++ShiftNum) {
4458 4379 : B[ShiftNum] = A[ShiftNum + 1];
4459 : }
4460 2906 : }
4461 :
4462 : // constructor
4463 13 : ElectricTransformer::ElectricTransformer(EnergyPlusData &state, std::string const &objectName)
4464 13 : : myOneTimeFlag_(true), usageMode_(TransformerUse::Invalid), heatLossesDestination_(ThermalLossDestination::Invalid), zoneNum_(0),
4465 13 : zoneRadFrac_(0.0), ratedCapacity_(0.0), factorTempCoeff_(0.0), tempRise_(0.0), eddyFrac_(0.0),
4466 13 : performanceInputMode_(TransformerPerformanceInput::Invalid), ratedEfficiency_(0.0), ratedPUL_(0.0), ratedTemp_(0.0), maxPUL_(0.0),
4467 26 : considerLosses_(true), ratedNL_(0.0), ratedLL_(0.0), overloadErrorIndex_(0), efficiency_(0.0), powerIn_(0.0), energyIn_(0.0), powerOut_(0.0),
4468 13 : energyOut_(0.0), noLoadLossRate_(0.0), noLoadLossEnergy_(0.0), loadLossRate_(0.0), loadLossEnergy_(0.0), thermalLossRate_(0.0),
4469 13 : thermalLossEnergy_(0.0), elecUseMeteredUtilityLosses_(0.0), powerConversionMeteredLosses_(0.0), qdotConvZone_(0.0), qdotRadZone_(0.0)
4470 : {
4471 : static constexpr std::string_view routineName = "ElectricTransformer constructor ";
4472 13 : auto &s_ipsc = state.dataIPShortCut;
4473 :
4474 13 : bool errorsFound = false;
4475 13 : int transformerIDFObjectNum = 0;
4476 13 : s_ipsc->cCurrentModuleObject = "ElectricLoadCenter:Transformer";
4477 :
4478 13 : transformerIDFObjectNum = state.dataInputProcessing->inputProcessor->getObjectItemNum(state, "ElectricLoadCenter:Transformer", objectName);
4479 13 : if (transformerIDFObjectNum > 0) {
4480 : int numAlphas; // Number of elements in the alpha array
4481 : int numNums; // Number of elements in the numeric array
4482 : int IOStat; // IO Status when calling get input subroutine
4483 26 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
4484 13 : s_ipsc->cCurrentModuleObject,
4485 : transformerIDFObjectNum,
4486 13 : s_ipsc->cAlphaArgs,
4487 : numAlphas,
4488 13 : s_ipsc->rNumericArgs,
4489 : numNums,
4490 : IOStat,
4491 13 : s_ipsc->lNumericFieldBlanks,
4492 13 : s_ipsc->lAlphaFieldBlanks,
4493 13 : s_ipsc->cAlphaFieldNames,
4494 13 : s_ipsc->cNumericFieldNames);
4495 :
4496 13 : ErrorObjectHeader eoh{routineName, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)};
4497 13 : name_ = s_ipsc->cAlphaArgs(1);
4498 : // how to verify names are unique across objects? add to GlobalNames?
4499 13 : if (s_ipsc->lAlphaFieldBlanks(2)) {
4500 0 : availSched_ = Sched::GetScheduleAlwaysOn(state);
4501 13 : } else if ((availSched_ = Sched::GetSchedule(state, s_ipsc->cAlphaArgs(2))) == nullptr) {
4502 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(2), s_ipsc->cAlphaArgs(2));
4503 0 : errorsFound = true;
4504 : }
4505 :
4506 13 : if (s_ipsc->lAlphaFieldBlanks(3)) {
4507 0 : usageMode_ = TransformerUse::PowerInFromGrid; // default
4508 13 : } else if ((usageMode_ = static_cast<TransformerUse>(getEnumValue(transformerUseNamesUC, s_ipsc->cAlphaArgs(3)))) ==
4509 : TransformerUse::Invalid) {
4510 0 : ShowSevereInvalidKey(state, eoh, s_ipsc->cAlphaFieldNames(3), s_ipsc->cAlphaArgs(3));
4511 0 : errorsFound = true;
4512 : }
4513 :
4514 13 : if (s_ipsc->lAlphaFieldBlanks(4)) {
4515 13 : heatLossesDestination_ = ThermalLossDestination::LostToOutside;
4516 0 : } else if ((zoneNum_ = Util::FindItemInList(s_ipsc->cAlphaArgs(4), state.dataHeatBal->Zone)) == 0) {
4517 0 : heatLossesDestination_ = ThermalLossDestination::LostToOutside;
4518 0 : ShowWarningItemNotFound(
4519 0 : state, eoh, s_ipsc->cAlphaFieldNames(4), s_ipsc->cAlphaArgs(4), "Transformer heat losses will not be added to a zone");
4520 : // continue with simulation but storage losses not sent to a zone.
4521 : } else {
4522 0 : heatLossesDestination_ = ThermalLossDestination::ZoneGains;
4523 : }
4524 :
4525 13 : zoneRadFrac_ = s_ipsc->rNumericArgs(1);
4526 13 : ratedCapacity_ = s_ipsc->rNumericArgs(2);
4527 : // unused phase_ = s_ipsc->rNumericArgs(3);
4528 :
4529 13 : if (Util::SameString(s_ipsc->cAlphaArgs(5), "Copper")) {
4530 0 : factorTempCoeff_ = 234.5;
4531 13 : } else if (Util::SameString(s_ipsc->cAlphaArgs(5), "Aluminum")) {
4532 13 : factorTempCoeff_ = 225.0;
4533 : } else {
4534 0 : ShowSevereInvalidKey(state, eoh, s_ipsc->cAlphaFieldNames(5), s_ipsc->cAlphaArgs(5));
4535 0 : errorsFound = true;
4536 : }
4537 13 : tempRise_ = s_ipsc->rNumericArgs(4);
4538 13 : eddyFrac_ = s_ipsc->rNumericArgs(5);
4539 :
4540 13 : performanceInputMode_ = static_cast<TransformerPerformanceInput>(getEnumValue(transformerPerformanceInputNamesUC, s_ipsc->cAlphaArgs(6)));
4541 13 : if (performanceInputMode_ == TransformerPerformanceInput::Invalid) {
4542 0 : ShowSevereInvalidKey(state, eoh, s_ipsc->cAlphaFieldNames(6), s_ipsc->cAlphaArgs(6));
4543 0 : errorsFound = true;
4544 : }
4545 :
4546 13 : if (ratedCapacity_ == 0) {
4547 0 : if (performanceInputMode_ == TransformerPerformanceInput::LossesMethod) {
4548 0 : ShowWarningError(state, format("{}{}=\"{}\".", routineName, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
4549 0 : ShowContinueError(state, format("Specified {} = {}", s_ipsc->cAlphaFieldNames(6), s_ipsc->cAlphaArgs(6)));
4550 0 : ShowContinueError(state, format("Specified {} = {:.1R}", s_ipsc->cNumericFieldNames(2), ratedCapacity_));
4551 0 : ShowContinueError(state, "Transformer load and no load losses cannot be calculated with 0.0 rated capacity.");
4552 0 : ShowContinueError(state, "Simulation continues but transformer losses will be set to zero.");
4553 : }
4554 : }
4555 13 : ratedNL_ = s_ipsc->rNumericArgs(6);
4556 13 : ratedLL_ = s_ipsc->rNumericArgs(7);
4557 13 : ratedEfficiency_ = s_ipsc->rNumericArgs(8);
4558 13 : ratedPUL_ = s_ipsc->rNumericArgs(9);
4559 13 : ratedTemp_ = s_ipsc->rNumericArgs(10);
4560 13 : maxPUL_ = s_ipsc->rNumericArgs(11);
4561 : // Check the input for MaxPUL if the performance input method is EfficiencyMethod
4562 13 : if (performanceInputMode_ == TransformerPerformanceInput::EfficiencyMethod) {
4563 12 : if (s_ipsc->lNumericFieldBlanks(11)) {
4564 12 : maxPUL_ = ratedPUL_;
4565 0 : } else if (maxPUL_ <= 0 || maxPUL_ > 1) {
4566 0 : ShowSevereError(state, format("{}{}=\"{}\", invalid entry.", routineName, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
4567 0 : ShowContinueError(state, format("Invalid {}=[{:.3R}].", s_ipsc->cNumericFieldNames(11), s_ipsc->rNumericArgs(11)));
4568 0 : ShowContinueError(state, "Entered value must be > 0 and <= 1.");
4569 0 : errorsFound = true;
4570 : }
4571 : }
4572 :
4573 13 : BooleanSwitch bs = getYesNoValue(s_ipsc->cAlphaArgs(7));
4574 13 : if (bs != BooleanSwitch::Invalid) {
4575 13 : considerLosses_ = static_cast<bool>(bs);
4576 0 : } else if (usageMode_ == TransformerUse::PowerInFromGrid) {
4577 0 : ShowSevereInvalidBool(state, eoh, s_ipsc->cAlphaFieldNames(7), s_ipsc->cAlphaArgs(7));
4578 0 : errorsFound = true;
4579 : }
4580 :
4581 13 : int numAlphaBeforeMeter = 7;
4582 13 : int numWiredMeters = numAlphas - numAlphaBeforeMeter;
4583 :
4584 13 : if (usageMode_ == TransformerUse::PowerInFromGrid) {
4585 :
4586 : // Provide warning if no meter is wired to a transformer used to get power from the grid
4587 12 : if (numWiredMeters <= 0) {
4588 0 : ShowWarningError(state, format("{}ElectricLoadCenter:Transformer=\"{}\":", routineName, name_));
4589 0 : ShowContinueError(state, "ISOLATED Transformer: No meter wired to a transformer used to input power from grid");
4590 : }
4591 :
4592 24 : wiredMeterNames_.resize(numWiredMeters, "");
4593 12 : wiredMeterPtrs_.resize(numWiredMeters, 0);
4594 12 : specialMeter_.resize(numWiredMeters, false);
4595 :
4596 : // Meter check deferred because they may have not been "loaded" yet,
4597 35 : for (int loopCount = 0; loopCount < numWiredMeters; ++loopCount) {
4598 23 : wiredMeterNames_[loopCount] = Util::makeUPPER(s_ipsc->cAlphaArgs(loopCount + numAlphaBeforeMeter + 1));
4599 : // Assign SpecialMeter as TRUE if the meter name is Electricity:Facility or Electricity:HVAC
4600 46 : if (Util::SameString(wiredMeterNames_[loopCount], "Electricity:Facility") ||
4601 46 : Util::SameString(wiredMeterNames_[loopCount], "Electricity:HVAC")) {
4602 0 : specialMeter_[loopCount] = true;
4603 : } else {
4604 23 : specialMeter_[loopCount] = false;
4605 : }
4606 : }
4607 : }
4608 26 : SetupOutputVariable(state,
4609 : "Transformer Efficiency",
4610 : Constant::Units::None,
4611 13 : efficiency_,
4612 : OutputProcessor::TimeStepType::System,
4613 : OutputProcessor::StoreType::Average,
4614 13 : name_);
4615 26 : SetupOutputVariable(state,
4616 : "Transformer Input Electricity Rate",
4617 : Constant::Units::W,
4618 13 : powerIn_,
4619 : OutputProcessor::TimeStepType::System,
4620 : OutputProcessor::StoreType::Average,
4621 13 : name_);
4622 26 : SetupOutputVariable(state,
4623 : "Transformer Input Electricity Energy",
4624 : Constant::Units::J,
4625 13 : energyIn_,
4626 : OutputProcessor::TimeStepType::System,
4627 : OutputProcessor::StoreType::Sum,
4628 13 : name_);
4629 26 : SetupOutputVariable(state,
4630 : "Transformer Output Electricity Rate",
4631 : Constant::Units::W,
4632 13 : powerOut_,
4633 : OutputProcessor::TimeStepType::System,
4634 : OutputProcessor::StoreType::Average,
4635 13 : name_);
4636 26 : SetupOutputVariable(state,
4637 : "Transformer Output Electricity Energy",
4638 : Constant::Units::J,
4639 13 : energyOut_,
4640 : OutputProcessor::TimeStepType::System,
4641 : OutputProcessor::StoreType::Sum,
4642 13 : name_);
4643 26 : SetupOutputVariable(state,
4644 : "Transformer No Load Loss Rate",
4645 : Constant::Units::W,
4646 13 : noLoadLossRate_,
4647 : OutputProcessor::TimeStepType::System,
4648 : OutputProcessor::StoreType::Average,
4649 13 : name_);
4650 26 : SetupOutputVariable(state,
4651 : "Transformer No Load Loss Energy",
4652 : Constant::Units::J,
4653 13 : noLoadLossEnergy_,
4654 : OutputProcessor::TimeStepType::System,
4655 : OutputProcessor::StoreType::Sum,
4656 13 : name_);
4657 26 : SetupOutputVariable(state,
4658 : "Transformer Load Loss Rate",
4659 : Constant::Units::W,
4660 13 : loadLossRate_,
4661 : OutputProcessor::TimeStepType::System,
4662 : OutputProcessor::StoreType::Average,
4663 13 : name_);
4664 26 : SetupOutputVariable(state,
4665 : "Transformer Load Loss Energy",
4666 : Constant::Units::J,
4667 13 : loadLossEnergy_,
4668 : OutputProcessor::TimeStepType::System,
4669 : OutputProcessor::StoreType::Sum,
4670 13 : name_);
4671 26 : SetupOutputVariable(state,
4672 : "Transformer Thermal Loss Rate",
4673 : Constant::Units::W,
4674 13 : thermalLossRate_,
4675 : OutputProcessor::TimeStepType::System,
4676 : OutputProcessor::StoreType::Average,
4677 13 : name_);
4678 26 : SetupOutputVariable(state,
4679 : "Transformer Thermal Loss Energy",
4680 : Constant::Units::J,
4681 13 : thermalLossEnergy_,
4682 : OutputProcessor::TimeStepType::System,
4683 : OutputProcessor::StoreType::Sum,
4684 13 : name_);
4685 13 : if (usageMode_ == TransformerUse::PowerInFromGrid) { // power losses metered as an end use exterior equipment
4686 24 : SetupOutputVariable(state,
4687 : "Transformer Distribution Electricity Loss Energy",
4688 : Constant::Units::J,
4689 12 : elecUseMeteredUtilityLosses_,
4690 : OutputProcessor::TimeStepType::System,
4691 : OutputProcessor::StoreType::Sum,
4692 12 : name_,
4693 : Constant::eResource::Electricity,
4694 : OutputProcessor::Group::HVAC, // Is this correct?
4695 : OutputProcessor::EndUseCat::ExteriorEquipment,
4696 : "Transformer");
4697 : }
4698 13 : if (usageMode_ == TransformerUse::PowerOutFromBldgToGrid) {
4699 2 : SetupOutputVariable(state,
4700 : "Transformer Cogeneration Electricity Loss Energy",
4701 : Constant::Units::J,
4702 1 : powerConversionMeteredLosses_,
4703 : OutputProcessor::TimeStepType::System,
4704 : OutputProcessor::StoreType::Sum,
4705 1 : name_,
4706 : Constant::eResource::ElectricityProduced,
4707 : OutputProcessor::Group::HVAC, // Is this correct?
4708 : OutputProcessor::EndUseCat::PowerConversion);
4709 : }
4710 13 : if (usageMode_ == TransformerUse::PowerBetweenLoadCenterAndBldg) {
4711 0 : SetupOutputVariable(state,
4712 : "Transformer Conversion Electricity Loss Energy",
4713 : Constant::Units::J,
4714 0 : powerConversionMeteredLosses_,
4715 : OutputProcessor::TimeStepType::System,
4716 : OutputProcessor::StoreType::Sum,
4717 0 : name_,
4718 : Constant::eResource::ElectricityProduced,
4719 : OutputProcessor::Group::HVAC, // Is this correct?
4720 : OutputProcessor::EndUseCat::PowerConversion);
4721 : }
4722 :
4723 13 : if (zoneNum_ > 0) {
4724 0 : SetupZoneInternalGain(
4725 : state, zoneNum_, name_, DataHeatBalance::IntGainType::ElectricLoadCenterTransformer, &qdotConvZone_, nullptr, &qdotRadZone_);
4726 : }
4727 :
4728 : } else {
4729 0 : ShowSevereError(state, format("{} did not find transformer name = {}", routineName, objectName));
4730 0 : errorsFound = true;
4731 : }
4732 :
4733 13 : if (errorsFound) {
4734 0 : ShowFatalError(state, format("{}Preceding errors terminate program.", routineName));
4735 : }
4736 13 : }
4737 :
4738 0 : Real64 ElectricTransformer::getLossRateForOutputPower(EnergyPlusData &state, Real64 const powerOutOfTransformer)
4739 : {
4740 0 : manageTransformers(state, powerOutOfTransformer);
4741 0 : return totalLossRate_;
4742 : }
4743 :
4744 0 : Real64 ElectricTransformer::getLossRateForInputPower(EnergyPlusData &state, Real64 const powerIntoTransformer)
4745 : {
4746 0 : manageTransformers(state, powerIntoTransformer);
4747 0 : return totalLossRate_;
4748 : }
4749 :
4750 146628 : void ElectricTransformer::manageTransformers(EnergyPlusData &state, Real64 const surplusPowerOutFromLoadCenters)
4751 : {
4752 146628 : Real64 constexpr ambTempRef = 20.0; // reference ambient temperature (C)
4753 146628 : if (myOneTimeFlag_) {
4754 : // calculate rated no load losses and rated load losses if the performance input method is based on
4755 : // nominal efficiency. This calculation is done only once
4756 :
4757 13 : if (performanceInputMode_ == TransformerPerformanceInput::EfficiencyMethod) {
4758 :
4759 12 : Real64 resRef = factorTempCoeff_ + tempRise_ + ambTempRef;
4760 12 : Real64 resSpecified = factorTempCoeff_ + ratedTemp_;
4761 12 : Real64 resRatio = resSpecified / resRef;
4762 12 : Real64 factorTempCorr = (1.0 - eddyFrac_) * resRatio + eddyFrac_ * (1.0 / resRatio);
4763 12 : Real64 numerator = ratedCapacity_ * ratedPUL_ * (1.0 - ratedEfficiency_);
4764 12 : Real64 denominator = ratedEfficiency_ * (1.0 + pow_2(ratedPUL_ / maxPUL_));
4765 :
4766 12 : ratedNL_ = numerator / denominator;
4767 12 : ratedLL_ = ratedNL_ / (factorTempCorr * pow_2(maxPUL_));
4768 : }
4769 13 : myOneTimeFlag_ = false;
4770 : }
4771 :
4772 146628 : Real64 elecLoad = 0.0; // transformer load which may be power in or out depending on the usage mode
4773 146628 : Real64 pastElecLoad = 0.0; // transformer load at the previous timestep
4774 146628 : switch (usageMode_) {
4775 138120 : case TransformerUse::PowerInFromGrid: {
4776 404385 : for (std::size_t meterNum = 0; meterNum < wiredMeterPtrs_.size(); ++meterNum) {
4777 :
4778 266265 : if (state.dataGlobal->MetersHaveBeenInitialized) {
4779 :
4780 203950 : elecLoad +=
4781 203950 : GetInstantMeterValue(state, wiredMeterPtrs_[meterNum], OutputProcessor::TimeStepType::Zone) / state.dataGlobal->TimeStepZoneSec +
4782 203950 : GetInstantMeterValue(state, wiredMeterPtrs_[meterNum], OutputProcessor::TimeStepType::System) /
4783 203950 : (state.dataHVACGlobal->TimeStepSysSec);
4784 : // PastElecLoad store the metered value in the previous time step. This value will be used to check whether
4785 : // a transformer is overloaded or not.
4786 203950 : pastElecLoad += GetCurrentMeterValue(state, wiredMeterPtrs_[meterNum]) / state.dataGlobal->TimeStepZoneSec;
4787 : } else {
4788 62315 : elecLoad = 0.0;
4789 62315 : pastElecLoad = 0.0;
4790 : }
4791 :
4792 : // Because transformer loss has been accounted for by Electricity:Facility and Electricity:HVAC, the transformer
4793 : // loss needs to be deducted from the metered value. Otherwise, double counting (circular relationship) occurs.
4794 266265 : if (specialMeter_[meterNum]) {
4795 0 : elecLoad = elecLoad - loadLossRate_ - noLoadLossRate_;
4796 :
4797 0 : if (elecLoad < 0) {
4798 0 : elecLoad = 0.0; // Essential check.
4799 : }
4800 : }
4801 : }
4802 :
4803 138120 : powerOut_ = elecLoad; // the metered value is transformer's output in PowerInFromGrid mode
4804 138120 : break;
4805 : }
4806 8508 : case TransformerUse::PowerOutFromBldgToGrid: {
4807 8508 : powerIn_ = surplusPowerOutFromLoadCenters;
4808 8508 : elecLoad = surplusPowerOutFromLoadCenters; // TODO this is input but should be output with the losses, but we don't have them yet.
4809 8508 : break;
4810 : }
4811 0 : case TransformerUse::PowerBetweenLoadCenterAndBldg: {
4812 : // TODO, new configuration for transformer, really part of the specific load center and connects it to the main building bus
4813 0 : powerIn_ = surplusPowerOutFromLoadCenters;
4814 0 : elecLoad = surplusPowerOutFromLoadCenters;
4815 0 : break;
4816 : }
4817 0 : case TransformerUse::Invalid: {
4818 : // do nothing
4819 0 : break;
4820 : }
4821 0 : default:
4822 0 : assert(false);
4823 : } // switch usage mode
4824 :
4825 : // check availability schedule
4826 146628 : if (ratedCapacity_ > 0.0 && availSched_->getCurrentVal() > 0.0) {
4827 :
4828 146628 : Real64 pUL = elecLoad / ratedCapacity_;
4829 :
4830 146628 : if (pUL > 1.0) {
4831 0 : pUL = 1.0;
4832 : }
4833 :
4834 : // Originally, PUL was used to check whether a transformer is overloaded (PUL > 1.0 or not). However, it was
4835 : // found that ElecLoad obtained from GetInstantMeterVlaue() might refer to intermideiate values before
4836 : // convergence. The intermediate values may issue false warning. This the reason why PastElecLoad obtained
4837 : // by GetCurrentMeterValue() is used here to check overload issue.
4838 146628 : if ((pastElecLoad / ratedCapacity_) > 1.0) {
4839 0 : if (overloadErrorIndex_ == 0) {
4840 0 : ShowSevereError(state, "Transformer Overloaded");
4841 0 : ShowContinueError(state, format("Entered in ElectricLoadCenter:Transformer ={}", name_));
4842 : }
4843 0 : ShowRecurringSevereErrorAtEnd(state, "Transformer Overloaded: Entered in ElectricLoadCenter:Transformer =" + name_, overloadErrorIndex_);
4844 : }
4845 :
4846 146628 : Real64 tempChange = std::pow(pUL, 1.6) * tempRise_;
4847 146628 : Real64 ambTemp = 20.0;
4848 146628 : if (heatLossesDestination_ == ThermalLossDestination::ZoneGains) {
4849 :
4850 0 : ambTemp = state.dataZoneTempPredictorCorrector->zoneHeatBalance(zoneNum_).MAT;
4851 : } else {
4852 146628 : ambTemp = 20.0;
4853 : }
4854 :
4855 146628 : Real64 resRef = factorTempCoeff_ + tempRise_ + ambTempRef;
4856 146628 : Real64 resSpecified = factorTempCoeff_ + tempChange + ambTemp;
4857 146628 : Real64 resRatio = resSpecified / resRef;
4858 146628 : Real64 factorTempCorr = (1.0 - eddyFrac_) * resRatio + eddyFrac_ * (1.0 / resRatio);
4859 :
4860 146628 : loadLossRate_ = ratedLL_ * pow_2(pUL) * factorTempCorr;
4861 146628 : noLoadLossRate_ = ratedNL_;
4862 : } else { // Transformer is not available.
4863 0 : loadLossRate_ = 0.0;
4864 0 : noLoadLossRate_ = 0.0;
4865 : }
4866 :
4867 146628 : totalLossRate_ = loadLossRate_ + noLoadLossRate_;
4868 :
4869 146628 : switch (usageMode_) {
4870 138120 : case TransformerUse::PowerInFromGrid: {
4871 138120 : powerIn_ = elecLoad + totalLossRate_;
4872 :
4873 : // Transformer losses are wired to the meter via the variable "%ElecUseUtility" only if transformer losses
4874 : // are considered in utility cost. If transformer losses are not considered in utility cost, 0 is assigned
4875 : // to the variable "%ElecUseUtility".
4876 138120 : if (considerLosses_) {
4877 138120 : elecUseMeteredUtilityLosses_ = totalLossRate_ * state.dataHVACGlobal->TimeStepSysSec;
4878 : } else {
4879 0 : elecUseMeteredUtilityLosses_ = 0.0;
4880 : }
4881 :
4882 : // Transformer has two modes.If it works in one mode, the variable for meter output in the other mode
4883 : // is assigned 0
4884 : // unused totalLossEnergy_ = totalLossRate_ * state.dataHVACGlobal->TimeStepSysSec;
4885 :
4886 138120 : break;
4887 : }
4888 :
4889 8508 : case TransformerUse::PowerOutFromBldgToGrid:
4890 : case TransformerUse::PowerBetweenLoadCenterAndBldg: {
4891 8508 : powerOut_ = elecLoad - totalLossRate_;
4892 :
4893 8508 : if (powerOut_ < 0) {
4894 6531 : powerOut_ = 0.0;
4895 : }
4896 :
4897 8508 : powerConversionMeteredLosses_ = -1.0 * totalLossRate_ * state.dataHVACGlobal->TimeStepSysSec;
4898 :
4899 : // Transformer has two modes.If it works in one mode, the variable for meter output in the other mode
4900 : // is assigned 0
4901 8508 : elecUseMeteredUtilityLosses_ = 0.0;
4902 8508 : break;
4903 : }
4904 :
4905 0 : case TransformerUse::Invalid: {
4906 : // do nothing
4907 0 : assert(false);
4908 : }
4909 0 : default:
4910 0 : assert(false);
4911 : } // switch
4912 :
4913 146628 : if (powerIn_ <= 0) {
4914 6531 : efficiency_ = 1.0; // Set to something reasonable to avoid a divide by zero error
4915 : } else {
4916 140097 : efficiency_ = powerOut_ / powerIn_;
4917 : }
4918 146628 : noLoadLossEnergy_ = noLoadLossRate_ * state.dataHVACGlobal->TimeStepSysSec;
4919 146628 : loadLossEnergy_ = loadLossRate_ * state.dataHVACGlobal->TimeStepSysSec;
4920 :
4921 146628 : energyIn_ = powerIn_ * state.dataHVACGlobal->TimeStepSysSec;
4922 146628 : energyOut_ = powerOut_ * state.dataHVACGlobal->TimeStepSysSec;
4923 :
4924 : // Thermal loss rate may not be equal to Total loss rate. This is the case when surplus power is less than the
4925 : // calculated total loss rate for a cogeneration transformer. That is why "PowerIn - PowerOut" is used below.
4926 146628 : thermalLossRate_ = powerIn_ - powerOut_;
4927 146628 : thermalLossEnergy_ = thermalLossRate_ * state.dataHVACGlobal->TimeStepSysSec;
4928 :
4929 146628 : if (zoneNum_ > 0) { // set values for zone heat gains
4930 0 : qdotConvZone_ = (1.0 - zoneRadFrac_) * thermalLossRate_;
4931 0 : qdotRadZone_ = (zoneRadFrac_)*thermalLossRate_;
4932 : }
4933 146628 : }
4934 :
4935 12 : void ElectricTransformer::setupMeterIndices(EnergyPlusData &state)
4936 : {
4937 12 : if (usageMode_ == TransformerUse::PowerInFromGrid) {
4938 35 : for (std::size_t meterNum = 0; meterNum < wiredMeterNames_.size(); ++meterNum) {
4939 :
4940 23 : wiredMeterPtrs_[meterNum] = GetMeterIndex(state, wiredMeterNames_[meterNum]);
4941 :
4942 : // Check whether the meter is an electricity meter
4943 : // Index function is used here because some resource types are not Electricity but strings containing
4944 : // Electricity such as ElectricityPurchased and ElectricityProduced.
4945 : // It is not proper to have this check in GetInput routine because the meter index may have not been defined
4946 23 : auto *meter = state.dataOutputProcessor->meters[wiredMeterPtrs_[meterNum]];
4947 23 : if (meter->resource != Constant::eResource::Electricity && meter->resource != Constant::eResource::ElectricityPurchased &&
4948 0 : meter->resource != Constant::eResource::ElectricitySurplusSold && meter->resource != Constant::eResource::ElectricityProduced &&
4949 0 : meter->resource != Constant::eResource::ElectricityNet) {
4950 0 : ShowFatalError(state, format("Non-electricity meter used for {}", name_));
4951 : }
4952 : }
4953 : }
4954 12 : }
4955 :
4956 135 : void ElectricTransformer::reinitAtBeginEnvironment()
4957 : {
4958 135 : efficiency_ = 0.0;
4959 135 : powerIn_ = 0.0;
4960 135 : energyIn_ = 0.0;
4961 135 : powerOut_ = 0.0;
4962 135 : energyOut_ = 0.0;
4963 135 : noLoadLossRate_ = 0.0;
4964 135 : noLoadLossEnergy_ = 0.0;
4965 135 : loadLossRate_ = 0.0;
4966 135 : loadLossEnergy_ = 0.0;
4967 135 : thermalLossRate_ = 0.0;
4968 135 : thermalLossEnergy_ = 0.0;
4969 135 : elecUseMeteredUtilityLosses_ = 0.0;
4970 135 : powerConversionMeteredLosses_ = 0.0;
4971 135 : qdotConvZone_ = 0.0;
4972 135 : qdotRadZone_ = 0.0;
4973 135 : }
4974 :
4975 122 : void ElectricTransformer::reinitZoneGainsAtBeginEnvironment()
4976 : {
4977 122 : qdotConvZone_ = 0.0;
4978 122 : qdotRadZone_ = 0.0;
4979 122 : }
4980 :
4981 0 : std::string const &ElectricTransformer::name() const
4982 : {
4983 0 : return name_;
4984 : }
4985 :
4986 : } // namespace EnergyPlus
|