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