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