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