Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : // C++ headers
49 : #include <algorithm>
50 : #include <string>
51 : #include <utility>
52 : #include <vector>
53 :
54 : // EnergyPlus headers
55 : #include <EnergyPlus/Autosizing/Base.hh>
56 : #include <EnergyPlus/BranchNodeConnections.hh>
57 : #include <EnergyPlus/CurveManager.hh>
58 : #include <EnergyPlus/Data/EnergyPlusData.hh>
59 : #include <EnergyPlus/DataEnvironment.hh>
60 : #include <EnergyPlus/DataHVACGlobals.hh>
61 : #include <EnergyPlus/DataIPShortCuts.hh>
62 : #include <EnergyPlus/DataLoopNode.hh>
63 : #include <EnergyPlus/DataPrecisionGlobals.hh>
64 : #include <EnergyPlus/DataSizing.hh>
65 : #include <EnergyPlus/FluidProperties.hh>
66 : #include <EnergyPlus/General.hh>
67 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
68 : #include <EnergyPlus/NodeInputManager.hh>
69 : #include <EnergyPlus/OutAirNodeManager.hh>
70 : #include <EnergyPlus/OutputProcessor.hh>
71 : #include <EnergyPlus/OutputReportPredefined.hh>
72 : #include <EnergyPlus/Plant/DataPlant.hh>
73 : #include <EnergyPlus/PlantComponent.hh>
74 : #include <EnergyPlus/PlantLoopHeatPumpEIR.hh>
75 : #include <EnergyPlus/PlantUtilities.hh>
76 : #include <EnergyPlus/Psychrometrics.hh>
77 : #include <EnergyPlus/UtilityRoutines.hh>
78 :
79 : namespace EnergyPlus::EIRPlantLoopHeatPumps {
80 :
81 : constexpr Real64 Fahrenheit2Celsius(Real64 F)
82 : {
83 : return (F - 32.0) * 5.0 / 9.0;
84 : }
85 :
86 41 : void EIRPlantLoopHeatPump::simulate(
87 : EnergyPlusData &state, const EnergyPlus::PlantLocation &calledFromLocation, bool const FirstHVACIteration, Real64 &CurLoad, bool const RunFlag)
88 : {
89 :
90 : // Call initialize to set flow rates, run flag, and entering temperatures
91 41 : this->running = RunFlag;
92 :
93 41 : this->loadSideInletTemp = state.dataLoopNodes->Node(this->loadSideNodes.inlet).Temp;
94 41 : this->sourceSideInletTemp = state.dataLoopNodes->Node(this->sourceSideNodes.inlet).Temp;
95 41 : if (this->heatRecoveryAvailable) {
96 6 : this->heatRecoveryInletTemp = state.dataLoopNodes->Node(this->heatRecoveryNodes.inlet).Temp;
97 : }
98 :
99 41 : if (this->waterSource) {
100 11 : this->setOperatingFlowRatesWSHP(state, FirstHVACIteration);
101 11 : if (calledFromLocation.loopNum == this->sourceSidePlantLoc.loopNum) { // condenser side
102 2 : Real64 sourceQdotArg = 0.0; // pass negative if heat pump heating
103 2 : if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating) {
104 0 : sourceQdotArg = this->sourceSideHeatTransfer * DataPrecisionGlobals::constant_minusone;
105 : } else {
106 2 : sourceQdotArg = this->sourceSideHeatTransfer;
107 : }
108 2 : PlantUtilities::UpdateChillerComponentCondenserSide(state,
109 : this->sourceSidePlantLoc.loopNum,
110 : this->sourceSidePlantLoc.loopSideNum,
111 : this->EIRHPType,
112 : this->sourceSideNodes.inlet,
113 : this->sourceSideNodes.outlet,
114 : this->sourceSideHeatTransfer,
115 : this->sourceSideInletTemp,
116 : this->sourceSideOutletTemp,
117 : this->sourceSideMassFlowRate,
118 : FirstHVACIteration);
119 2 : return;
120 : }
121 30 : } else if (this->airSource) {
122 30 : this->setHeatRecoveryOperatingStatusASHP(state, FirstHVACIteration);
123 30 : this->setOperatingFlowRatesASHP(state, FirstHVACIteration);
124 :
125 30 : if (calledFromLocation.loopNum == this->heatRecoveryPlantLoc.loopNum) {
126 0 : if (this->heatRecoveryAvailable) {
127 0 : PlantUtilities::UpdateChillerComponentCondenserSide(state,
128 : this->heatRecoveryPlantLoc.loopNum,
129 : this->heatRecoveryPlantLoc.loopSideNum,
130 : this->EIRHPType,
131 : this->heatRecoveryNodes.inlet,
132 : this->heatRecoveryNodes.outlet,
133 : this->heatRecoveryRate,
134 : this->heatRecoveryInletTemp,
135 : this->heatRecoveryOutletTemp,
136 : this->heatRecoveryMassFlowRate,
137 : FirstHVACIteration);
138 : }
139 : }
140 : }
141 :
142 39 : if (this->running) {
143 32 : if (this->sysControlType == ControlType::Setpoint) {
144 4 : Real64 leavingSetpoint = state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPoint;
145 4 : Real64 CurSpecHeat = this->loadSidePlantLoc.loop->glycol->getSpecificHeat(state, loadSideInletTemp, "EIRPlantLoopHeatPump::simulate");
146 4 : Real64 controlLoad = this->loadSideMassFlowRate * CurSpecHeat * (leavingSetpoint - loadSideInletTemp);
147 :
148 4 : this->doPhysics(state, controlLoad);
149 : } else {
150 28 : this->doPhysics(state, CurLoad);
151 : }
152 : } else {
153 7 : this->resetReportingVariables();
154 : }
155 :
156 : // update report variables and nodes
157 39 : this->report(state);
158 : }
159 :
160 25 : Real64 EIRPlantLoopHeatPump::getLoadSideOutletSetPointTemp(EnergyPlusData &state) const
161 : {
162 25 : if (this->loadSidePlantLoc.loop->LoopDemandCalcScheme == DataPlant::LoopDemandCalcScheme::SingleSetPoint) {
163 19 : if (this->loadSidePlantLoc.comp->CurOpSchemeType == DataPlant::OpScheme::CompSetPtBased) {
164 : // there will be a valid set-point on outlet
165 15 : return state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPoint;
166 : } else { // use plant loop overall set-point
167 4 : return state.dataLoopNodes->Node(this->loadSidePlantLoc.loop->TempSetPointNodeNum).TempSetPoint;
168 : }
169 6 : } else if (this->loadSidePlantLoc.loop->LoopDemandCalcScheme == DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand) {
170 6 : if (this->loadSidePlantLoc.comp->CurOpSchemeType == DataPlant::OpScheme::CompSetPtBased) {
171 : // there will be a valid set-point on outlet
172 2 : if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRCooling) {
173 1 : return state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPointHi;
174 : } else {
175 1 : return state.dataLoopNodes->Node(this->loadSideNodes.outlet).TempSetPointLo;
176 : }
177 : } else { // use plant loop overall set-point
178 4 : if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRCooling) {
179 3 : return state.dataLoopNodes->Node(this->loadSidePlantLoc.loop->TempSetPointNodeNum).TempSetPointHi;
180 : } else {
181 1 : return state.dataLoopNodes->Node(this->loadSidePlantLoc.loop->TempSetPointNodeNum).TempSetPointLo;
182 : }
183 : }
184 : } else {
185 : // there's no other enums for loop demand calcs, so I don't have a reasonable unit test for these
186 : // lines, they simply should not be able to get here. But a fatal is here anyway just in case,
187 : // and the lines are excluded from coverage.
188 : ShowFatalError(state, "Unsupported loop demand calculation scheme in EIR heat pump"); // LCOV_EXCL_LINE
189 : return -999; // not actually returned with Fatal Error call above // LCOV_EXCL_LINE
190 : }
191 : }
192 :
193 8 : void EIRPlantLoopHeatPump::resetReportingVariables()
194 : {
195 8 : this->loadSideHeatTransfer = 0.0;
196 8 : this->loadSideEnergy = 0.0;
197 8 : this->loadSideOutletTemp = this->loadSideInletTemp;
198 8 : this->powerUsage = 0.0;
199 8 : this->powerEnergy = 0.0;
200 8 : this->sourceSideHeatTransfer = 0.0;
201 8 : this->sourceSideOutletTemp = this->sourceSideInletTemp;
202 8 : this->sourceSideEnergy = 0.0;
203 8 : this->defrostEnergyRate = 0.0;
204 8 : this->defrostEnergy = 0.0;
205 8 : this->loadDueToDefrost = 0.0;
206 8 : this->fractionalDefrostTime = 0.0;
207 8 : this->partLoadRatio = 0.0;
208 8 : this->cyclingRatio = 0.0;
209 8 : this->defrostPowerMultiplier = 1.0;
210 8 : this->heatRecoveryRate = 0.0;
211 8 : this->heatRecoveryEnergy = 0.0;
212 8 : this->heatRecoveryMassFlowRate = 0.0;
213 8 : this->heatRecoveryOutletTemp = this->heatRecoveryInletTemp;
214 8 : this->heatRecoveryIsActive = false;
215 8 : this->heatRecoveryOperatingStatus = 0;
216 8 : this->thermosiphonStatus = 0;
217 8 : }
218 :
219 18 : void EIRPlantLoopHeatPump::setOperatingFlowRatesWSHP(EnergyPlusData &state, bool FirstHVACIteration)
220 : {
221 18 : if (!this->running) {
222 7 : this->loadSideMassFlowRate = 0.0;
223 7 : this->sourceSideMassFlowRate = 0.0;
224 :
225 7 : PlantUtilities::SetComponentFlowRate(
226 7 : state, this->loadSideMassFlowRate, this->loadSideNodes.inlet, this->loadSideNodes.outlet, this->loadSidePlantLoc);
227 7 : PlantUtilities::SetComponentFlowRate(
228 7 : state, this->sourceSideMassFlowRate, this->sourceSideNodes.inlet, this->sourceSideNodes.outlet, this->sourceSidePlantLoc);
229 7 : PlantUtilities::PullCompInterconnectTrigger(state,
230 7 : this->loadSidePlantLoc,
231 7 : this->condMassFlowRateTriggerIndex,
232 7 : this->sourceSidePlantLoc,
233 : DataPlant::CriteriaType::MassFlowRate,
234 : this->sourceSideMassFlowRate);
235 : // Set flows if the heat pump is running
236 : } else { // the heat pump must run
237 : // apply min/max operating limits based on source side entering fluid temperature
238 11 : if (this->minSourceTempLimit > this->sourceSideInletTemp || this->maxSourceTempLimit < this->sourceSideInletTemp) {
239 0 : this->loadSideMassFlowRate = (this->heatRecoveryHeatPump) ? state.dataLoopNodes->Node(this->loadSideNodes.inlet).MassFlowRate : 0.0;
240 0 : this->sourceSideMassFlowRate = (this->heatRecoveryHeatPump) ? state.dataLoopNodes->Node(this->sourceSideNodes.inlet).MassFlowRate : 0.0;
241 0 : this->running = false;
242 : } else {
243 11 : this->loadSideMassFlowRate =
244 11 : (this->heatRecoveryHeatPump) ? state.dataLoopNodes->Node(this->loadSideNodes.inlet).MassFlowRate : this->loadSideDesignMassFlowRate;
245 11 : this->sourceSideMassFlowRate = (this->heatRecoveryHeatPump) ? state.dataLoopNodes->Node(this->sourceSideNodes.inlet).MassFlowRate
246 : : this->sourceSideDesignMassFlowRate;
247 :
248 11 : if (!FirstHVACIteration && this->flowControl == DataPlant::FlowMode::VariableSpeedPump) {
249 0 : if ((this->loadVSBranchPump || this->loadVSLoopPump) && !this->heatRecoveryHeatPump) {
250 0 : this->loadSideMassFlowRate *= std::max(this->partLoadRatio, this->minimumPLR);
251 0 : if (this->loadVSBranchPump) {
252 0 : this->loadSideMassFlowRate = std::max(this->loadSideMassFlowRate, this->loadVSPumpMinLimitMassFlow);
253 : }
254 : }
255 0 : if ((this->sourceVSBranchPump || this->sourceVSLoopPump) && !this->heatRecoveryHeatPump) {
256 0 : this->sourceSideMassFlowRate *= std::max(this->partLoadRatio, this->minimumPLR);
257 0 : if (this->sourceVSBranchPump) {
258 0 : this->sourceSideMassFlowRate = std::max(this->sourceSideMassFlowRate, this->sourceVSPumpMinLimitMassFlow);
259 : }
260 : }
261 : }
262 :
263 11 : PlantUtilities::SetComponentFlowRate(
264 11 : state, this->loadSideMassFlowRate, this->loadSideNodes.inlet, this->loadSideNodes.outlet, this->loadSidePlantLoc);
265 11 : PlantUtilities::SetComponentFlowRate(
266 11 : state, this->sourceSideMassFlowRate, this->sourceSideNodes.inlet, this->sourceSideNodes.outlet, this->sourceSidePlantLoc);
267 : }
268 :
269 : // if there's no flow in one, try to turn the entire heat pump off
270 11 : if (this->loadSideMassFlowRate <= 0.0 || this->sourceSideMassFlowRate <= 0.0) {
271 3 : this->loadSideMassFlowRate = 0.0;
272 3 : this->sourceSideMassFlowRate = 0.0;
273 3 : this->running = false;
274 3 : PlantUtilities::SetComponentFlowRate(
275 3 : state, this->loadSideMassFlowRate, this->loadSideNodes.inlet, this->loadSideNodes.outlet, this->loadSidePlantLoc);
276 3 : PlantUtilities::SetComponentFlowRate(
277 3 : state, this->sourceSideMassFlowRate, this->sourceSideNodes.inlet, this->sourceSideNodes.outlet, this->sourceSidePlantLoc);
278 : }
279 11 : PlantUtilities::PullCompInterconnectTrigger(state,
280 11 : this->loadSidePlantLoc,
281 11 : this->condMassFlowRateTriggerIndex,
282 11 : this->sourceSidePlantLoc,
283 : DataPlant::CriteriaType::MassFlowRate,
284 : this->sourceSideMassFlowRate);
285 : }
286 18 : }
287 :
288 45 : void EIRPlantLoopHeatPump::setOperatingFlowRatesASHP(EnergyPlusData &state, bool FirstHVACIteration)
289 : {
290 45 : if (!this->running) {
291 11 : this->loadSideMassFlowRate = 0.0;
292 11 : this->sourceSideMassFlowRate = 0.0;
293 11 : PlantUtilities::SetComponentFlowRate(
294 11 : state, this->loadSideMassFlowRate, this->loadSideNodes.inlet, this->loadSideNodes.outlet, this->loadSidePlantLoc);
295 11 : if (this->heatRecoveryAvailable) {
296 : // set the HR flow to zero if the heat pump is off
297 2 : this->heatRecoveryMassFlowRate = 0.0;
298 2 : PlantUtilities::SetComponentFlowRate(
299 2 : state, this->heatRecoveryMassFlowRate, this->heatRecoveryNodes.inlet, this->heatRecoveryNodes.outlet, this->heatRecoveryPlantLoc);
300 : }
301 : // Set flows if the heat pump is running
302 : } else { // the heat pump must run
303 : // apply min/max operating limits based on source side entering fluid temperature
304 34 : if ((this->minSourceTempLimit > this->sourceSideInletTemp || this->maxSourceTempLimit < this->sourceSideInletTemp) &&
305 0 : !this->heatRecoveryIsActive) {
306 0 : this->loadSideMassFlowRate = 0.0;
307 0 : this->sourceSideMassFlowRate = 0.0;
308 0 : this->running = false;
309 0 : PlantUtilities::SetComponentFlowRate(
310 0 : state, this->loadSideMassFlowRate, this->loadSideNodes.inlet, this->loadSideNodes.outlet, this->loadSidePlantLoc);
311 0 : if (this->heatRecoveryAvailable) {
312 0 : this->heatRecoveryMassFlowRate = 0.0;
313 0 : PlantUtilities::SetComponentFlowRate(
314 0 : state, this->heatRecoveryMassFlowRate, this->heatRecoveryNodes.inlet, this->heatRecoveryNodes.outlet, this->heatRecoveryPlantLoc);
315 : }
316 : } else {
317 34 : this->loadSideMassFlowRate = this->loadSideDesignMassFlowRate;
318 34 : this->sourceSideMassFlowRate = this->sourceSideDesignMassFlowRate;
319 :
320 34 : if (!FirstHVACIteration && this->flowControl == DataPlant::FlowMode::VariableSpeedPump) {
321 0 : if (this->loadVSBranchPump || this->loadVSLoopPump) {
322 0 : this->loadSideMassFlowRate *= std::max(this->partLoadRatio, this->minimumPLR);
323 0 : if (this->loadVSBranchPump) {
324 0 : this->loadSideMassFlowRate = std::max(this->loadSideMassFlowRate, this->loadVSPumpMinLimitMassFlow);
325 : }
326 : }
327 : }
328 :
329 34 : PlantUtilities::SetComponentFlowRate(
330 34 : state, this->loadSideMassFlowRate, this->loadSideNodes.inlet, this->loadSideNodes.outlet, this->loadSidePlantLoc);
331 :
332 34 : if (this->heatRecoveryIsActive) {
333 4 : this->heatRecoveryMassFlowRate = this->heatRecoveryDesignMassFlowRate;
334 4 : PlantUtilities::SetComponentFlowRate(
335 4 : state, this->heatRecoveryMassFlowRate, this->heatRecoveryNodes.inlet, this->heatRecoveryNodes.outlet, this->heatRecoveryPlantLoc);
336 : }
337 : }
338 :
339 : // if there's no flow in one, try to turn the entire heat pump off
340 34 : if (this->loadSideMassFlowRate <= 0.0) {
341 4 : this->loadSideMassFlowRate = 0.0;
342 4 : this->sourceSideMassFlowRate = 0.0;
343 4 : this->running = false;
344 4 : PlantUtilities::SetComponentFlowRate(
345 4 : state, this->loadSideMassFlowRate, this->loadSideNodes.inlet, this->loadSideNodes.outlet, this->loadSidePlantLoc);
346 : // if heat recovery is connected to plant loop
347 4 : if (this->heatRecoveryAvailable) {
348 0 : this->heatRecoveryMassFlowRate = 0.0;
349 0 : PlantUtilities::SetComponentFlowRate(
350 0 : state, this->heatRecoveryMassFlowRate, this->heatRecoveryNodes.inlet, this->heatRecoveryNodes.outlet, this->heatRecoveryPlantLoc);
351 : }
352 : }
353 34 : if (this->heatRecoveryAvailable) {
354 4 : PlantUtilities::PullCompInterconnectTrigger(state,
355 4 : this->loadSidePlantLoc,
356 4 : this->condMassFlowRateTriggerIndex,
357 4 : this->heatRecoveryPlantLoc,
358 : DataPlant::CriteriaType::MassFlowRate,
359 : this->heatRecoveryMassFlowRate);
360 : }
361 : }
362 45 : }
363 :
364 22 : void EIRPlantLoopHeatPump::doPhysics(EnergyPlusData &state, Real64 currentLoad)
365 : {
366 : // ideally the plant is going to ensure that we don't have a runflag=true when the load is invalid, but
367 : // I'm not sure we can count on that so we will do one check here to make sure we don't calculate things badly
368 22 : if ((this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRCooling && currentLoad >= 0.0) ||
369 22 : (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating && currentLoad <= 0.0)) {
370 3 : this->resetReportingVariables();
371 3 : return;
372 : }
373 :
374 : // dispatch to specific physics calculations based on the heat pump type
375 19 : if (this->waterSource) {
376 8 : this->doPhysicsWSHP(state, currentLoad);
377 11 : } else if (this->airSource) {
378 11 : this->doPhysicsASHP(state, currentLoad);
379 : }
380 : }
381 :
382 8 : void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLoad)
383 : {
384 :
385 : // add free cooling at some point, compressor is off during free cooling, temp limits restrict free cooling range
386 :
387 8 : Real64 availableCapacity = this->referenceCapacity;
388 8 : Real64 partLoadRatio = 0.0;
389 :
390 8 : this->calcAvailableCapacity(state, currentLoad, availableCapacity, partLoadRatio);
391 8 : this->setPartLoadAndCyclingRatio(state, partLoadRatio);
392 :
393 : // evaluate the actual current operating load side heat transfer rate
394 8 : this->calcLoadSideHeatTransfer(state, availableCapacity);
395 :
396 : // no defrost calculation for WSHP
397 : // calculate power usage from EIR curves
398 8 : this->calcPowerUsage(state);
399 :
400 : // evaluate the source side heat transfer rate
401 8 : this->calcSourceSideHeatTransferWSHP(state);
402 8 : }
403 :
404 11 : void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLoad)
405 : {
406 : // add free cooling at some point, compressor is off during free cooling, temp limits restrict free cooling range
407 :
408 11 : Real64 availableCapacity = this->referenceCapacity;
409 11 : Real64 partLoadRatio = 0.0;
410 :
411 11 : this->calcAvailableCapacity(state, currentLoad, availableCapacity, partLoadRatio);
412 11 : this->setPartLoadAndCyclingRatio(state, partLoadRatio);
413 :
414 : // do defrost calculation if applicable
415 11 : this->doDefrost(state, availableCapacity);
416 :
417 : // evaluate the actual current operating load side heat transfer rate
418 11 : this->calcLoadSideHeatTransfer(state, availableCapacity);
419 :
420 : // calculate power usage from EIR curves
421 11 : this->calcPowerUsage(state);
422 :
423 11 : if (this->heatRecoveryIsActive) {
424 : // evaluate the heat recovery side heat transfer rate
425 4 : this->calcHeatRecoveryHeatTransferASHP(state);
426 : } else {
427 : // evaluate the source side heat transfer rate
428 7 : this->calcSourceSideHeatTransferASHP(state);
429 : }
430 11 : }
431 :
432 19 : void EIRPlantLoopHeatPump::calcAvailableCapacity(EnergyPlusData &state, Real64 const currentLoad, Real64 &availableCapacity, Real64 &partLoadRatio)
433 : {
434 : // get setpoint on the load side outlet
435 19 : Real64 loadSideOutletSetpointTemp = this->getLoadSideOutletSetPointTemp(state);
436 19 : Real64 originalLoadSideOutletSPTemp = loadSideOutletSetpointTemp;
437 :
438 : // add free cooling at some point, compressor is off during free cooling, temp limits restrict free cooling range
439 :
440 19 : Real64 capacityModifierFuncTemp = 1.0;
441 19 : bool waterTempExceeded = false;
442 :
443 : // evaluate capacity modifier curve and determine load side heat transfer
444 : // any adjustment to outlet water temp set point requires some form of iteration
445 19 : for (int loop = 0; loop < 2; ++loop) {
446 :
447 19 : if (this->heatRecoveryIsActive) {
448 4 : if (this->heatRecoveryCapFTempCurveIndex > 0) {
449 0 : capacityModifierFuncTemp =
450 0 : Curve::CurveValue(state, this->heatRecoveryCapFTempCurveIndex, loadSideOutletSetpointTemp, this->heatRecoveryInletTemp);
451 : } else {
452 4 : capacityModifierFuncTemp =
453 4 : Curve::CurveValue(state, this->capFuncTempCurveIndex, loadSideOutletSetpointTemp, this->heatRecoveryInletTemp);
454 : }
455 4 : availableCapacity = this->referenceCapacity * capacityModifierFuncTemp;
456 : } else {
457 15 : capacityModifierFuncTemp = Curve::CurveValue(state, this->capFuncTempCurveIndex, loadSideOutletSetpointTemp, this->sourceSideInletTemp);
458 15 : availableCapacity = this->referenceCapacity * capacityModifierFuncTemp;
459 : // apply air source HP dry air heating capacity correction
460 15 : availableCapacity *= heatingCapacityModifierASHP(state);
461 : }
462 :
463 19 : if (availableCapacity > 0) {
464 19 : partLoadRatio = std::clamp(std::abs(currentLoad) / availableCapacity, 0.0, 1.0);
465 : }
466 :
467 19 : if (this->minSupplyWaterTempCurveIndex > 0) {
468 0 : Real64 minWaterTemp = Curve::CurveValue(state, this->minSupplyWaterTempCurveIndex, state.dataEnvrn->OutDryBulbTemp);
469 0 : if (loadSideOutletSetpointTemp < minWaterTemp) {
470 0 : loadSideOutletSetpointTemp = originalLoadSideOutletSPTemp + (1.0 - partLoadRatio) * (minWaterTemp - originalLoadSideOutletSPTemp);
471 0 : waterTempExceeded = true;
472 : }
473 : }
474 19 : if (this->maxSupplyWaterTempCurveIndex > 0) {
475 0 : Real64 maxWaterTemp = Curve::CurveValue(state, this->maxSupplyWaterTempCurveIndex, state.dataEnvrn->OutDryBulbTemp);
476 0 : if (loadSideOutletSetpointTemp > maxWaterTemp) {
477 0 : loadSideOutletSetpointTemp = maxWaterTemp + (1.0 - partLoadRatio) * (originalLoadSideOutletSPTemp - maxWaterTemp);
478 0 : waterTempExceeded = true;
479 : }
480 : }
481 19 : if (this->heatRecoveryHeatPump) {
482 2 : this->calcLoadSideHeatTransfer(state, availableCapacity);
483 2 : this->calcPowerUsage(state);
484 2 : Real64 sourceSideHeatTransfer = this->calcQsource(availableCapacity * partLoadRatio, this->powerUsage);
485 : // check to see if souce side outlet temp exceeds limit and reduce PLR if necessary
486 2 : Real64 const CpSrc = this->sourceSidePlantLoc.loop->glycol->getSpecificHeat(
487 : state, this->sourceSideInletTemp, "EIRPlantLoopHeatPump::calcLoadSideHeatTransfer()");
488 2 : Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc;
489 2 : Real64 const tempSourceOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, sourceSideHeatTransfer / sourceMCp);
490 2 : if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating && tempSourceOutletTemp < this->minSourceTempLimit) {
491 0 : partLoadRatio *= (this->sourceSideInletTemp - this->minSourceTempLimit) / (this->sourceSideInletTemp - tempSourceOutletTemp);
492 2 : } else if (tempSourceOutletTemp > this->maxSourceTempLimit) {
493 1 : partLoadRatio *= (this->maxSourceTempLimit - this->sourceSideInletTemp) / (tempSourceOutletTemp - this->sourceSideInletTemp);
494 : }
495 : }
496 19 : if (!waterTempExceeded) {
497 19 : break;
498 : }
499 : }
500 :
501 : // check the curve values, reset to zero if negative
502 19 : if (this->heatRecoveryIsActive && this->heatRecoveryCapFTempCurveIndex > 0) {
503 0 : this->heatRecoveryCapModFTCurveCheck(state, loadSideOutletSetpointTemp, capacityModifierFuncTemp);
504 : } else {
505 19 : this->capModFTCurveCheck(state, loadSideOutletSetpointTemp, capacityModifierFuncTemp);
506 : }
507 19 : }
508 :
509 15 : Real64 EIRPlantLoopHeatPump::heatingCapacityModifierASHP(EnergyPlusData &state) const
510 : {
511 15 : Real64 constexpr RH90 = 90.0;
512 15 : Real64 constexpr RH60 = 60.0;
513 15 : Real64 constexpr rangeRH = 30.0;
514 :
515 : // apply heating mode dry outdoor (evaporator) coil correction factor for air-cooled equipment
516 15 : if (this->capacityDryAirCurveIndex > 0 && this->airSource && state.dataEnvrn->OutRelHum < RH90) { // above 90% RH yields full capacity
517 0 : Real64 dryCorrectionFactor = std::min(1.0, Curve::CurveValue(state, this->capacityDryAirCurveIndex, state.dataEnvrn->OutDryBulbTemp));
518 0 : if (state.dataEnvrn->OutRelHum <= RH60) {
519 : // dry heating capacity correction factor is a function of outdoor dry-bulb temperature
520 0 : return dryCorrectionFactor;
521 : } else {
522 : // interpolation of heating capacity between wet and dry is based on outdoor relative humidity over 60%-90% range
523 0 : Real64 semiDryFactor = dryCorrectionFactor + (1.0 - dryCorrectionFactor) * (1.0 - ((RH90 - state.dataEnvrn->OutRelHum) / rangeRH));
524 0 : return semiDryFactor;
525 : }
526 : } else {
527 : // no correction needed, use full capacity
528 15 : return 1.0;
529 : }
530 : }
531 :
532 19 : void EIRPlantLoopHeatPump::setPartLoadAndCyclingRatio([[maybe_unused]] EnergyPlusData &state, Real64 &partLoadRatio)
533 : {
534 : // Initialize cycling ratio to 1.0
535 19 : Real64 cyclingRatio = 1.0;
536 :
537 : // Check if part load ratio is below the minimum threshold
538 19 : if (partLoadRatio < this->minimumPLR) {
539 : // Adjust cycling ratio and set part load ratio to minimum
540 0 : cyclingRatio = partLoadRatio / this->minimumPLR;
541 0 : partLoadRatio = this->minimumPLR;
542 : }
543 :
544 : // update class member variables
545 19 : this->partLoadRatio = partLoadRatio;
546 19 : this->cyclingRatio = cyclingRatio;
547 19 : }
548 :
549 21 : void EIRPlantLoopHeatPump::calcLoadSideHeatTransfer(EnergyPlusData &state, Real64 const availableCapacity)
550 : {
551 : // evaluate the actual current operating load side heat transfer rate
552 21 : Real64 CpLoad = this->loadSidePlantLoc.loop->glycol->getSpecificHeat(
553 21 : state, state.dataLoopNodes->Node(this->loadSideNodes.inlet).Temp, "EIRPlantLoopHeatPump::calcLoadSideHeatTransfer()");
554 :
555 21 : Real64 const operatingPLR = this->partLoadRatio * this->cyclingRatio;
556 21 : this->loadSideHeatTransfer = availableCapacity * operatingPLR;
557 :
558 : // calculate load side outlet conditions
559 21 : Real64 const loadMCp = this->loadSideMassFlowRate * CpLoad;
560 21 : this->loadSideOutletTemp = this->calcLoadOutletTemp(this->loadSideInletTemp, this->loadSideHeatTransfer / loadMCp);
561 :
562 : // now what to do here if outlet water temp exceeds limit based on HW supply temp limit curves?
563 : // currentLoad will be met and there should? be some adjustment based on outlet water temp limit?
564 21 : }
565 :
566 21 : void EIRPlantLoopHeatPump::calcPowerUsage(EnergyPlusData &state)
567 : {
568 : // calculate power usage from EIR curves
569 21 : Real64 eirModifierFuncTemp = 0.0;
570 21 : if (this->airSource && this->heatRecoveryIsActive) {
571 4 : if (this->heatRecoveryEIRFTempCurveIndex > 0) {
572 0 : eirModifierFuncTemp =
573 0 : Curve::CurveValue(state, this->heatRecoveryEIRFTempCurveIndex, this->loadSideOutletTemp, this->heatRecoveryInletTemp);
574 : // check cap func of temp curve value and reset to zero if negative
575 0 : this->heatRecoveryEIRModCurveCheck(state, eirModifierFuncTemp);
576 : } else {
577 4 : eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp);
578 : // check cap func of temp curve value and reset to zero if negative
579 4 : this->eirModCurveCheck(state, eirModifierFuncTemp);
580 : }
581 : } else {
582 17 : eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp);
583 : // check curves value and resets to zero if negative
584 17 : this->eirModCurveCheck(state, eirModifierFuncTemp);
585 : }
586 21 : Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, this->partLoadRatio);
587 : // check EIR func of PLR curve value and resets to zero if negative
588 21 : this->eirModFPLRCurveCheck(state, eirModifierFuncPLR);
589 :
590 : // compute power usage
591 21 : if (this->thermosiphonDisabled(state)) {
592 20 : this->powerUsage = (this->loadSideHeatTransfer / this->referenceCOP) * eirModifierFuncPLR * eirModifierFuncTemp *
593 20 : this->defrostPowerMultiplier * this->cyclingRatio;
594 : }
595 21 : }
596 :
597 8 : void EIRPlantLoopHeatPump::calcSourceSideHeatTransferWSHP(EnergyPlusData &state)
598 : {
599 :
600 : // energy balance on heat pump
601 8 : this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage);
602 :
603 : // calculate source side outlet conditions
604 8 : Real64 const CpSrc = this->sourceSidePlantLoc.loop->glycol->getSpecificHeat(
605 : state, this->sourceSideInletTemp, "EIRPlantLoopHeatPump::calcSourceSideHeatTransferWSHP()");
606 8 : Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc;
607 8 : this->sourceSideOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, this->sourceSideHeatTransfer / sourceMCp);
608 :
609 8 : if (this->waterSource && abs(this->sourceSideOutletTemp - this->sourceSideInletTemp) > 100.0) { // whoaa out of range happenings on water loop
610 : //
611 : // TODO setup recurring error warning?
612 : // lets do something different than fatal the simulation
613 0 : if ((this->sourceSideMassFlowRate / this->sourceSideDesignMassFlowRate) < 0.01) { // current source side flow is 1% of design max
614 : // just send it all to skin losses and leave the fluid temperature alone
615 0 : this->sourceSideOutletTemp = this->sourceSideInletTemp;
616 0 : } else if (this->sourceSideOutletTemp > this->sourceSideInletTemp) {
617 0 : this->sourceSideOutletTemp = this->sourceSideInletTemp + 100.0; // cap it at 100C delta
618 :
619 0 : } else if (this->sourceSideOutletTemp < this->sourceSideInletTemp) {
620 0 : this->sourceSideOutletTemp = this->sourceSideInletTemp - 100.0; // cap it at 100C delta
621 : }
622 : }
623 8 : }
624 :
625 7 : void EIRPlantLoopHeatPump::calcSourceSideHeatTransferASHP(EnergyPlusData &state)
626 : {
627 : // energy balance on heat pump
628 7 : this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage);
629 :
630 : // calculate source side outlet conditions
631 7 : Real64 const CpSrc = Psychrometrics::PsyCpAirFnW(state.dataEnvrn->OutHumRat);
632 7 : Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc;
633 7 : this->sourceSideOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, this->sourceSideHeatTransfer / sourceMCp);
634 7 : if (this->heatRecoveryAvailable && !this->heatRecoveryIsActive) {
635 : // reset the HR report variables
636 0 : this->heatRecoveryRate = 0.0;
637 0 : this->heatRecoveryOutletTemp = this->heatRecoveryInletTemp;
638 : }
639 7 : }
640 :
641 4 : void EIRPlantLoopHeatPump::calcHeatRecoveryHeatTransferASHP(EnergyPlusData &state)
642 : {
643 : // energy balance on heat pump
644 4 : this->heatRecoveryRate = this->calcQheatRecovery(this->loadSideHeatTransfer, this->powerUsage);
645 4 : Real64 heatRecoverRateTot = this->heatRecoveryRate;
646 :
647 : // calculate heat recovery side outlet conditions
648 4 : Real64 const CpHR = this->heatRecoveryPlantLoc.loop->glycol->getSpecificHeat(
649 : state, this->heatRecoveryInletTemp, "EIRPlantLoopHeatPump::calcHeatRecoveryHeatTransferASHP()");
650 4 : Real64 const hRecoveryMCp = this->heatRecoveryMassFlowRate * CpHR;
651 4 : if (this->heatRecoveryMassFlowRate > 0.0) {
652 4 : this->heatRecoveryOutletTemp = this->calcHROutletTemp(this->heatRecoveryInletTemp, this->heatRecoveryRate / hRecoveryMCp);
653 : } else {
654 0 : this->heatRecoveryOutletTemp = this->heatRecoveryInletTemp;
655 0 : this->heatRecoveryRate = 0.0;
656 : }
657 : // limit the HR HW outlet temperature to the maximum allowed (HW Recovery)
658 4 : if (this->heatRecoveryOutletTemp > this->maxHeatRecoveryTempLimit) {
659 1 : if (this->heatRecoveryInletTemp < this->maxHeatRecoveryTempLimit) {
660 1 : this->heatRecoveryOutletTemp = this->maxHeatRecoveryTempLimit;
661 1 : this->heatRecoveryRate = hRecoveryMCp * (this->heatRecoveryOutletTemp - this->heatRecoveryInletTemp);
662 : } else {
663 0 : this->heatRecoveryRate = 0.0;
664 0 : this->heatRecoveryOutletTemp = this->heatRecoveryInletTemp;
665 : }
666 : }
667 : // limit the HR CW outlet temp to the minimum allowed (CW Recovery)
668 4 : if (this->heatRecoveryOutletTemp < this->minHeatRecoveryTempLimit) {
669 1 : if (this->heatRecoveryInletTemp > this->minHeatRecoveryTempLimit) {
670 1 : this->heatRecoveryOutletTemp = this->minHeatRecoveryTempLimit;
671 1 : this->heatRecoveryRate = hRecoveryMCp * (this->heatRecoveryInletTemp - this->heatRecoveryOutletTemp);
672 : } else {
673 0 : this->heatRecoveryRate = 0.0;
674 0 : this->heatRecoveryOutletTemp = this->heatRecoveryInletTemp;
675 : }
676 : }
677 : // report the net heat balance as source side heat transfer
678 4 : Real64 heatReoveryRateUnused = std::max(0.0, (heatRecoverRateTot - this->heatRecoveryRate));
679 4 : if (heatReoveryRateUnused > 0.0) {
680 2 : this->sourceSideHeatTransfer = heatReoveryRateUnused;
681 : // calculate source side outlet conditions
682 2 : Real64 const CpSrc = Psychrometrics::PsyCpAirFnW(state.dataEnvrn->OutHumRat);
683 2 : Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc;
684 2 : this->sourceSideOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, this->sourceSideHeatTransfer / sourceMCp);
685 : } else {
686 : // reset the source side report variables
687 2 : this->sourceSideHeatTransfer = 0.0;
688 2 : this->sourceSideOutletTemp = this->sourceSideInletTemp;
689 : }
690 4 : }
691 :
692 30 : void EIRPlantLoopHeatPump::setHeatRecoveryOperatingStatusASHP([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] bool FirstHVACIteration)
693 : {
694 30 : if (!this->running) {
695 5 : if (this->heatRecoveryAvailable) {
696 : // set the HR operation off
697 2 : this->heatRecoveryIsActive = false;
698 2 : this->heatRecoveryOperatingStatus = 0;
699 : }
700 : } else { // the heat pump must be running
701 25 : if (this->heatRecoveryAvailable) {
702 : // apply min/max HR operating limits based on heat recovery entering fluid temperature
703 4 : if (this->minHeatRecoveryTempLimit > this->heatRecoveryInletTemp || this->maxHeatRecoveryTempLimit < this->heatRecoveryInletTemp) {
704 : // set the HR operation off
705 0 : this->heatRecoveryIsActive = false;
706 0 : this->heatRecoveryOperatingStatus = 0;
707 : } else {
708 : // set the HR operation on
709 4 : this->heatRecoveryIsActive = true;
710 4 : this->heatRecoveryOperatingStatus = 1;
711 : }
712 : }
713 : }
714 30 : }
715 :
716 19 : void EIRPlantLoopHeatPump::capModFTCurveCheck(EnergyPlusData &state, const Real64 loadSideOutletSetpointTemp, Real64 &capacityModifierFuncTemp)
717 : {
718 19 : if (capacityModifierFuncTemp < 0.0) {
719 0 : if (this->capModFTErrorIndex == 0) {
720 0 : ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)], this->name));
721 0 : ShowContinueError(state,
722 0 : format(" Capacity Modifier curve (function of Temperatures) output is negative ({:.3T}).", capacityModifierFuncTemp));
723 0 : ShowContinueError(state,
724 0 : format(" Negative value occurs using a water temperature of {:.2T}C and an outdoor air temperature of {:.2T}C.",
725 : loadSideOutletSetpointTemp,
726 0 : this->sourceSideInletTemp));
727 0 : ShowContinueErrorTimeStamp(state, " Resetting curve output to zero and continuing simulation.");
728 : }
729 0 : ShowRecurringWarningErrorAtEnd(state,
730 0 : format("{} \"{}\": Capacity Modifier curve (function of Temperatures) output is negative warning continues...",
731 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
732 0 : this->name),
733 0 : this->capModFTErrorIndex,
734 : capacityModifierFuncTemp,
735 : capacityModifierFuncTemp);
736 0 : capacityModifierFuncTemp = 0.0;
737 : }
738 19 : }
739 :
740 0 : void EIRPlantLoopHeatPump::heatRecoveryCapModFTCurveCheck(EnergyPlusData &state,
741 : const Real64 loadSideOutletSetpointTemp,
742 : Real64 &capacityModifierFuncTemp)
743 : {
744 0 : if (capacityModifierFuncTemp < 0.0) {
745 0 : if (this->heatRecCapModFTErrorIndex == 0) {
746 0 : ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)], this->name));
747 0 : ShowContinueError(state,
748 0 : format(" Heat Recovery mode Capacity Modifier curve (function of Temperatures) output is negative ({:.3T}).",
749 : capacityModifierFuncTemp));
750 0 : ShowContinueError(
751 : state,
752 0 : format(
753 : " Negative value occurs using a load side water temperature of {:.2T}C and heat recovery entering water temperature of {:.2T}C.",
754 : loadSideOutletSetpointTemp,
755 0 : this->heatRecoveryInletTemp));
756 0 : ShowContinueErrorTimeStamp(state, " Resetting curve output to zero and continuing simulation.");
757 : }
758 0 : ShowRecurringWarningErrorAtEnd(
759 : state,
760 0 : format("{} \"{}\": Heat Recovery mode Capacity Modifier curve (function of Temperatures) output is negative warning continues...",
761 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
762 0 : this->name),
763 0 : this->heatRecCapModFTErrorIndex,
764 : capacityModifierFuncTemp,
765 : capacityModifierFuncTemp);
766 0 : capacityModifierFuncTemp = 0.0;
767 : }
768 0 : }
769 :
770 21 : void EIRPlantLoopHeatPump::eirModCurveCheck(EnergyPlusData &state, Real64 &eirModifierFuncTemp)
771 : {
772 21 : if (eirModifierFuncTemp < 0.0) {
773 0 : if (this->eirModFTErrorIndex == 0) {
774 0 : ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)], this->name));
775 0 : ShowContinueError(state, format(" EIR Modifier curve (function of Temperatures) output is negative ({:.3T}).", eirModifierFuncTemp));
776 0 : ShowContinueError(state,
777 0 : format(" Negative value occurs using a water temperature of {:.2T}C and an outdoor air temperature of {:.2T}C.",
778 0 : this->loadSideOutletTemp,
779 0 : this->sourceSideInletTemp));
780 0 : ShowContinueErrorTimeStamp(state, " Resetting curve output to zero and continuing simulation.");
781 : }
782 0 : ShowRecurringWarningErrorAtEnd(state,
783 0 : format("{} \"{}\": EIR Modifier curve (function of Temperatures) output is negative warning continues...",
784 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
785 0 : this->name),
786 0 : this->eirModFTErrorIndex,
787 : eirModifierFuncTemp,
788 : eirModifierFuncTemp);
789 0 : eirModifierFuncTemp = 0.0;
790 : }
791 21 : }
792 :
793 0 : void EIRPlantLoopHeatPump::heatRecoveryEIRModCurveCheck(EnergyPlusData &state, Real64 &eirModifierFuncTemp)
794 : {
795 0 : if (eirModifierFuncTemp < 0.0) {
796 0 : if (this->heatRecEIRModFTErrorIndex == 0 && heatRecoveryEIRFTempCurveIndex > 0) {
797 0 : ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)], this->name));
798 0 : ShowContinueError(
799 0 : state, format(" Heat Recovery mode EIR Modifier curve (function of Temperatures) output is negative ({:.3T}).", eirModifierFuncTemp));
800 0 : ShowContinueError(
801 : state,
802 0 : format(
803 : " Negative value occurs using a load side water temperature of {:.2T}C and heat recovery entering water temperature of {:.2T}C.",
804 0 : this->loadSideOutletTemp,
805 0 : this->heatRecoveryInletTemp));
806 0 : ShowContinueErrorTimeStamp(state, " Resetting curve output to zero and continuing simulation.");
807 : }
808 0 : ShowRecurringWarningErrorAtEnd(
809 : state,
810 0 : format("{} \"{}\": Heat Recovery mode EIR Modifier curve (function of Temperatures) output is negative warning continues...",
811 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
812 0 : this->name),
813 0 : this->eirModFTErrorIndex,
814 : eirModifierFuncTemp,
815 : eirModifierFuncTemp);
816 0 : eirModifierFuncTemp = 0.0;
817 : }
818 0 : }
819 :
820 21 : void EIRPlantLoopHeatPump::eirModFPLRCurveCheck(EnergyPlusData &state, Real64 &eirModifierFuncPLR)
821 : {
822 21 : if (eirModifierFuncPLR < 0.0) {
823 1 : if (this->eirModFPLRErrorIndex == 0) {
824 1 : ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)], this->name));
825 1 : ShowContinueError(state, format(" EIR Modifier curve (function of PLR) output is negative ({:.3T}).", eirModifierFuncPLR));
826 1 : ShowContinueError(state, format(" Negative value occurs using a Part Load Ratio of {:.2T}", this->partLoadRatio));
827 3 : ShowContinueErrorTimeStamp(state, " Resetting curve output to zero and continuing simulation.");
828 : }
829 7 : ShowRecurringWarningErrorAtEnd(state,
830 2 : format("{} \"{}\": EIR Modifier curve (function of PLR) output is negative warning continues...",
831 1 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
832 1 : this->name),
833 1 : this->eirModFPLRErrorIndex,
834 : eirModifierFuncPLR,
835 : eirModifierFuncPLR);
836 1 : eirModifierFuncPLR = 0.0;
837 : }
838 21 : }
839 :
840 11 : void EIRPlantLoopHeatPump::doDefrost(EnergyPlusData &state, Real64 &availableCapacity)
841 : {
842 : // Initializing defrost adjustment factors
843 11 : Real64 InputPowerMultiplier = 1.0;
844 11 : Real64 HeatingCapacityMultiplier = 1.0;
845 :
846 : // Check outdoor temperature to determine of defrost is active
847 11 : if (this->defrostAvailable && state.dataEnvrn->OutDryBulbTemp <= this->maxOutdoorTemperatureDefrost) {
848 : // Calculate defrost adjustment factors depending on defrost control type
849 : // Calculate delta w through outdoor coil by assuming a coil temp of 0.82*DBT-9.7(F) per DOE2.1E
850 0 : Real64 OutdoorCoilT = 0.82 * state.dataEnvrn->OutDryBulbTemp - 8.589;
851 : Real64 OutdoorCoildw =
852 0 : max(1.0e-6, (state.dataEnvrn->OutHumRat - Psychrometrics::PsyWFnTdpPb(state, OutdoorCoilT, state.dataEnvrn->OutBaroPress)));
853 0 : if (this->defrostStrategy == DefrostControl::Timed) {
854 0 : if (this->defrostTime > 0.0) {
855 0 : this->fractionalDefrostTime = this->defrostTime; // DefrostTime in hours
856 0 : HeatingCapacityMultiplier = 0.909 - 107.33 * OutdoorCoildw;
857 0 : InputPowerMultiplier = 0.90 - 36.45 * OutdoorCoildw;
858 0 : this->loadDueToDefrost =
859 0 : (0.01 * this->fractionalDefrostTime) * (7.222 - state.dataEnvrn->OutDryBulbTemp) * (this->referenceCapacity / 1.01667);
860 0 : Real64 defrostEIRFT = 1.0 / this->referenceCOP;
861 0 : if (defrostEIRFTIndex > 0) {
862 0 : defrostEIRFT = Curve::CurveValue(
863 0 : state, this->defrostEIRFTIndex, max(15.555, state.dataEnvrn->OutWetBulbTemp), max(15.555, state.dataEnvrn->OutDryBulbTemp));
864 : }
865 0 : this->defrostEnergyRate = defrostEIRFT * (this->referenceCapacity / 1.01667) * this->fractionalDefrostTime;
866 : } else {
867 0 : this->loadDueToDefrost = 0.0;
868 0 : this->defrostEnergyRate = 0.0;
869 0 : this->fractionalDefrostTime = 0.0;
870 : }
871 0 : } else if (this->defrostStrategy == DefrostControl::OnDemand) {
872 0 : this->fractionalDefrostTime = 1.0 / (1.0 + 0.01446 / OutdoorCoildw);
873 0 : HeatingCapacityMultiplier = 0.875 * (1.0 - this->fractionalDefrostTime);
874 0 : InputPowerMultiplier = 0.954 * (1.0 - this->fractionalDefrostTime);
875 0 : this->loadDueToDefrost =
876 0 : (0.01 * this->fractionalDefrostTime) * (7.222 - state.dataEnvrn->OutDryBulbTemp) * (this->referenceCapacity / 1.01667);
877 0 : Real64 defrostEIRFT = 0.0;
878 0 : if (defrostEIRFTIndex > 0) {
879 0 : defrostEIRFT = Curve::CurveValue(
880 0 : state, this->defrostEIRFTIndex, max(15.555, state.dataEnvrn->OutWetBulbTemp), max(15.555, state.dataEnvrn->OutDryBulbTemp));
881 : }
882 0 : this->defrostEnergyRate = defrostEIRFT * (this->referenceCapacity / 1.01667) * this->fractionalDefrostTime;
883 0 : } else if (this->defrostStrategy == DefrostControl::TimedEmpirical) {
884 : // cycles of defrost per hour
885 0 : Real64 thisHourDefrostCycles = Curve::CurveValue(state, this->defrostFreqCurveIndex, state.dataEnvrn->OutDryBulbTemp);
886 : // is directly proportional to the ratio of capacity used for that hour (PLR)
887 0 : Real64 const operatingPLR = this->partLoadRatio * this->cyclingRatio;
888 0 : thisHourDefrostCycles *= operatingPLR;
889 : // fraction of heat load per cycle of defrost
890 0 : Real64 thisHourDefrostHeatLoad = 0.0;
891 0 : if (this->defrostLoadCurveDims == 2) { // BiQuadratic
892 : thisHourDefrostHeatLoad =
893 0 : Curve::CurveValue(state, this->defrostHeatLoadCurveIndex, state.dataEnvrn->OutWetBulbTemp, state.dataEnvrn->OutDryBulbTemp);
894 : } else {
895 0 : thisHourDefrostHeatLoad = Curve::CurveValue(state, this->defrostHeatLoadCurveIndex, state.dataEnvrn->OutDryBulbTemp);
896 : }
897 : // heat load is applied to full load (not rated) and is proportional to defrost cycles per hour
898 0 : this->loadDueToDefrost = availableCapacity * thisHourDefrostHeatLoad * thisHourDefrostCycles;
899 : // electric input fraction due to defrost
900 0 : Real64 defrostHeatEnergyFraction = 0.0;
901 0 : if (this->defrostEnergyCurveDims == 2) { // BiQuadratic
902 : defrostHeatEnergyFraction =
903 0 : Curve::CurveValue(state, this->defrostHeatEnergyCurveIndex, state.dataEnvrn->OutWetBulbTemp, state.dataEnvrn->OutDryBulbTemp);
904 : } else {
905 0 : defrostHeatEnergyFraction = Curve::CurveValue(state, this->defrostHeatEnergyCurveIndex, state.dataEnvrn->OutDryBulbTemp);
906 : }
907 : // defrost energy rate is applied to rated power and is proportional to defrost cycles per hour
908 0 : this->defrostEnergyRate = (this->referenceCapacity / this->referenceCOP) * defrostHeatEnergyFraction * thisHourDefrostCycles;
909 :
910 : // question on how these multipliers are accounted for with capacity and power (e.g., 1+ or 1-)
911 0 : InputPowerMultiplier = 1.0 + thisHourDefrostHeatLoad;
912 0 : HeatingCapacityMultiplier = 1.0 + (thisHourDefrostHeatLoad * thisHourDefrostCycles);
913 0 : this->fractionalDefrostTime = thisHourDefrostCycles * this->fractionalDefrostTime;
914 : }
915 : } else {
916 11 : this->defrostEnergyRate = 0.0;
917 11 : this->loadDueToDefrost = 0.0;
918 11 : this->fractionalDefrostTime = 0.0;
919 : }
920 11 : availableCapacity *= HeatingCapacityMultiplier;
921 : // update class member variables
922 11 : this->defrostPowerMultiplier = InputPowerMultiplier;
923 11 : }
924 :
925 41 : void EIRPlantLoopHeatPump::onInitLoopEquip(EnergyPlusData &state, [[maybe_unused]] const PlantLocation &calledFromLocation)
926 : {
927 : // This function does all one-time and begin-environment initialization
928 95 : std::string static const routineName = std::string("EIRPlantLoopHeatPump :") + __FUNCTION__;
929 :
930 41 : this->oneTimeInit(state); // plant setup
931 36 : this->isPlantInletOrOutlet(state); // check location
932 :
933 36 : if (calledFromLocation.loopNum == this->loadSidePlantLoc.loopNum) {
934 36 : this->sizeLoadSide(state);
935 36 : if (this->waterSource) {
936 15 : this->sizeSrcSideWSHP(state);
937 21 : } else if (this->airSource) {
938 21 : this->sizeSrcSideASHP(state);
939 21 : this->sizeHeatRecoveryASHP(state);
940 : }
941 : }
942 :
943 36 : if (state.dataGlobal->BeginEnvrnFlag && this->envrnInit && state.dataPlnt->PlantFirstSizesOkayToFinalize) {
944 :
945 23 : Real64 rho = this->loadSidePlantLoc.loop->glycol->getDensity(state, Constant::InitConvTemp, routineName);
946 23 : this->loadSideDesignMassFlowRate = rho * this->loadSideDesignVolFlowRate;
947 23 : PlantUtilities::InitComponentNodes(state, 0.0, this->loadSideDesignMassFlowRate, this->loadSideNodes.inlet, this->loadSideNodes.outlet);
948 :
949 23 : if (this->waterSource) {
950 7 : rho = this->sourceSidePlantLoc.loop->glycol->getDensity(state, Constant::InitConvTemp, routineName);
951 7 : this->sourceSideDesignMassFlowRate = rho * this->sourceSideDesignVolFlowRate;
952 7 : PlantUtilities::InitComponentNodes(
953 : state, 0.0, this->sourceSideDesignMassFlowRate, this->sourceSideNodes.inlet, this->sourceSideNodes.outlet);
954 16 : } else if (this->airSource) {
955 16 : rho = Psychrometrics::PsyRhoAirFnPbTdbW(state, state.dataEnvrn->StdBaroPress, state.dataEnvrn->OutDryBulbTemp, 0.0, routineName);
956 16 : this->sourceSideDesignMassFlowRate = rho * this->sourceSideDesignVolFlowRate;
957 : // heat recovery
958 16 : if (this->heatRecoveryAvailable) {
959 6 : rho = this->heatRecoveryPlantLoc.loop->glycol->getDensity(state, Constant::InitConvTemp, routineName);
960 6 : this->heatRecoveryDesignMassFlowRate = rho * this->heatRecoveryDesignVolFlowRate;
961 6 : PlantUtilities::InitComponentNodes(
962 : state, 0.0, this->heatRecoveryDesignMassFlowRate, this->heatRecoveryNodes.inlet, this->heatRecoveryNodes.outlet);
963 : }
964 : }
965 :
966 23 : if (this->flowControl == DataPlant::FlowMode::VariableSpeedPump) {
967 0 : this->loadVSPumpMinLimitMassFlow =
968 0 : PlantUtilities::MinFlowIfBranchHasVSPump(state, this->loadSidePlantLoc, this->loadVSBranchPump, this->loadVSLoopPump, true);
969 0 : if (this->waterSource) {
970 0 : this->sourceVSPumpMinLimitMassFlow = PlantUtilities::MinFlowIfBranchHasVSPump(
971 0 : state, this->sourceSidePlantLoc, this->sourceVSBranchPump, this->sourceVSLoopPump, false);
972 : }
973 : }
974 :
975 23 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
976 8 : this->envrnInit = false;
977 : }
978 : }
979 36 : if (!state.dataGlobal->BeginEnvrnFlag) {
980 1 : this->envrnInit = true;
981 : }
982 36 : }
983 :
984 4 : void EIRPlantLoopHeatPump::getDesignCapacities(
985 : [[maybe_unused]] EnergyPlusData &state, const PlantLocation &calledFromLocation, Real64 &MaxLoad, Real64 &MinLoad, Real64 &OptLoad)
986 : {
987 4 : if (calledFromLocation.loopNum == this->loadSidePlantLoc.loopNum) {
988 3 : MinLoad = 0.0;
989 3 : MaxLoad = this->referenceCapacity;
990 3 : OptLoad = this->referenceCapacity;
991 : } else {
992 1 : MinLoad = 0.0;
993 1 : MaxLoad = 0.0;
994 1 : OptLoad = 0.0;
995 : }
996 4 : }
997 :
998 49 : void EIRPlantLoopHeatPump::sizeLoadSide(EnergyPlusData &state)
999 : {
1000 : // Tries to size the load side flow rate and capacity, source side flow, and the rated power usage
1001 : // There are two major sections to this function, one if plant sizing is available, and one if not
1002 : // If plant sizing is available, then we can generate sizes for the equipment. This is done for not-only
1003 : // autosized fields, but also hard-sized fields so that we can report out significant deviations between
1004 : // the two values.
1005 : // If plant sizing is not available, it tries to use a companion heat pump coil to do sizing
1006 :
1007 49 : bool errorsFound = false;
1008 :
1009 : // these variables will be used throughout this function as a temporary value of that physical state
1010 49 : Real64 tmpCapacity = this->referenceCapacity;
1011 49 : Real64 tmpLoadVolFlow = this->loadSideDesignVolFlowRate;
1012 49 : HeatSizingType heatingSizingMethod = this->heatSizingMethod;
1013 :
1014 49 : std::string_view const typeName = DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)];
1015 49 : Real64 loadSideInitTemp =
1016 49 : (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating) ? Constant::HWInitConvTemp : Constant::CWInitConvTemp;
1017 : // I guess I can assume the plant fluids are the same for HW and CW. So only the sizing type is an issue on which to use.
1018 :
1019 49 : Real64 rho = this->loadSidePlantLoc.loop->glycol->getDensity(state, loadSideInitTemp, "EIRPlantLoopHeatPump::sizeLoadSide()");
1020 49 : Real64 Cp = this->loadSidePlantLoc.loop->glycol->getSpecificHeat(state, loadSideInitTemp, "EIRPlantLoopHeatPump::sizeLoadSide()");
1021 :
1022 49 : int pltLoadSizNum = this->loadSidePlantLoc.loop->PlantSizNum;
1023 49 : if (pltLoadSizNum > 0) {
1024 : // this first IF block is really just about calculating the local tmpCapacity and tmpLoadVolFlow values
1025 : // these represent what the unit would size those to, whether it is doing auto-sizing or not
1026 19 : if (state.dataSize->PlantSizData(pltLoadSizNum).DesVolFlowRate > HVAC::SmallWaterVolFlow) {
1027 7 : tmpLoadVolFlow = state.dataSize->PlantSizData(pltLoadSizNum).DesVolFlowRate * this->sizingFactor;
1028 7 : Real64 deltaT = state.dataSize->PlantSizData(pltLoadSizNum).DeltaT;
1029 7 : if (this->companionHeatPumpCoil) {
1030 6 : if (this->companionHeatPumpCoil->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating) {
1031 3 : heatingSizingMethod = this->companionHeatPumpCoil->heatSizingMethod;
1032 : }
1033 6 : Real64 companionVolFlowRate = this->companionHeatPumpCoil->loadSideDesignVolFlowRate;
1034 6 : int compLoopNum = this->companionHeatPumpCoil->loadSidePlantLoc.loopNum;
1035 6 : if (compLoopNum > 0) {
1036 3 : companionVolFlowRate = state.dataSize->PlantSizData(compLoopNum).DesVolFlowRate * this->companionHeatPumpCoil->sizingFactor;
1037 : }
1038 6 : Real64 compRefCapacity = this->companionHeatPumpCoil->referenceCapacity;
1039 6 : Real64 compRho = rho;
1040 6 : Real64 compCp = Cp;
1041 6 : Real64 compDeltaT = deltaT;
1042 6 : if (compLoopNum > 0) {
1043 3 : compRho = state.dataPlnt->PlantLoop(compLoopNum)
1044 6 : .glycol->getDensity(state,
1045 3 : this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRCooling ? Constant::HWInitConvTemp
1046 : : Constant::CWInitConvTemp,
1047 : "EIRPlantLoopHeatPump::sizeLoadSide()");
1048 : compCp =
1049 3 : state.dataPlnt->PlantLoop(compLoopNum)
1050 6 : .glycol->getSpecificHeat(state,
1051 3 : this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRCooling ? Constant::HWInitConvTemp
1052 : : Constant::CWInitConvTemp,
1053 : "EIRPlantLoopHeatPump::sizeLoadSide()");
1054 3 : compDeltaT = state.dataSize->PlantSizData(compLoopNum).DeltaT;
1055 : }
1056 6 : if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRCooling) {
1057 3 : tmpCapacity = Cp * rho * deltaT * tmpLoadVolFlow;
1058 3 : if (heatingSizingMethod == HeatSizingType::Heating) {
1059 0 : tmpCapacity = (compCp * compRho * compDeltaT * companionVolFlowRate) / this->companionHeatPumpCoil->heatSizingRatio;
1060 3 : } else if (heatingSizingMethod == HeatSizingType::GreaterOfCoolingOrHeating) {
1061 0 : compRefCapacity = compCp * compRho * compDeltaT * companionVolFlowRate;
1062 0 : if (compRefCapacity > tmpCapacity) {
1063 0 : rho = compRho;
1064 0 : tmpLoadVolFlow = companionVolFlowRate;
1065 0 : tmpCapacity = compRefCapacity / this->companionHeatPumpCoil->heatSizingRatio;
1066 : }
1067 : }
1068 : } else { // size heating side based on sizing method
1069 3 : if (heatingSizingMethod == HeatSizingType::Heating) {
1070 0 : tmpCapacity = Cp * rho * deltaT * tmpLoadVolFlow;
1071 : } else {
1072 3 : compRefCapacity = compCp * compRho * compDeltaT * companionVolFlowRate;
1073 3 : if (heatingSizingMethod == HeatSizingType::Cooling) {
1074 3 : tmpCapacity = compRefCapacity * this->heatSizingRatio;
1075 3 : rho = compRho;
1076 3 : tmpLoadVolFlow = companionVolFlowRate;
1077 : } else { // else GreaterOfHeatingOrCooling
1078 0 : tmpCapacity = Cp * rho * deltaT * tmpLoadVolFlow;
1079 0 : if (compRefCapacity > tmpCapacity) {
1080 0 : tmpCapacity = compRefCapacity * this->heatSizingRatio;
1081 0 : rho = compRho;
1082 0 : tmpLoadVolFlow = companionVolFlowRate;
1083 : }
1084 : }
1085 : }
1086 : }
1087 : } else {
1088 1 : tmpCapacity = Cp * rho * deltaT * tmpLoadVolFlow * this->heatSizingRatio;
1089 : }
1090 12 : } else if (this->companionHeatPumpCoil && this->companionHeatPumpCoil->loadSideDesignVolFlowRate > 0.0) {
1091 10 : tmpLoadVolFlow = this->companionHeatPumpCoil->loadSideDesignVolFlowRate;
1092 10 : if (this->companionHeatPumpCoil->referenceCapacity == DataSizing::AutoSize) {
1093 : // use reverse init temp, e.g., if this is cooling use HWInitConvTemp
1094 4 : Real64 compLoadSideInitTemp =
1095 4 : (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRCooling) ? Constant::HWInitConvTemp : Constant::CWInitConvTemp;
1096 4 : int compLoopNum = this->companionHeatPumpCoil->loadSidePlantLoc.loopNum;
1097 4 : if (compLoopNum > 0) {
1098 0 : Real64 const compRho = state.dataPlnt->PlantLoop(compLoopNum)
1099 0 : .glycol->getDensity(state, compLoadSideInitTemp, "EIRPlantLoopHeatPump::sizeLoadSide()");
1100 0 : Real64 const compCp = state.dataPlnt->PlantLoop(compLoopNum)
1101 0 : .glycol->getSpecificHeat(state, Constant::CWInitConvTemp, "EIRPlantLoopHeatPump::sizeLoadSide()");
1102 0 : rho = compRho;
1103 0 : Cp = compCp;
1104 : }
1105 4 : tmpCapacity = Cp * rho * state.dataSize->PlantSizData(pltLoadSizNum).DeltaT * tmpLoadVolFlow * this->heatSizingRatio;
1106 : } else {
1107 6 : tmpCapacity = this->companionHeatPumpCoil->referenceCapacity;
1108 : }
1109 10 : } else {
1110 2 : if (this->referenceCapacityWasAutoSized) tmpCapacity = 0.0;
1111 2 : if (this->loadSideDesignVolFlowRateWasAutoSized) tmpLoadVolFlow = 0.0;
1112 : }
1113 19 : if (this->heatRecoveryHeatPump) {
1114 0 : tmpLoadVolFlow = state.dataSize->PlantSizData(pltLoadSizNum).DesVolFlowRate;
1115 : }
1116 19 : if (this->loadSideDesignVolFlowRateWasAutoSized) this->loadSideDesignVolFlowRate = tmpLoadVolFlow;
1117 19 : if (this->referenceCapacityWasAutoSized) {
1118 16 : this->referenceCapacity = tmpCapacity;
1119 : }
1120 : // now we actually need to store and report out the values
1121 19 : if (state.dataPlnt->PlantFirstSizesOkayToFinalize) {
1122 : // handle the auto-sizable reference capacity
1123 18 : if (this->referenceCapacityWasAutoSized) {
1124 : // if auto-sized, we just need to store the sized value and then report out the capacity when plant is ready
1125 16 : this->referenceCapacity = tmpCapacity;
1126 16 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
1127 16 : BaseSizer::reportSizerOutput(state, typeName, this->name, "Design Size Nominal Capacity [W]", tmpCapacity);
1128 : }
1129 16 : if (state.dataPlnt->PlantFirstSizesOkayToReport) {
1130 16 : BaseSizer::reportSizerOutput(state, typeName, this->name, "Initial Design Size Nominal Capacity [W]", tmpCapacity);
1131 : }
1132 : } else {
1133 : // this blocks means the capacity value was hard-sized
1134 2 : if (this->referenceCapacity > 0.0 && tmpCapacity > 0.0) {
1135 : // then the capacity was hard-sized to a good value and the tmpCapacity was calculated to a good value too
1136 2 : Real64 hardSizedCapacity = this->referenceCapacity;
1137 2 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
1138 2 : if (state.dataGlobal->DoPlantSizing) {
1139 1 : BaseSizer::reportSizerOutput(state,
1140 : typeName,
1141 : this->name,
1142 : "Design Size Nominal Capacity [W]",
1143 : tmpCapacity,
1144 : "User-Specified Nominal Capacity [W]",
1145 : hardSizedCapacity);
1146 : } else {
1147 1 : BaseSizer::reportSizerOutput(state, typeName, this->name, "User-Specified Nominal Capacity [W]", hardSizedCapacity);
1148 : }
1149 : // we can warn here if there is a bit mismatch between hard- and auto-sized
1150 2 : if (state.dataGlobal->DisplayExtraWarnings) {
1151 2 : if ((std::abs(tmpCapacity - hardSizedCapacity) / hardSizedCapacity) > state.dataSize->AutoVsHardSizingThreshold) {
1152 4 : ShowWarningMessage(state,
1153 4 : format("EIRPlantLoopHeatPump::size(): Potential issue with equipment sizing for {}", this->name));
1154 2 : ShowContinueError(state, format("User-Specified Nominal Capacity of {:.2R} [W]", hardSizedCapacity));
1155 2 : ShowContinueError(state, format("differs from Design Size Nominal Capacity of {:.2R} [W]", tmpCapacity));
1156 4 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
1157 6 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
1158 : }
1159 : }
1160 : }
1161 : // moving forward with more calculations, we need to update the 'tmp' capacity to the hard-sized value
1162 2 : tmpCapacity = hardSizedCapacity;
1163 : }
1164 : }
1165 : // now handle the auto-sizable load side flow rate
1166 18 : if (this->loadSideDesignVolFlowRateWasAutoSized) {
1167 8 : this->loadSideDesignVolFlowRate = tmpLoadVolFlow;
1168 8 : this->loadSideDesignMassFlowRate = rho * this->loadSideDesignVolFlowRate;
1169 8 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
1170 8 : BaseSizer::reportSizerOutput(state, typeName, this->name, "Design Size Load Side Volume Flow Rate [m3/s]", tmpLoadVolFlow);
1171 : }
1172 8 : if (state.dataPlnt->PlantFirstSizesOkayToReport) {
1173 8 : BaseSizer::reportSizerOutput(
1174 : state, typeName, this->name, "Initial Design Size Load Side Volume Flow Rate [m3/s]", tmpLoadVolFlow);
1175 : }
1176 : } else {
1177 10 : if (this->loadSideDesignVolFlowRate > 0.0 && tmpLoadVolFlow > 0.0) {
1178 10 : Real64 hardSizedLoadSideFlow = this->loadSideDesignVolFlowRate;
1179 10 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
1180 10 : if (state.dataGlobal->DoPlantSizing) {
1181 1 : BaseSizer::reportSizerOutput(state,
1182 : typeName,
1183 : this->name,
1184 : "Design Size Load Side Volume Flow Rate [m3/s]",
1185 : tmpLoadVolFlow,
1186 : "User-Specified Load Side Volume Flow Rate [m3/s]",
1187 : hardSizedLoadSideFlow);
1188 : } else {
1189 9 : BaseSizer::reportSizerOutput(
1190 : state, typeName, this->name, "User-Specified Load Side Volume Flow Rate [m3/s]", hardSizedLoadSideFlow);
1191 : }
1192 10 : if (state.dataGlobal->DisplayExtraWarnings) {
1193 2 : if ((std::abs(tmpLoadVolFlow - hardSizedLoadSideFlow) / hardSizedLoadSideFlow) >
1194 2 : state.dataSize->AutoVsHardSizingThreshold) {
1195 0 : ShowMessage(state, format("EIRPlantLoopHeatPump::size(): Potential issue with equipment sizing for {}", this->name));
1196 0 : ShowContinueError(state, format("User-Specified Load Side Volume Flow Rate of {:.2R} [m3/s]", hardSizedLoadSideFlow));
1197 0 : ShowContinueError(state,
1198 0 : format("differs from Design Size Load Side Volume Flow Rate of {:.2R} [m3/s]", tmpLoadVolFlow));
1199 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
1200 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
1201 : }
1202 : }
1203 : }
1204 10 : tmpLoadVolFlow = hardSizedLoadSideFlow;
1205 : }
1206 : }
1207 : }
1208 : } else {
1209 : // no plant sizing available...try to use the companion coil
1210 30 : if (this->companionHeatPumpCoil) {
1211 11 : if (this->companionHeatPumpCoil->loadSideDesignVolFlowRateWasAutoSized && this->companionHeatPumpCoil->loadSideDesignVolFlowRate > 0.0) {
1212 1 : tmpLoadVolFlow = this->companionHeatPumpCoil->loadSideDesignVolFlowRate;
1213 1 : if (state.dataPlnt->PlantFirstSizesOkayToFinalize) {
1214 1 : this->loadSideDesignVolFlowRate = tmpLoadVolFlow;
1215 1 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
1216 1 : BaseSizer::reportSizerOutput(state, typeName, this->name, "Design Size Load Side Volume Flow Rate [m3/s]", tmpLoadVolFlow);
1217 : }
1218 1 : if (state.dataPlnt->PlantFirstSizesOkayToReport) {
1219 1 : BaseSizer::reportSizerOutput(
1220 : state, typeName, this->name, "Initial Design Size Load Side Volume Flow Rate [m3/s]", tmpLoadVolFlow);
1221 : }
1222 : }
1223 : }
1224 11 : if (this->companionHeatPumpCoil->referenceCapacityWasAutoSized && this->companionHeatPumpCoil->referenceCapacity > 0.0) {
1225 1 : tmpCapacity = this->companionHeatPumpCoil->referenceCapacity;
1226 1 : if (state.dataPlnt->PlantFirstSizesOkayToFinalize) {
1227 1 : this->referenceCapacity = tmpCapacity;
1228 1 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
1229 1 : BaseSizer::reportSizerOutput(state, typeName, this->name, "Design Size Nominal Capacity [W]", tmpCapacity);
1230 : }
1231 1 : if (state.dataPlnt->PlantFirstSizesOkayToReport) {
1232 1 : BaseSizer::reportSizerOutput(state, typeName, this->name, "Initial Design Size Nominal Capacity [W]", tmpCapacity);
1233 : }
1234 : }
1235 : }
1236 : } else {
1237 : // no companion coil, and no plant sizing, so can't do anything
1238 22 : if ((this->loadSideDesignVolFlowRateWasAutoSized || this->referenceCapacityWasAutoSized) &&
1239 3 : state.dataPlnt->PlantFirstSizesOkayToFinalize) {
1240 2 : ShowSevereError(state, "EIRPlantLoopHeatPump::size(): Autosizing requires a loop Sizing:Plant object.");
1241 1 : ShowContinueError(state, format("Occurs in HeatPump:PlantLoop:EquationFit:Cooling object = {}", this->name));
1242 1 : errorsFound = true;
1243 : }
1244 : }
1245 30 : if (!this->loadSideDesignVolFlowRateWasAutoSized && state.dataPlnt->PlantFinalSizesOkayToReport) {
1246 1 : BaseSizer::reportSizerOutput(state, typeName, this->name, "User-Specified Load Side Flow Rate [m3/s]", this->loadSideDesignVolFlowRate);
1247 : }
1248 30 : if (!this->referenceCapacityWasAutoSized && state.dataPlnt->PlantFinalSizesOkayToReport) {
1249 1 : BaseSizer::reportSizerOutput(state, typeName, this->name, "User-Specified Nominal Capacity [W]", this->referenceCapacity);
1250 : }
1251 : }
1252 49 : if (errorsFound) {
1253 3 : ShowFatalError(state, "Preceding sizing errors cause program termination");
1254 : }
1255 48 : }
1256 :
1257 22 : void EIRPlantLoopHeatPump::sizeSrcSideWSHP(EnergyPlusData &state)
1258 : {
1259 : // size the source-side for the water-source HP
1260 22 : bool errorsFound = false;
1261 :
1262 : // these variables will be used throughout this function as a temporary value of that physical state
1263 22 : Real64 tmpCapacity = this->referenceCapacity;
1264 22 : Real64 tmpLoadVolFlow = this->loadSideDesignVolFlowRate;
1265 : Real64 tmpSourceVolFlow;
1266 :
1267 22 : std::string_view const typeName = DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)];
1268 22 : Real64 sourceSideInitTemp =
1269 22 : (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRCooling) ? Constant::CWInitConvTemp : Constant::HWInitConvTemp;
1270 :
1271 22 : Real64 const rhoSrc = this->loadSidePlantLoc.loop->glycol->getDensity(state, sourceSideInitTemp, "EIRPlantLoopHeatPump::sizeSrcSideWSHP()");
1272 22 : Real64 const CpSrc = this->loadSidePlantLoc.loop->glycol->getSpecificHeat(state, sourceSideInitTemp, "EIRPlantLoopHeatPump::sizeSrcSideWSHP()");
1273 :
1274 : // To start we need to override the calculated load side flow
1275 : // rate if it was actually hard-sized
1276 22 : if (!this->loadSideDesignVolFlowRateWasAutoSized) tmpLoadVolFlow = this->loadSideDesignVolFlowRate;
1277 :
1278 : // calculate an auto-sized value for source design flow regardless of whether it was auto-sized or not
1279 22 : int plantSourceSizingIndex = this->sourceSidePlantLoc.loop->PlantSizNum;
1280 22 : if (plantSourceSizingIndex > 0) {
1281 : // to get the source flow, we first must calculate the required heat impact on the source side
1282 : // First the definition of COP: COP = Qload/Power, therefore Power = Qload/COP
1283 : // Then the energy balance: Qsrc = Qload + Power
1284 : // Substituting for Power: Qsrc = Qload + Qload/COP, therefore Qsrc = Qload (1 + 1/COP)
1285 6 : Real64 designSourceSideHeatTransfer = 0.0;
1286 6 : if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating) {
1287 4 : designSourceSideHeatTransfer = tmpCapacity * (1 - 1 / this->referenceCOP);
1288 : } else {
1289 2 : designSourceSideHeatTransfer = tmpCapacity * (1 + 1 / this->referenceCOP);
1290 : }
1291 : // To get the design source flow rate, just apply the sensible heat rate equation:
1292 : // Qsrc = rho_src * Vdot_src * Cp_src * DeltaT_src
1293 : // Vdot_src = Q_src / (rho_src * Cp_src * DeltaT_src)
1294 6 : tmpSourceVolFlow = designSourceSideHeatTransfer / (state.dataSize->PlantSizData(plantSourceSizingIndex).DeltaT * CpSrc * rhoSrc);
1295 6 : if (this->waterSource && this->heatRecoveryHeatPump) {
1296 : // If component is on plant outlet branch, use plant flow rate.
1297 0 : tmpSourceVolFlow = state.dataSize->PlantSizData(plantSourceSizingIndex).DesVolFlowRate;
1298 : }
1299 : } else {
1300 : // just assume it's the same as the load side if we don't have any sizing information
1301 16 : tmpSourceVolFlow = tmpLoadVolFlow;
1302 : }
1303 22 : if (this->sourceSideDesignVolFlowRateWasAutoSized) {
1304 9 : this->sourceSideDesignVolFlowRate = tmpSourceVolFlow;
1305 9 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
1306 4 : BaseSizer::reportSizerOutput(state, typeName, this->name, "Design Size Source Side Volume Flow Rate [m3/s]", tmpSourceVolFlow);
1307 : }
1308 9 : if (state.dataPlnt->PlantFirstSizesOkayToReport) {
1309 4 : BaseSizer::reportSizerOutput(state, typeName, this->name, "Initial Design Size Source Side Volume Flow Rate [m3/s]", tmpSourceVolFlow);
1310 : }
1311 : } else {
1312 : // source design flow was hard-sized
1313 13 : if (this->sourceSideDesignVolFlowRate > 0.0 && tmpSourceVolFlow > 0.0) {
1314 10 : Real64 const hardSizedSourceSideFlow = this->sourceSideDesignVolFlowRate;
1315 10 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
1316 1 : if (state.dataGlobal->DoPlantSizing) {
1317 0 : BaseSizer::reportSizerOutput(state,
1318 : typeName,
1319 : this->name,
1320 : "Design Size Source Side Volume Flow Rate [m3/s]",
1321 : tmpSourceVolFlow,
1322 : "User-Specified Source Side Volume Flow Rate [m3/s]",
1323 : hardSizedSourceSideFlow);
1324 : } else {
1325 1 : BaseSizer::reportSizerOutput(
1326 : state, typeName, this->name, "User-Specified Source Side Volume Flow Rate [m3/s]", hardSizedSourceSideFlow);
1327 : }
1328 1 : if (state.dataGlobal->DisplayExtraWarnings) {
1329 0 : if ((std::abs(tmpSourceVolFlow - hardSizedSourceSideFlow) / hardSizedSourceSideFlow) >
1330 0 : state.dataSize->AutoVsHardSizingThreshold) {
1331 0 : ShowMessage(state, format("EIRPlantLoopHeatPump::size(): Potential issue with equipment sizing for {}", this->name));
1332 0 : ShowContinueError(state, format("User-Specified Source Side Volume Flow Rate of {:.2R} [m3/s]", hardSizedSourceSideFlow));
1333 0 : ShowContinueError(state, format("differs from Design Size Source Side Volume Flow Rate of {:.2R} [m3/s]", tmpSourceVolFlow));
1334 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
1335 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
1336 : }
1337 : }
1338 : }
1339 10 : tmpSourceVolFlow = hardSizedSourceSideFlow;
1340 : }
1341 : }
1342 22 : if (this->companionHeatPumpCoil) {
1343 11 : tmpSourceVolFlow *= this->companionHeatPumpCoil->heatSizingRatio;
1344 : } else {
1345 11 : tmpSourceVolFlow *= this->heatSizingRatio;
1346 : }
1347 :
1348 : // skipping autosized power section
1349 :
1350 : // register the design volume flows with the plant, only doing half of source because the companion
1351 : // is generally on the same loop
1352 22 : if (!this->heatRecoveryHeatPump) {
1353 22 : PlantUtilities::RegisterPlantCompDesignFlow(state, this->loadSideNodes.inlet, tmpLoadVolFlow);
1354 : }
1355 22 : if (!this->heatRecoveryHeatPump) {
1356 22 : PlantUtilities::RegisterPlantCompDesignFlow(state, this->sourceSideNodes.inlet, tmpSourceVolFlow / 0.5);
1357 : }
1358 :
1359 22 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
1360 : // create predefined report
1361 7 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchMechType, this->name, typeName);
1362 7 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchMechNomEff, this->name, this->referenceCOP);
1363 7 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchMechNomCap, this->name, this->referenceCapacity);
1364 : }
1365 :
1366 22 : if (errorsFound) {
1367 0 : ShowFatalError(state, "Preceding sizing errors cause program termination");
1368 : }
1369 22 : }
1370 :
1371 26 : void EIRPlantLoopHeatPump::sizeSrcSideASHP(EnergyPlusData &state)
1372 : {
1373 : // size the source-side for the air-source HP
1374 26 : bool errorsFound = false;
1375 :
1376 : // these variables will be used throughout this function as a temporary value of that physical state
1377 26 : Real64 tmpCapacity = this->referenceCapacity;
1378 26 : Real64 tmpLoadVolFlow = this->loadSideDesignVolFlowRate;
1379 26 : Real64 tmpSourceVolFlow = 0.0;
1380 :
1381 : // will leave like this for now
1382 : // need to update these to better values later
1383 26 : Real64 sourceSideInitTemp = 20;
1384 26 : Real64 sourceSideHumRat = 0.0;
1385 26 : if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating) {
1386 : // same here; update later
1387 8 : sourceSideInitTemp = 20;
1388 : }
1389 :
1390 26 : Real64 const rhoSrc = Psychrometrics::PsyRhoAirFnPbTdbW(state, state.dataEnvrn->StdBaroPress, sourceSideInitTemp, sourceSideHumRat);
1391 26 : Real64 const CpSrc = Psychrometrics::PsyCpAirFnW(sourceSideHumRat);
1392 :
1393 : // set the source-side flow rate
1394 26 : if (this->sourceSideDesignVolFlowRateWasAutoSized) {
1395 : // load-side capacity should already be set, so unless the flow rate is specified, we can set
1396 : // an assumed reasonable flow rate since this doesn't affect downstream components
1397 15 : Real64 DeltaT_src = 10;
1398 : // to get the source flow, we first must calculate the required heat impact on the source side
1399 : // First the definition of COP: COP = Qload/Power, therefore Power = Qload/COP
1400 : // Then the energy balance: Qsrc = Qload + Power
1401 : // Substituting for Power: Qsrc = Qload + Qload/COP, therefore Qsrc = Qload (1 + 1/COP)
1402 15 : Real64 designSourceSideHeatTransfer = 0.0;
1403 15 : if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating) {
1404 6 : designSourceSideHeatTransfer = tmpCapacity * (1 - 1 / this->referenceCOP);
1405 : } else {
1406 9 : designSourceSideHeatTransfer = tmpCapacity * (1 + 1 / this->referenceCOP);
1407 : }
1408 : // To get the design source flow rate, just apply the sensible heat rate equation:
1409 : // Qsrc = rho_src * Vdot_src * Cp_src * DeltaT_src
1410 : // Vdot_src = Q_src / (rho_src * Cp_src * DeltaT_src)
1411 15 : tmpSourceVolFlow = designSourceSideHeatTransfer / (rhoSrc * CpSrc * DeltaT_src);
1412 11 : } else if (!this->sourceSideDesignVolFlowRateWasAutoSized && this->sourceSideDesignVolFlowRate > 0) {
1413 : // given the value by the user
1414 : // set it directly
1415 11 : tmpSourceVolFlow = this->sourceSideDesignVolFlowRate;
1416 : } else if (!this->sourceSideDesignVolFlowRateWasAutoSized && this->sourceSideDesignVolFlowRate == 0) { // LCOV_EXCL_LINE
1417 : // user gave a flow rate of 0
1418 : // protected by the input processor to be >0.0
1419 : // fatal out just in case
1420 : errorsFound = true; // LCOV_EXCL_LINE
1421 0 : ShowSevereError(state,
1422 0 : format("Invalid condenser flow rate for EIR PLHP (name={}; entered value: {}",
1423 0 : this->name,
1424 : this->sourceSideDesignVolFlowRate)); // LCOV_EXCL_LINE
1425 : } else {
1426 : // can't imagine how it would ever get to this point
1427 : // just assume it's the same as the load side if we don't have any sizing information
1428 : tmpSourceVolFlow = tmpLoadVolFlow; // LCOV_EXCL_LINE
1429 : }
1430 :
1431 26 : if (this->companionHeatPumpCoil) {
1432 18 : tmpSourceVolFlow *= this->companionHeatPumpCoil->heatSizingRatio;
1433 : } else {
1434 8 : tmpSourceVolFlow *= this->heatSizingRatio;
1435 : }
1436 26 : this->sourceSideDesignVolFlowRate = tmpSourceVolFlow;
1437 26 : this->sourceSideDesignMassFlowRate = rhoSrc * this->sourceSideDesignVolFlowRate;
1438 :
1439 26 : std::string_view const typeName = DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)];
1440 26 : if (this->sourceSideDesignVolFlowRateWasAutoSized) {
1441 15 : this->sourceSideDesignVolFlowRate = tmpSourceVolFlow;
1442 15 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
1443 12 : BaseSizer::reportSizerOutput(state, typeName, this->name, "Design Size Source Side Volume Flow Rate [m3/s]", tmpSourceVolFlow);
1444 : }
1445 15 : if (state.dataPlnt->PlantFirstSizesOkayToReport) {
1446 12 : BaseSizer::reportSizerOutput(state, typeName, this->name, "Initial Design Size Source Side Volume Flow Rate [m3/s]", tmpSourceVolFlow);
1447 : }
1448 : } else {
1449 : // source design flow was hard-sized
1450 11 : if (this->sourceSideDesignVolFlowRate > 0.0) {
1451 11 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
1452 1 : if (state.dataGlobal->DoPlantSizing) {
1453 0 : BaseSizer::reportSizerOutput(state,
1454 : typeName,
1455 : this->name,
1456 : "Design Size Source Side Volume Flow Rate [m3/s]",
1457 : tmpSourceVolFlow,
1458 : "User-Specified Source Side Volume Flow Rate [m3/s]",
1459 0 : this->sourceSideDesignVolFlowRate);
1460 : } else {
1461 1 : BaseSizer::reportSizerOutput(
1462 : state, typeName, this->name, "User-Specified Source Side Volume Flow Rate [m3/s]", this->sourceSideDesignVolFlowRate);
1463 : }
1464 : }
1465 : }
1466 : }
1467 :
1468 26 : if (errorsFound) {
1469 : ShowFatalError(state, "Preceding sizing errors cause program termination"); // LCOV_EXCL_LINE
1470 : }
1471 26 : }
1472 :
1473 21 : void EIRPlantLoopHeatPump::sizeHeatRecoveryASHP(EnergyPlusData &state)
1474 : {
1475 : // size heat recovery side volume flow rate for air-source HP
1476 21 : if (!this->heatRecoveryAvailable) {
1477 15 : return;
1478 : }
1479 :
1480 : // these variables will be used throughout this function as a temporary value
1481 6 : Real64 tmpCapacity = this->referenceCapacity;
1482 6 : Real64 tmpLoadVolFlow = this->loadSideDesignVolFlowRate;
1483 6 : Real64 tmpHeatRecoveryVolFlow = 0.0;
1484 : // size the heat-recovery flow rate
1485 6 : std::string_view const typeName = DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)];
1486 6 : Real64 heatRecoveryInitTemp =
1487 6 : (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRCooling) ? Constant::HWInitConvTemp : Constant::CWInitConvTemp;
1488 : Real64 const rhoHR =
1489 6 : this->heatRecoveryPlantLoc.loop->glycol->getDensity(state, heatRecoveryInitTemp, "EIRPlantLoopHeatPump::sizeHeatRecoveryASHP()");
1490 : Real64 const CpHR =
1491 6 : this->heatRecoveryPlantLoc.loop->glycol->getSpecificHeat(state, heatRecoveryInitTemp, "EIRPlantLoopHeatPump::sizeHeatRecoveryASHP()");
1492 :
1493 : // calculate an auto-sized value for heat recovery design flow regardless of whether it was auto-sized or not
1494 6 : int plantHRSizingIndex = this->heatRecoveryPlantLoc.loop->PlantSizNum;
1495 6 : if (plantHRSizingIndex > 0) {
1496 : // Definition of COP: COP = Qload/Power, therefore Power = Qload/COP
1497 : // Energy balance: Qhr = Qload + Power = Qload(1 + 1/COP), cooling mode (recovers hot water)
1498 : // Qhr = Qload - Power = Qload(1 - 1/COP), heating mode (recovers chilled water)
1499 6 : Real64 designHeatRecoveryHeatTransfer = 0.0;
1500 6 : if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating) {
1501 3 : designHeatRecoveryHeatTransfer = tmpCapacity * (1 - 1 / this->referenceCOP);
1502 : } else {
1503 3 : designHeatRecoveryHeatTransfer = tmpCapacity * (1 + 1 / this->referenceCOP);
1504 : }
1505 : // calculate the design heat recovery flow rate, by applying the sensible heat rate equation:
1506 6 : tmpHeatRecoveryVolFlow = designHeatRecoveryHeatTransfer / (state.dataSize->PlantSizData(plantHRSizingIndex).DeltaT * CpHR * rhoHR);
1507 : // not sure about this
1508 : // if (this->airSource && this->heatRecoveryHeatPump) {
1509 : // // If component is on plant outlet branch, use plant flow rate
1510 : // tmpHeatRecoveryVolFlow = state.dataSize->PlantSizData(plantHRSizingIndex).DesVolFlowRate;
1511 : //}
1512 : } else {
1513 : // set it to the load side if there is plant sizing information
1514 0 : tmpHeatRecoveryVolFlow = tmpLoadVolFlow;
1515 : }
1516 : // check if the sizing ratio is based on the this->EIRHPType
1517 6 : if (this->companionHeatPumpCoil) {
1518 6 : tmpHeatRecoveryVolFlow *= this->companionHeatPumpCoil->heatSizingRatio;
1519 : } else {
1520 0 : tmpHeatRecoveryVolFlow *= this->heatSizingRatio;
1521 : }
1522 6 : this->heatRecoveryDesignMassFlowRate = rhoHR * this->heatRecoveryDesignVolFlowRate;
1523 :
1524 6 : if (this->heatRecoveryDesignVolFlowRateWasAutoSized) {
1525 6 : this->heatRecoveryDesignVolFlowRate = tmpHeatRecoveryVolFlow;
1526 6 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
1527 6 : BaseSizer::reportSizerOutput(
1528 : state, typeName, this->name, "Design Size Heat Recovery Side Volume Flow Rate [m3/s]", tmpHeatRecoveryVolFlow);
1529 : }
1530 6 : if (state.dataPlnt->PlantFirstSizesOkayToReport) {
1531 6 : BaseSizer::reportSizerOutput(
1532 : state, typeName, this->name, "Initial Design Size Heat Recovery Side Volume Flow Rate [m3/s]", tmpHeatRecoveryVolFlow);
1533 : }
1534 : } else {
1535 : // heat recovery design volume flow rate was hard-sized
1536 0 : if (this->heatRecoveryDesignVolFlowRate > 0.0 && tmpHeatRecoveryVolFlow > 0.0) {
1537 0 : Real64 const hardSizedHeatRecoveryFlow = this->heatRecoveryDesignVolFlowRate;
1538 0 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
1539 0 : if (state.dataGlobal->DoPlantSizing) {
1540 0 : BaseSizer::reportSizerOutput(state,
1541 : typeName,
1542 : this->name,
1543 : "Design Size Heat Recovery Side Volume Flow Rate [m3/s]",
1544 : tmpHeatRecoveryVolFlow,
1545 : "User-Specified Heat Recovery Side Volume Flow Rate [m3/s]",
1546 : hardSizedHeatRecoveryFlow);
1547 : } else {
1548 0 : BaseSizer::reportSizerOutput(
1549 : state, typeName, this->name, "User-Specified Heat Recovery Side Volume Flow Rate [m3/s]", hardSizedHeatRecoveryFlow);
1550 : }
1551 0 : if (state.dataGlobal->DisplayExtraWarnings) {
1552 0 : if ((std::abs(tmpHeatRecoveryVolFlow - hardSizedHeatRecoveryFlow) / hardSizedHeatRecoveryFlow) >
1553 0 : state.dataSize->AutoVsHardSizingThreshold) {
1554 0 : ShowMessage(state, format("EIRPlantLoopHeatPump::size(): Potential issue with equipment sizing for {}", this->name));
1555 0 : ShowContinueError(state,
1556 0 : format("User-Specified Heat Recovery Side Volume Flow Rate of {:.2R} [m3/s]", hardSizedHeatRecoveryFlow));
1557 0 : ShowContinueError(
1558 0 : state, format("differs from Design Size Heat Recovery Side Volume Flow Rate of {:.2R} [m3/s]", tmpHeatRecoveryVolFlow));
1559 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
1560 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
1561 : }
1562 : }
1563 : }
1564 : }
1565 : }
1566 : }
1567 :
1568 56 : PlantComponent *EIRPlantLoopHeatPump::factory(EnergyPlusData &state, DataPlant::PlantEquipmentType hp_type, const std::string &hp_name)
1569 : {
1570 56 : if (state.dataEIRPlantLoopHeatPump->getInputsPLHP) {
1571 40 : EIRPlantLoopHeatPump::processInputForEIRPLHP(state);
1572 38 : EIRPlantLoopHeatPump::pairUpCompanionCoils(state);
1573 38 : state.dataEIRPlantLoopHeatPump->getInputsPLHP = false;
1574 : }
1575 :
1576 80 : for (auto &plhp : state.dataEIRPlantLoopHeatPump->heatPumps) {
1577 64 : if (plhp.name == Util::makeUPPER(hp_name) && plhp.EIRHPType == hp_type) {
1578 38 : return &plhp;
1579 : }
1580 : }
1581 :
1582 32 : ShowFatalError(state, format("EIR Plant Loop Heat Pump factory: Error getting inputs for PLHP named: {}", hp_name));
1583 : return nullptr; // LCOV_EXCL_LINE
1584 : }
1585 :
1586 41 : void EIRPlantLoopHeatPump::pairUpCompanionCoils(EnergyPlusData &state)
1587 : {
1588 95 : for (auto &thisHP : state.dataEIRPlantLoopHeatPump->heatPumps) {
1589 56 : if (!thisHP.companionCoilName.empty()) {
1590 32 : std::string const thisCoilName = Util::makeUPPER(thisHP.name);
1591 32 : DataPlant::PlantEquipmentType thisCoilType = thisHP.EIRHPType;
1592 32 : std::string const targetCompanionName = Util::makeUPPER(thisHP.companionCoilName);
1593 50 : for (auto &potentialCompanionCoil : state.dataEIRPlantLoopHeatPump->heatPumps) {
1594 49 : DataPlant::PlantEquipmentType potentialCompanionType = potentialCompanionCoil.EIRHPType;
1595 49 : std::string potentialCompanionName = Util::makeUPPER(potentialCompanionCoil.name);
1596 49 : if (potentialCompanionName == thisCoilName) {
1597 : // skip the current coil
1598 17 : continue;
1599 : }
1600 32 : if (potentialCompanionName == targetCompanionName) {
1601 31 : if (thisCoilType == potentialCompanionType) {
1602 1 : ShowSevereError(state, format("Invalid companion specification for EIR Plant Loop Heat Pump named \"{}\"", thisCoilName));
1603 2 : ShowContinueError(state, "For heating objects, the companion must be a cooling object, and vice-versa");
1604 3 : ShowFatalError(state, "Invalid companion object causes program termination");
1605 : }
1606 30 : thisHP.companionHeatPumpCoil = &potentialCompanionCoil;
1607 30 : break;
1608 : }
1609 49 : }
1610 31 : if (!thisHP.companionHeatPumpCoil) {
1611 2 : ShowSevereError(state, "Could not find matching companion heat pump coil.");
1612 1 : ShowContinueError(state, format("Base coil: {}", thisCoilName));
1613 1 : ShowContinueError(state, format("Looking for companion coil named: {}", targetCompanionName));
1614 3 : ShowFatalError(state, "Simulation aborts due to previous severe error");
1615 : }
1616 34 : }
1617 : }
1618 39 : }
1619 :
1620 40 : void EIRPlantLoopHeatPump::processInputForEIRPLHP(EnergyPlusData &state)
1621 : {
1622 :
1623 : struct ClassType
1624 : {
1625 : DataPlant::PlantEquipmentType thisType;
1626 : std::string nodesType;
1627 : std::function<Real64(Real64, Real64)> calcLoadOutletTemp;
1628 : std::function<Real64(Real64, Real64)> calcQsource;
1629 : std::function<Real64(Real64, Real64)> calcSourceOutletTemp;
1630 : std::function<Real64(Real64, Real64)> calcQheatRecovery;
1631 : std::function<Real64(Real64, Real64)> calcHROutletTemp;
1632 :
1633 80 : ClassType(DataPlant::PlantEquipmentType _thisType,
1634 : std::string _nodesType,
1635 : std::function<Real64(Real64, Real64)> _tLoadOutFunc,
1636 : std::function<Real64(Real64, Real64)> _qSrcFunc,
1637 : std::function<Real64(Real64, Real64)> _tSrcOutFunc,
1638 : std::function<Real64(Real64, Real64)> _qHeatRecovery,
1639 : std::function<Real64(Real64, Real64)> _tHROutFunc)
1640 80 : : thisType(_thisType), nodesType(std::move(_nodesType)), calcLoadOutletTemp(_tLoadOutFunc), calcQsource(_qSrcFunc),
1641 80 : calcSourceOutletTemp(_tSrcOutFunc), calcQheatRecovery(_qHeatRecovery), calcHROutletTemp(_tHROutFunc)
1642 : {
1643 80 : }
1644 : };
1645 : std::array<ClassType, 2> classesToInput = {ClassType{DataPlant::PlantEquipmentType::HeatPumpEIRCooling,
1646 : "Chilled Water Nodes",
1647 : EIRPlantLoopHeatPumps::EIRPlantLoopHeatPump::subtract,
1648 : EIRPlantLoopHeatPumps::EIRPlantLoopHeatPump::add,
1649 : EIRPlantLoopHeatPumps::EIRPlantLoopHeatPump::add,
1650 : EIRPlantLoopHeatPumps::EIRPlantLoopHeatPump::add,
1651 : EIRPlantLoopHeatPumps::EIRPlantLoopHeatPump::add},
1652 : ClassType{DataPlant::PlantEquipmentType::HeatPumpEIRHeating,
1653 : "Hot Water Nodes",
1654 : EIRPlantLoopHeatPumps::EIRPlantLoopHeatPump::add,
1655 : EIRPlantLoopHeatPumps::EIRPlantLoopHeatPump::subtract,
1656 : EIRPlantLoopHeatPumps::EIRPlantLoopHeatPump::subtract,
1657 : EIRPlantLoopHeatPumps::EIRPlantLoopHeatPump::subtract,
1658 80 : EIRPlantLoopHeatPumps::EIRPlantLoopHeatPump::subtract}};
1659 :
1660 40 : bool errorsFound = false;
1661 40 : std::string &cCurrentModuleObject = state.dataIPShortCut->cCurrentModuleObject;
1662 120 : for (auto const &classToInput : classesToInput) {
1663 80 : cCurrentModuleObject = DataPlant::PlantEquipTypeNames[static_cast<int>(classToInput.thisType)];
1664 : DataLoopNode::ConnectionObjectType objType = static_cast<DataLoopNode::ConnectionObjectType>(
1665 80 : getEnumValue(BranchNodeConnections::ConnectionObjectTypeNamesUC, Util::makeUPPER(cCurrentModuleObject)));
1666 80 : int numPLHP = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, cCurrentModuleObject);
1667 80 : if (numPLHP > 0) {
1668 54 : auto const instances = state.dataInputProcessing->inputProcessor->epJSON.find(cCurrentModuleObject);
1669 54 : if (instances == state.dataInputProcessing->inputProcessor->epJSON.end()) continue;
1670 54 : auto &instancesValue = instances.value();
1671 54 : auto const &schemaProps = state.dataInputProcessing->inputProcessor->getObjectSchemaProps(state, cCurrentModuleObject);
1672 108 : for (auto instance = instancesValue.begin(); instance != instancesValue.end(); ++instance) {
1673 54 : auto const &fields = instance.value();
1674 54 : std::string const &thisObjectName = instance.key();
1675 54 : state.dataInputProcessing->inputProcessor->markObjectAsUsed(cCurrentModuleObject, thisObjectName);
1676 :
1677 54 : EIRPlantLoopHeatPump thisPLHP;
1678 54 : thisPLHP.EIRHPType = classToInput.thisType;
1679 54 : thisPLHP.name = Util::makeUPPER(thisObjectName);
1680 108 : std::string loadSideInletNodeName = Util::makeUPPER(fields.at("load_side_inlet_node_name").get<std::string>());
1681 108 : std::string loadSideOutletNodeName = Util::makeUPPER(fields.at("load_side_outlet_node_name").get<std::string>());
1682 108 : std::string condenserType = Util::makeUPPER(fields.at("condenser_type").get<std::string>());
1683 108 : std::string sourceSideInletNodeName = Util::makeUPPER(fields.at("source_side_inlet_node_name").get<std::string>());
1684 54 : std::string sourceSideOutletNodeName = Util::makeUPPER(fields.at("source_side_outlet_node_name").get<std::string>());
1685 : thisPLHP.companionCoilName =
1686 108 : Util::makeUPPER(state.dataInputProcessing->inputProcessor->getAlphaFieldValue(fields, schemaProps, "companion_heat_pump_name"));
1687 :
1688 54 : thisPLHP.loadSideDesignVolFlowRate =
1689 108 : state.dataInputProcessing->inputProcessor->getRealFieldValue(fields, schemaProps, "load_side_reference_flow_rate");
1690 54 : if (thisPLHP.loadSideDesignVolFlowRate == DataSizing::AutoSize) {
1691 16 : thisPLHP.loadSideDesignVolFlowRateWasAutoSized = true;
1692 : }
1693 :
1694 54 : thisPLHP.sourceSideDesignVolFlowRate =
1695 108 : state.dataInputProcessing->inputProcessor->getRealFieldValue(fields, schemaProps, "source_side_reference_flow_rate");
1696 54 : if (thisPLHP.sourceSideDesignVolFlowRate == DataSizing::AutoSize) {
1697 24 : thisPLHP.sourceSideDesignVolFlowRateWasAutoSized = true;
1698 : }
1699 :
1700 108 : thisPLHP.referenceCapacity = state.dataInputProcessing->inputProcessor->getRealFieldValue(fields, schemaProps, "reference_capacity");
1701 54 : if (thisPLHP.referenceCapacity == DataSizing::AutoSize) {
1702 26 : thisPLHP.referenceCapacityWasAutoSized = true;
1703 : }
1704 :
1705 54 : thisPLHP.referenceCOP =
1706 108 : state.dataInputProcessing->inputProcessor->getRealFieldValue(fields, schemaProps, "reference_coefficient_of_performance");
1707 :
1708 162 : thisPLHP.sizingFactor = state.dataInputProcessing->inputProcessor->getRealFieldValue(fields, schemaProps, "sizing_factor");
1709 :
1710 54 : std::string const capFtName = Util::makeUPPER(fields.at("capacity_modifier_function_of_temperature_curve_name").get<std::string>());
1711 54 : thisPLHP.capFuncTempCurveIndex = Curve::GetCurveIndex(state, capFtName);
1712 54 : if (thisPLHP.capFuncTempCurveIndex == 0) {
1713 1 : ShowSevereError(state, format("Invalid curve name for EIR PLHP (name={}; entered curve name: {}", thisPLHP.name, capFtName));
1714 1 : errorsFound = true;
1715 : }
1716 :
1717 : std::string const eirFtName =
1718 54 : Util::makeUPPER(fields.at("electric_input_to_output_ratio_modifier_function_of_temperature_curve_name").get<std::string>());
1719 54 : thisPLHP.powerRatioFuncTempCurveIndex = Curve::GetCurveIndex(state, eirFtName);
1720 54 : if (thisPLHP.powerRatioFuncTempCurveIndex == 0) {
1721 1 : ShowSevereError(state, format("Invalid curve name for EIR PLHP (name={}; entered curve name: {}", thisPLHP.name, eirFtName));
1722 1 : errorsFound = true;
1723 : }
1724 :
1725 : std::string const eirFplrName =
1726 54 : Util::makeUPPER(fields.at("electric_input_to_output_ratio_modifier_function_of_part_load_ratio_curve_name").get<std::string>());
1727 54 : thisPLHP.powerRatioFuncPLRCurveIndex = Curve::GetCurveIndex(state, eirFplrName);
1728 54 : if (thisPLHP.powerRatioFuncPLRCurveIndex == 0) {
1729 1 : ShowSevereError(state, format("Invalid curve name for EIR PLHP (name={}; entered curve name: {}", thisPLHP.name, eirFplrName));
1730 1 : errorsFound = true;
1731 : }
1732 :
1733 : // inputs are past min-fields
1734 : // fields common to both objects
1735 108 : thisPLHP.minimumPLR = state.dataInputProcessing->inputProcessor->getRealFieldValue(fields, schemaProps, "minimum_part_load_ratio");
1736 54 : thisPLHP.minSourceTempLimit =
1737 108 : state.dataInputProcessing->inputProcessor->getRealFieldValue(fields, schemaProps, "minimum_source_inlet_temperature");
1738 54 : thisPLHP.maxSourceTempLimit =
1739 162 : state.dataInputProcessing->inputProcessor->getRealFieldValue(fields, schemaProps, "maximum_source_inlet_temperature");
1740 :
1741 54 : auto const minimumSupplyWaterTempCurveName = fields.find("minimum_supply_water_temperature_curve_name");
1742 54 : if (minimumSupplyWaterTempCurveName != fields.end()) {
1743 0 : thisPLHP.minSupplyWaterTempCurveIndex =
1744 0 : Curve::GetCurveIndex(state, Util::makeUPPER(minimumSupplyWaterTempCurveName.value().get<std::string>()));
1745 : }
1746 :
1747 54 : auto const maximumSupplyWaterTempCurveName = fields.find("maximum_supply_water_temperature_curve_name");
1748 54 : if (maximumSupplyWaterTempCurveName != fields.end()) {
1749 0 : thisPLHP.maxSupplyWaterTempCurveIndex =
1750 0 : Curve::GetCurveIndex(state, Util::makeUPPER(maximumSupplyWaterTempCurveName.value().get<std::string>()));
1751 : }
1752 : // fields only in cooling object
1753 54 : if (thisPLHP.EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRCooling) {
1754 33 : auto const thermosiphonTempCurveName = fields.find("thermosiphon_capacity_fraction_curve_name");
1755 33 : if (thermosiphonTempCurveName != fields.end()) {
1756 1 : thisPLHP.thermosiphonTempCurveIndex =
1757 1 : Curve::GetCurveIndex(state, Util::makeUPPER(thermosiphonTempCurveName.value().get<std::string>()));
1758 1 : if (thisPLHP.thermosiphonTempCurveIndex == 0) {
1759 0 : ShowSevereError(state, format("{} =\"{}\"", state.dataIPShortCut->cCurrentModuleObject, thisPLHP.name));
1760 0 : ShowContinueError(state,
1761 0 : format("Invalid Thermosiphon Capacity Fraction Curve Name = {}",
1762 0 : thermosiphonTempCurveName.value().get<std::string>()));
1763 0 : errorsFound = true;
1764 : }
1765 : }
1766 99 : thisPLHP.thermosiphonMinTempDiff = state.dataInputProcessing->inputProcessor->getRealFieldValue(
1767 : fields, schemaProps, "thermosiphon_minimum_temperature_difference");
1768 : }
1769 :
1770 : std::string flowControlTypeName =
1771 108 : Util::makeUPPER(state.dataInputProcessing->inputProcessor->getAlphaFieldValue(fields, schemaProps, "flow_mode"));
1772 54 : thisPLHP.flowControl = static_cast<DataPlant::FlowMode>(getEnumValue(DataPlant::FlowModeNamesUC, flowControlTypeName));
1773 :
1774 : // fields only in heating object
1775 54 : if (thisPLHP.EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating) {
1776 21 : thisPLHP.heatSizingRatio =
1777 42 : state.dataInputProcessing->inputProcessor->getRealFieldValue(fields, schemaProps, "heating_to_cooling_capacity_sizing_ratio");
1778 63 : thisPLHP.maxOutdoorTemperatureDefrost = state.dataInputProcessing->inputProcessor->getRealFieldValue(
1779 : fields, schemaProps, "maximum_outdoor_dry_bulb_temperature_for_defrost_operation");
1780 : }
1781 :
1782 54 : constexpr std::array<std::string_view, static_cast<int>(HeatSizingType::Num)> PLHPHeatSizTypeNamesUC = {
1783 : "HEATINGCAPACITY", "COOLINGCAPACITY", "GREATEROFHEATINGORCOOLING"};
1784 54 : auto const heatSizingType = fields.find("heat_pump_sizing_method");
1785 54 : if (heatSizingType != fields.end()) {
1786 0 : thisPLHP.heatSizingMethod =
1787 0 : static_cast<HeatSizingType>(getEnumValue(PLHPHeatSizTypeNamesUC, Util::makeUPPER(heatSizingType.value().get<std::string>())));
1788 : } else {
1789 : // revert to legacy sizing method, if no companion coil and this coil type is heating, set to heating
1790 54 : if (thisPLHP.companionCoilName.empty() && thisPLHP.EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating) {
1791 7 : thisPLHP.heatSizingMethod = HeatSizingType::Heating;
1792 : } else {
1793 47 : thisPLHP.heatSizingMethod = HeatSizingType::Cooling;
1794 : }
1795 : }
1796 :
1797 54 : constexpr std::array<std::string_view, static_cast<int>(ControlType::Num)> PLHPCtrlTypeNamesUC = {"SETPOINT", "LOAD"};
1798 54 : auto const controlType = fields.find("control_type");
1799 54 : if (controlType != fields.end()) {
1800 0 : thisPLHP.sysControlType =
1801 0 : static_cast<ControlType>(getEnumValue(PLHPCtrlTypeNamesUC, Util::makeUPPER(controlType.value().get<std::string>())));
1802 : } else {
1803 54 : thisPLHP.sysControlType = ControlType::Load;
1804 : }
1805 54 : auto const capacityDryAirCurveName = fields.find("dry_outdoor_correction_factor_curve_name");
1806 54 : if (capacityDryAirCurveName != fields.end()) {
1807 0 : thisPLHP.capacityDryAirCurveIndex =
1808 0 : Curve::GetCurveIndex(state, Util::makeUPPER(capacityDryAirCurveName.value().get<std::string>()));
1809 : }
1810 :
1811 54 : constexpr std::array<std::string_view, static_cast<int>(DefrostControl::Num)> PLHPDefrostTypeNamesUC = {
1812 : "NONE", "TIMED", "ONDEMAND", "TIMEDEMPIRICAL"};
1813 54 : auto const defrostControlStrategy = fields.find("heat_pump_defrost_control");
1814 54 : if (defrostControlStrategy != fields.end()) {
1815 0 : thisPLHP.defrostStrategy = static_cast<DefrostControl>(
1816 0 : getEnumValue(PLHPDefrostTypeNamesUC, Util::makeUPPER(defrostControlStrategy.value().get<std::string>())));
1817 : } else {
1818 54 : thisPLHP.defrostStrategy = DefrostControl::None;
1819 : }
1820 :
1821 54 : if (thisPLHP.EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating &&
1822 21 : (thisPLHP.defrostStrategy == DefrostControl::Timed || thisPLHP.defrostStrategy == DefrostControl::TimedEmpirical)) {
1823 0 : auto const timePeriod = fields.find("heat_pump_defrost_time_period_fraction");
1824 0 : if (timePeriod != fields.end()) {
1825 0 : thisPLHP.defrostTime = timePeriod.value().get<Real64>();
1826 : } else {
1827 0 : Real64 defaultVal = 0.0;
1828 0 : if (!state.dataInputProcessing->inputProcessor->getDefaultValue(
1829 : state, cCurrentModuleObject, "heat_pump_defrost_time_period_fraction", defaultVal)) {
1830 : // excluding from coverage
1831 : ShowSevereError(state, // LCOV_EXCL_LINE
1832 0 : format("EIR PLHP \"{}\": Heat Pump Defrost Time Period Fraction not entered and default value not found.",
1833 : thisPLHP.name)); // LCOV_EXCL_LINE
1834 : errorsFound = true; // LCOV_EXCL_LINE
1835 : } else {
1836 0 : thisPLHP.defrostTime = defaultVal;
1837 : }
1838 : }
1839 : }
1840 :
1841 54 : if (thisPLHP.defrostStrategy == DefrostControl::TimedEmpirical) {
1842 0 : auto const timedEmpiricalDefFreqStratCurveName = fields.find("timed_empirical_defrost_frequency_curve_name");
1843 0 : if (timedEmpiricalDefFreqStratCurveName != fields.end()) {
1844 0 : thisPLHP.defrostFreqCurveIndex =
1845 0 : Curve::GetCurveIndex(state, Util::makeUPPER(timedEmpiricalDefFreqStratCurveName.value().get<std::string>()));
1846 : }
1847 0 : auto const timedEmpiricalDefHeatLoadPenaltyCurveName = fields.find("timed_empirical_defrost_heat_load_penalty_curve_name");
1848 0 : if (timedEmpiricalDefHeatLoadPenaltyCurveName != fields.end()) {
1849 0 : thisPLHP.defrostHeatLoadCurveIndex =
1850 0 : Curve::GetCurveIndex(state, Util::makeUPPER(timedEmpiricalDefHeatLoadPenaltyCurveName.value().get<std::string>()));
1851 0 : thisPLHP.defrostLoadCurveDims = state.dataCurveManager->curves(thisPLHP.defrostHeatLoadCurveIndex)->numDims;
1852 : }
1853 0 : auto const defrostHeatEnergyCurveIndexCurveName = fields.find("timed_empirical_defrost_heat_input_energy_fraction_curve_name");
1854 0 : if (defrostHeatEnergyCurveIndexCurveName != fields.end()) {
1855 0 : thisPLHP.defrostHeatEnergyCurveIndex =
1856 0 : Curve::GetCurveIndex(state, Util::makeUPPER(defrostHeatEnergyCurveIndexCurveName.value().get<std::string>()));
1857 0 : thisPLHP.defrostEnergyCurveDims = state.dataCurveManager->curves(thisPLHP.defrostHeatEnergyCurveIndex)->numDims;
1858 : }
1859 54 : } else if (thisPLHP.EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating) { // used for Timed or OnDemand
1860 21 : auto const defEIRFTCurveName = fields.find("defrost_energy_input_ratio_function_of_temperature_curve_name");
1861 21 : if (defEIRFTCurveName != fields.end()) {
1862 0 : thisPLHP.defrostEIRFTIndex = Curve::GetCurveIndex(state, Util::makeUPPER(defEIRFTCurveName.value().get<std::string>()));
1863 : }
1864 : }
1865 :
1866 54 : bool nodeErrorsFound = false;
1867 54 : thisPLHP.loadSideNodes.inlet = NodeInputManager::GetOnlySingleNode(state,
1868 : loadSideInletNodeName,
1869 : nodeErrorsFound,
1870 : objType,
1871 : thisPLHP.name,
1872 : DataLoopNode::NodeFluidType::Water,
1873 : DataLoopNode::ConnectionType::Inlet,
1874 : NodeInputManager::CompFluidStream::Primary,
1875 : DataLoopNode::ObjectIsNotParent);
1876 54 : thisPLHP.loadSideNodes.outlet = NodeInputManager::GetOnlySingleNode(state,
1877 : loadSideOutletNodeName,
1878 : nodeErrorsFound,
1879 : objType,
1880 : thisPLHP.name,
1881 : DataLoopNode::NodeFluidType::Water,
1882 : DataLoopNode::ConnectionType::Outlet,
1883 : NodeInputManager::CompFluidStream::Primary,
1884 : DataLoopNode::ObjectIsNotParent);
1885 54 : DataLoopNode::NodeFluidType condenserNodeType = DataLoopNode::NodeFluidType::Blank;
1886 54 : DataLoopNode::ConnectionType condenserNodeConnectionType_Inlet = DataLoopNode::ConnectionType::Blank;
1887 54 : DataLoopNode::ConnectionType condenserNodeConnectionType_Outlet = DataLoopNode::ConnectionType::Blank;
1888 54 : if (condenserType == "WATERSOURCE") {
1889 29 : thisPLHP.waterSource = true;
1890 29 : condenserNodeType = DataLoopNode::NodeFluidType::Water;
1891 29 : condenserNodeConnectionType_Inlet = DataLoopNode::ConnectionType::Inlet;
1892 29 : condenserNodeConnectionType_Outlet = DataLoopNode::ConnectionType::Outlet;
1893 25 : } else if (condenserType == "AIRSOURCE") {
1894 25 : thisPLHP.airSource = true;
1895 25 : condenserNodeType = DataLoopNode::NodeFluidType::Air;
1896 25 : condenserNodeConnectionType_Inlet = DataLoopNode::ConnectionType::Inlet;
1897 25 : condenserNodeConnectionType_Outlet = DataLoopNode::ConnectionType::Outlet;
1898 25 : if (sourceSideInletNodeName == sourceSideOutletNodeName) {
1899 1 : ShowSevereError(state, format("PlantLoopHeatPump {} has the same inlet and outlet node.", thisObjectName));
1900 1 : ShowContinueError(state, format("Node Name: {}", sourceSideInletNodeName));
1901 1 : errorsFound = true;
1902 : }
1903 : } else {
1904 : // Again, this should be protected by the input processor
1905 0 : ShowErrorMessage(
1906 : state, format("Invalid heat pump condenser type (name={}; entered type: {}", thisPLHP.name, condenserType)); // LCOV_EXCL_LINE
1907 : errorsFound = true; // LCOV_EXCL_LINE
1908 : }
1909 54 : thisPLHP.sourceSideNodes.inlet = NodeInputManager::GetOnlySingleNode(state,
1910 : sourceSideInletNodeName,
1911 : nodeErrorsFound,
1912 : objType,
1913 : thisPLHP.name,
1914 : condenserNodeType,
1915 : condenserNodeConnectionType_Inlet,
1916 : NodeInputManager::CompFluidStream::Secondary,
1917 : DataLoopNode::ObjectIsNotParent);
1918 54 : thisPLHP.sourceSideNodes.outlet = NodeInputManager::GetOnlySingleNode(state,
1919 : sourceSideOutletNodeName,
1920 : nodeErrorsFound,
1921 : objType,
1922 : thisPLHP.name,
1923 : condenserNodeType,
1924 : condenserNodeConnectionType_Outlet,
1925 : NodeInputManager::CompFluidStream::Secondary,
1926 : DataLoopNode::ObjectIsNotParent);
1927 :
1928 : // heat recovery inputs
1929 54 : std::string heatRecoveryInletNodeName;
1930 54 : std::string heatRecoveryOutletNodeName;
1931 108 : auto const hrInletNodeName = fields.find("heat_recovery_inlet_node_name");
1932 54 : auto const hrOutletNodeName = fields.find("heat_recovery_outlet_node_name");
1933 54 : if (hrInletNodeName != fields.end() && hrOutletNodeName != fields.end()) {
1934 16 : heatRecoveryInletNodeName = Util::makeUPPER(fields.at("heat_recovery_inlet_node_name").get<std::string>());
1935 8 : heatRecoveryOutletNodeName = Util::makeUPPER(fields.at("heat_recovery_outlet_node_name").get<std::string>());
1936 8 : thisPLHP.heatRecoveryAvailable = true;
1937 : } else {
1938 46 : thisPLHP.heatRecoveryAvailable = false;
1939 : }
1940 :
1941 54 : if (thisPLHP.airSource && thisPLHP.heatRecoveryAvailable) {
1942 8 : thisPLHP.heatRecoveryNodes.inlet = NodeInputManager::GetOnlySingleNode(state,
1943 : heatRecoveryInletNodeName,
1944 : nodeErrorsFound,
1945 : objType,
1946 : thisPLHP.name,
1947 : DataLoopNode::NodeFluidType::Water,
1948 : DataLoopNode::ConnectionType::Inlet,
1949 : NodeInputManager::CompFluidStream::Tertiary,
1950 : DataLoopNode::ObjectIsNotParent);
1951 8 : thisPLHP.heatRecoveryNodes.outlet = NodeInputManager::GetOnlySingleNode(state,
1952 : heatRecoveryOutletNodeName,
1953 : nodeErrorsFound,
1954 : objType,
1955 : thisPLHP.name,
1956 : DataLoopNode::NodeFluidType::Water,
1957 : DataLoopNode::ConnectionType::Outlet,
1958 : NodeInputManager::CompFluidStream::Tertiary,
1959 : DataLoopNode::ObjectIsNotParent);
1960 :
1961 8 : thisPLHP.heatRecoveryDesignVolFlowRate =
1962 16 : state.dataInputProcessing->inputProcessor->getRealFieldValue(fields, schemaProps, "heat_recovery_reference_flow_rate");
1963 8 : if (thisPLHP.heatRecoveryDesignVolFlowRate == DataSizing::AutoSize) {
1964 8 : thisPLHP.heatRecoveryDesignVolFlowRateWasAutoSized = true;
1965 : }
1966 :
1967 : // fields only in cooling object
1968 8 : if (thisPLHP.heatRecoveryAvailable && thisPLHP.EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRCooling) {
1969 12 : thisPLHP.maxHeatRecoveryTempLimit = state.dataInputProcessing->inputProcessor->getRealFieldValue(
1970 : fields, schemaProps, "maximum_heat_recovery_outlet_temperature");
1971 : }
1972 : // fields only in heating object
1973 8 : if (thisPLHP.heatRecoveryAvailable && thisPLHP.EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating) {
1974 12 : thisPLHP.minHeatRecoveryTempLimit = state.dataInputProcessing->inputProcessor->getRealFieldValue(
1975 : fields, schemaProps, "minimum_heat_recovery_outlet_temperature");
1976 : }
1977 : }
1978 :
1979 54 : if (nodeErrorsFound) errorsFound = true;
1980 54 : BranchNodeConnections::TestCompSet(
1981 54 : state, cCurrentModuleObject, thisPLHP.name, loadSideInletNodeName, loadSideOutletNodeName, classToInput.nodesType);
1982 :
1983 54 : if (thisPLHP.waterSource) {
1984 58 : BranchNodeConnections::TestCompSet(
1985 : state, cCurrentModuleObject, thisPLHP.name, sourceSideInletNodeName, sourceSideOutletNodeName, "Condenser Water Nodes");
1986 : }
1987 :
1988 54 : if (thisPLHP.airSource && thisPLHP.heatRecoveryAvailable) {
1989 16 : BranchNodeConnections::TestCompSet(state,
1990 : cCurrentModuleObject,
1991 : thisPLHP.name,
1992 : heatRecoveryInletNodeName,
1993 : heatRecoveryOutletNodeName,
1994 : "Heat Recovery Water Nodes");
1995 :
1996 8 : auto const heatRecoveryCapFTempCurveName = fields.find("heat_recovery_capacity_modifier_function_of_temperature_curve_name");
1997 8 : if (heatRecoveryCapFTempCurveName != fields.end()) {
1998 0 : thisPLHP.heatRecoveryCapFTempCurveIndex =
1999 0 : Curve::GetCurveIndex(state, Util::makeUPPER(heatRecoveryCapFTempCurveName.value().get<std::string>()));
2000 : }
2001 : auto const heatRecoveryEIRFTempCurveName =
2002 8 : fields.find("heat_recovery_electric_input_to_output_ratio_modifier_function_of_temperature_curve_name");
2003 8 : if (heatRecoveryEIRFTempCurveName != fields.end()) {
2004 0 : thisPLHP.heatRecoveryEIRFTempCurveIndex =
2005 0 : Curve::GetCurveIndex(state, Util::makeUPPER(heatRecoveryEIRFTempCurveName.value().get<std::string>()));
2006 : }
2007 : }
2008 :
2009 54 : if (thisPLHP.airSource && thisPLHP.EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating &&
2010 9 : thisPLHP.defrostStrategy != DefrostControl::None) {
2011 0 : thisPLHP.defrostAvailable = true;
2012 : }
2013 : // store the worker functions that generalized the heating/cooling sides
2014 54 : thisPLHP.calcLoadOutletTemp = classToInput.calcLoadOutletTemp;
2015 54 : thisPLHP.calcQsource = classToInput.calcQsource;
2016 54 : thisPLHP.calcSourceOutletTemp = classToInput.calcSourceOutletTemp;
2017 : // heat recovery
2018 54 : thisPLHP.calcQheatRecovery = classToInput.calcQheatRecovery;
2019 54 : thisPLHP.calcHROutletTemp = classToInput.calcHROutletTemp;
2020 :
2021 54 : if (!errorsFound) {
2022 52 : state.dataEIRPlantLoopHeatPump->heatPumps.push_back(thisPLHP);
2023 : }
2024 54 : }
2025 : }
2026 : }
2027 40 : if (errorsFound) {
2028 : // currently there are no straightforward unit tests possible to get here
2029 : // all curves are required and inputs are validated by the input processor
2030 : // obviously this will stay here but I don't feel like counting it against coverage
2031 : ShowFatalError(state, "Previous EIR PLHP errors cause program termination"); // LCOV_EXCL_LINE
2032 : }
2033 40 : }
2034 :
2035 32525 : void EIRPlantLoopHeatPump::checkConcurrentOperation(EnergyPlusData &state)
2036 : {
2037 : // This will do a recurring warning for concurrent companion operation.
2038 : // This function should be called at the end of the time-step to ensure any iteration-level operation
2039 : // is worked out and the results are final.
2040 : // This function does not try to be intelligent about only reporting for one of the companions. The only
2041 : // way I could think of was to have a vector, either static here or in the namespace, that would hold
2042 : // companion index values as I warn against their partner, so then I would have to add the values to the
2043 : // vector each pass, and check then each loop. This seemed really bulky and inefficient, so I chose to
2044 : // leave a tight loop here of just reporting for each coil if it and the companion are running.
2045 32529 : for (auto &thisPLHP : state.dataEIRPlantLoopHeatPump->heatPumps) {
2046 4 : if (!thisPLHP.companionHeatPumpCoil) {
2047 2 : continue;
2048 : }
2049 2 : if (thisPLHP.running && thisPLHP.companionHeatPumpCoil->running && !thisPLHP.companionHeatPumpCoil->heatRecoveryAvailable) {
2050 16 : ShowRecurringWarningErrorAtEnd(state,
2051 2 : "Companion heat pump objects running concurrently, check operation. Base object name: " + thisPLHP.name,
2052 2 : thisPLHP.recurringConcurrentOperationWarningIndex);
2053 : }
2054 : }
2055 32525 : }
2056 :
2057 36 : void EIRPlantLoopHeatPump::isPlantInletOrOutlet(EnergyPlusData &state)
2058 : {
2059 : // check to see if component is on a plant inlet or outlet branch to determine if flow should be registered
2060 : // only components on plant parallel component branches should be registered
2061 : // this check for the load side on a plant inlet branch and source side on a plant outlet branch
2062 : // likely will need more checking here but this works for now with existing test file
2063 36 : bool loadSideIsPlantInlet = false;
2064 36 : bool sourceSideIsPlantOutlet = false;
2065 93 : for (auto thisPlant : state.dataPlnt->PlantLoop) {
2066 171 : for (auto thisLoopSide : thisPlant.LoopSide) {
2067 114 : if (this->loadSideNodes.inlet == thisLoopSide.NodeNumIn) {
2068 0 : loadSideIsPlantInlet = true;
2069 : }
2070 114 : if (this->sourceSideNodes.outlet == thisLoopSide.NodeNumOut) {
2071 0 : sourceSideIsPlantOutlet = true;
2072 : }
2073 114 : if (loadSideIsPlantInlet && sourceSideIsPlantOutlet) {
2074 0 : this->heatRecoveryHeatPump = true;
2075 0 : break;
2076 : }
2077 114 : }
2078 57 : if (this->heatRecoveryHeatPump) {
2079 0 : break;
2080 : }
2081 57 : }
2082 36 : }
2083 :
2084 36 : void EIRPlantLoopHeatPump::oneTimeInit(EnergyPlusData &state)
2085 : {
2086 : // This function does all the one-time initialization
2087 36 : constexpr std::string_view routineName = "EIRPlantLoopHeatPump : oneTimeInit"; // + __FUNCTION__;
2088 :
2089 36 : if (this->oneTimeInitFlag) {
2090 35 : bool errFlag = false;
2091 :
2092 : // setup output variables
2093 70 : SetupOutputVariable(state,
2094 : "Heat Pump Part Load Ratio",
2095 : Constant::Units::None,
2096 35 : this->partLoadRatio,
2097 : OutputProcessor::TimeStepType::System,
2098 : OutputProcessor::StoreType::Average,
2099 35 : this->name);
2100 70 : SetupOutputVariable(state,
2101 : "Heat Pump Cycling Ratio",
2102 : Constant::Units::None,
2103 35 : this->cyclingRatio,
2104 : OutputProcessor::TimeStepType::System,
2105 : OutputProcessor::StoreType::Average,
2106 35 : this->name);
2107 70 : SetupOutputVariable(state,
2108 : "Heat Pump Load Side Heat Transfer Rate",
2109 : Constant::Units::W,
2110 35 : this->loadSideHeatTransfer,
2111 : OutputProcessor::TimeStepType::System,
2112 : OutputProcessor::StoreType::Average,
2113 35 : this->name);
2114 70 : SetupOutputVariable(state,
2115 : "Heat Pump Load Side Heat Transfer Energy",
2116 : Constant::Units::J,
2117 35 : this->loadSideEnergy,
2118 : OutputProcessor::TimeStepType::System,
2119 : OutputProcessor::StoreType::Sum,
2120 35 : this->name,
2121 : Constant::eResource::EnergyTransfer,
2122 : OutputProcessor::Group::Plant);
2123 70 : SetupOutputVariable(state,
2124 : "Heat Pump Source Side Heat Transfer Rate",
2125 : Constant::Units::W,
2126 35 : this->sourceSideHeatTransfer,
2127 : OutputProcessor::TimeStepType::System,
2128 : OutputProcessor::StoreType::Average,
2129 35 : this->name);
2130 70 : SetupOutputVariable(state,
2131 : "Heat Pump Source Side Heat Transfer Energy",
2132 : Constant::Units::J,
2133 35 : this->sourceSideEnergy,
2134 : OutputProcessor::TimeStepType::System,
2135 : OutputProcessor::StoreType::Sum,
2136 35 : this->name);
2137 70 : SetupOutputVariable(state,
2138 : "Heat Pump Load Side Inlet Temperature",
2139 : Constant::Units::C,
2140 35 : this->loadSideInletTemp,
2141 : OutputProcessor::TimeStepType::System,
2142 : OutputProcessor::StoreType::Average,
2143 35 : this->name);
2144 70 : SetupOutputVariable(state,
2145 : "Heat Pump Load Side Outlet Temperature",
2146 : Constant::Units::C,
2147 35 : this->loadSideOutletTemp,
2148 : OutputProcessor::TimeStepType::System,
2149 : OutputProcessor::StoreType::Average,
2150 35 : this->name);
2151 70 : SetupOutputVariable(state,
2152 : "Heat Pump Source Side Inlet Temperature",
2153 : Constant::Units::C,
2154 35 : this->sourceSideInletTemp,
2155 : OutputProcessor::TimeStepType::System,
2156 : OutputProcessor::StoreType::Average,
2157 35 : this->name);
2158 70 : SetupOutputVariable(state,
2159 : "Heat Pump Source Side Outlet Temperature",
2160 : Constant::Units::C,
2161 35 : this->sourceSideOutletTemp,
2162 : OutputProcessor::TimeStepType::System,
2163 : OutputProcessor::StoreType::Average,
2164 35 : this->name);
2165 70 : SetupOutputVariable(state,
2166 : "Heat Pump Electricity Rate",
2167 : Constant::Units::W,
2168 35 : this->powerUsage,
2169 : OutputProcessor::TimeStepType::System,
2170 : OutputProcessor::StoreType::Average,
2171 35 : this->name);
2172 35 : if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRCooling) { // energy from HeatPump:PlantLoop:EIR:Cooling object
2173 42 : SetupOutputVariable(state,
2174 : "Heat Pump Electricity Energy",
2175 : Constant::Units::J,
2176 21 : this->powerEnergy,
2177 : OutputProcessor::TimeStepType::System,
2178 : OutputProcessor::StoreType::Sum,
2179 21 : this->name,
2180 : Constant::eResource::Electricity,
2181 : OutputProcessor::Group::Plant,
2182 : OutputProcessor::EndUseCat::Cooling,
2183 : "Heat Pump");
2184 21 : SetupOutputVariable(state,
2185 : "Thermosiphon Status",
2186 : Constant::Units::None,
2187 21 : this->thermosiphonStatus,
2188 : OutputProcessor::TimeStepType::System,
2189 : OutputProcessor::StoreType::Average,
2190 21 : this->name);
2191 14 : } else if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating) { // energy from HeatPump:PlantLoop:EIR:Heating object
2192 28 : SetupOutputVariable(state,
2193 : "Heat Pump Electricity Energy",
2194 : Constant::Units::J,
2195 14 : this->powerEnergy,
2196 : OutputProcessor::TimeStepType::System,
2197 : OutputProcessor::StoreType::Sum,
2198 14 : this->name,
2199 : Constant::eResource::Electricity,
2200 : OutputProcessor::Group::Plant,
2201 : OutputProcessor::EndUseCat::Heating,
2202 : "Heat Pump");
2203 14 : if (this->defrostAvailable) {
2204 0 : SetupOutputVariable(state,
2205 : "Heat Pump Load Due To Defrost",
2206 : Constant::Units::W,
2207 0 : this->loadDueToDefrost,
2208 : OutputProcessor::TimeStepType::System,
2209 : OutputProcessor::StoreType::Average,
2210 0 : this->name);
2211 0 : SetupOutputVariable(state,
2212 : "Heat Pump Fractioal Defrost Time",
2213 : Constant::Units::W,
2214 0 : this->fractionalDefrostTime,
2215 : OutputProcessor::TimeStepType::System,
2216 : OutputProcessor::StoreType::Average,
2217 0 : this->name);
2218 0 : SetupOutputVariable(state,
2219 : "Heat Pump Defrost Electricity Rate",
2220 : Constant::Units::W,
2221 0 : this->defrostEnergyRate,
2222 : OutputProcessor::TimeStepType::System,
2223 : OutputProcessor::StoreType::Average,
2224 0 : this->name);
2225 0 : SetupOutputVariable(state,
2226 : "Heat Pump Defrost Electricity Energy",
2227 : Constant::Units::J,
2228 0 : this->defrostEnergy,
2229 : OutputProcessor::TimeStepType::System,
2230 : OutputProcessor::StoreType::Sum,
2231 0 : this->name,
2232 : Constant::eResource::Electricity,
2233 : OutputProcessor::Group::Plant,
2234 : OutputProcessor::EndUseCat::Heating,
2235 : "Heat Pump");
2236 : }
2237 : }
2238 70 : SetupOutputVariable(state,
2239 : "Heat Pump Load Side Mass Flow Rate",
2240 : Constant::Units::kg_s,
2241 35 : this->loadSideMassFlowRate,
2242 : OutputProcessor::TimeStepType::System,
2243 : OutputProcessor::StoreType::Average,
2244 35 : this->name);
2245 70 : SetupOutputVariable(state,
2246 : "Heat Pump Source Side Mass Flow Rate",
2247 : Constant::Units::kg_s,
2248 35 : this->sourceSideMassFlowRate,
2249 : OutputProcessor::TimeStepType::System,
2250 : OutputProcessor::StoreType::Average,
2251 35 : this->name);
2252 : // report variable used for debugging, System Node Specific Heat can also report the node Cp
2253 : // added spaces to SetupOutputVariable to avoid issue with variable parsing script
2254 : // Setup Output Variable(state,
2255 : // "Heat Pump Source Side Specific Heat",
2256 : // Constant::Units::J_kgK,
2257 : // this->sourceSideCp,
2258 : // OutputProcessor::TimeStepType::System,
2259 : // OutputProcessor::StoreType::Average,
2260 : // this->name);
2261 :
2262 35 : if (this->heatRecoveryAvailable) {
2263 12 : SetupOutputVariable(state,
2264 : "Heat Pump Heat Recovery Heat Transfer Rate",
2265 : Constant::Units::W,
2266 6 : this->heatRecoveryRate,
2267 : OutputProcessor::TimeStepType::System,
2268 : OutputProcessor::StoreType::Average,
2269 6 : this->name);
2270 12 : SetupOutputVariable(state,
2271 : "Heat Pump Heat Recovery Heat Transfer Energy",
2272 : Constant::Units::J,
2273 6 : this->heatRecoveryEnergy,
2274 : OutputProcessor::TimeStepType::System,
2275 : OutputProcessor::StoreType::Sum,
2276 6 : this->name);
2277 :
2278 12 : SetupOutputVariable(state,
2279 : "Heat Pump Heat Recovery Inlet Temperature",
2280 : Constant::Units::C,
2281 6 : this->heatRecoveryInletTemp,
2282 : OutputProcessor::TimeStepType::System,
2283 : OutputProcessor::StoreType::Average,
2284 6 : this->name);
2285 12 : SetupOutputVariable(state,
2286 : "Heat Pump Heat Recovery Outlet Temperature",
2287 : Constant::Units::C,
2288 6 : this->heatRecoveryOutletTemp,
2289 : OutputProcessor::TimeStepType::System,
2290 : OutputProcessor::StoreType::Average,
2291 6 : this->name);
2292 12 : SetupOutputVariable(state,
2293 : "Heat Pump Heat Recovery Mass Flow Rate",
2294 : Constant::Units::kg_s,
2295 6 : this->heatRecoveryMassFlowRate,
2296 : OutputProcessor::TimeStepType::System,
2297 : OutputProcessor::StoreType::Average,
2298 6 : this->name);
2299 6 : SetupOutputVariable(state,
2300 : "Heat Pump Heat Recovery Operation Status",
2301 : Constant::Units::None,
2302 6 : this->heatRecoveryOperatingStatus,
2303 : OutputProcessor::TimeStepType::System,
2304 : OutputProcessor::StoreType::Average,
2305 6 : this->name);
2306 : }
2307 :
2308 : // find this component on the plant
2309 35 : bool thisErrFlag = false;
2310 105 : PlantUtilities::ScanPlantLoopsForObject(
2311 70 : state, this->name, this->EIRHPType, this->loadSidePlantLoc, thisErrFlag, _, _, _, this->loadSideNodes.inlet, _);
2312 :
2313 35 : if (thisErrFlag) {
2314 4 : ShowSevereError(state,
2315 4 : format("{}: Plant topology problem for {} name = \"{}\"",
2316 : routineName,
2317 2 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
2318 2 : this->name));
2319 4 : ShowContinueError(state, "Could not locate component's load side connections on a plant loop");
2320 2 : errFlag = true;
2321 33 : } else if (this->loadSidePlantLoc.loopSideNum != DataPlant::LoopSideLocation::Supply) { // only check if !thisErrFlag
2322 2 : ShowSevereError(state,
2323 2 : format("{}: Invalid connections for {} name = \"{}\"",
2324 : routineName,
2325 1 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
2326 1 : this->name));
2327 2 : ShowContinueError(state, "The load side connections are not on the Supply Side of a plant loop");
2328 1 : errFlag = true;
2329 : }
2330 :
2331 35 : thisErrFlag = false;
2332 35 : if (this->waterSource) {
2333 57 : PlantUtilities::ScanPlantLoopsForObject(
2334 38 : state, this->name, this->EIRHPType, this->sourceSidePlantLoc, thisErrFlag, _, _, _, this->sourceSideNodes.inlet, _);
2335 :
2336 19 : if (thisErrFlag) {
2337 4 : ShowSevereError(state,
2338 4 : format("{}: Plant topology problem for {} name = \"{}\"",
2339 : routineName,
2340 2 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
2341 2 : this->name));
2342 4 : ShowContinueError(state, "Could not locate component's source side connections on a plant loop");
2343 2 : errFlag = true;
2344 17 : } else if (this->sourceSidePlantLoc.loopSideNum != DataPlant::LoopSideLocation::Demand) { // only check if !thisErrFlag
2345 2 : ShowSevereError(state,
2346 2 : format("{}: Invalid connections for {} name = \"{}\"",
2347 : routineName,
2348 1 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
2349 1 : this->name));
2350 2 : ShowContinueError(state, "The source side connections are not on the Demand Side of a plant loop");
2351 1 : errFlag = true;
2352 : }
2353 :
2354 : // make sure it is not the same loop on both sides.
2355 19 : if (this->loadSidePlantLoc.loopNum == this->sourceSidePlantLoc.loopNum) { // user is being too tricky, don't allow
2356 10 : ShowSevereError(state,
2357 10 : format("{}: Invalid connections for {} name = \"{}\"",
2358 : routineName,
2359 5 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
2360 5 : this->name));
2361 10 : ShowContinueError(state, "The load and source sides need to be on different loops.");
2362 5 : errFlag = true;
2363 : } else {
2364 :
2365 14 : PlantUtilities::InterConnectTwoPlantLoopSides(state, this->loadSidePlantLoc, this->sourceSidePlantLoc, this->EIRHPType, true);
2366 : }
2367 16 : } else if (this->airSource) {
2368 : // nothing to do here ? not any more
2369 16 : if (this->heatRecoveryAvailable) {
2370 18 : PlantUtilities::ScanPlantLoopsForObject(
2371 12 : state, this->name, this->EIRHPType, this->heatRecoveryPlantLoc, thisErrFlag, _, _, _, this->heatRecoveryNodes.inlet, _);
2372 :
2373 6 : if (thisErrFlag) {
2374 0 : ShowSevereError(state,
2375 0 : format("{}: Plant topology problem for {} name = \"{}\"",
2376 : routineName,
2377 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
2378 0 : this->name));
2379 0 : ShowContinueError(state, "Could not locate component's heat recovery side connections on a plant loop.");
2380 0 : errFlag = true;
2381 6 : } else if (this->heatRecoveryPlantLoc.loopSideNum != DataPlant::LoopSideLocation::Demand) { // only check if !thisErrFlag
2382 0 : ShowSevereError(state,
2383 0 : format("{}: Invalid connections for {} name = \"{}\"",
2384 : routineName,
2385 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
2386 0 : this->name));
2387 0 : ShowContinueError(state, "The heat recovery side connections are not on the Demand Side of a plant loop.");
2388 0 : errFlag = true;
2389 : }
2390 :
2391 : // make sure it is not the same loop on both sides.
2392 6 : if (this->loadSidePlantLoc.loopNum == this->heatRecoveryPlantLoc.loopNum) { // user is being too tricky, don't allow
2393 0 : ShowSevereError(state,
2394 0 : format("{}: Invalid connections for {} name = \"{}\"",
2395 : routineName,
2396 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
2397 0 : this->name));
2398 0 : ShowContinueError(state, "The load and heat recovery sides need to be on different loops.");
2399 0 : errFlag = true;
2400 : } else {
2401 :
2402 6 : PlantUtilities::InterConnectTwoPlantLoopSides(state, this->loadSidePlantLoc, this->heatRecoveryPlantLoc, this->EIRHPType, true);
2403 : }
2404 : }
2405 : }
2406 :
2407 35 : if (errFlag) {
2408 10 : ShowFatalError(state, format("{}: Program terminated due to previous condition(s).", routineName));
2409 : }
2410 30 : this->oneTimeInitFlag = false;
2411 : }
2412 31 : }
2413 :
2414 21 : bool EIRPlantLoopHeatPump::thermosiphonDisabled(EnergyPlusData &state)
2415 : {
2416 21 : if (this->thermosiphonTempCurveIndex > 0) {
2417 3 : this->thermosiphonStatus = 0;
2418 3 : Real64 dT = this->loadSideOutletTemp - this->sourceSideInletTemp;
2419 3 : if (dT < this->thermosiphonMinTempDiff) {
2420 1 : return true;
2421 : }
2422 2 : Real64 thermosiphonCapFrac = Curve::CurveValue(state, this->thermosiphonTempCurveIndex, dT);
2423 2 : Real64 capFrac = this->partLoadRatio * this->cyclingRatio;
2424 2 : if (thermosiphonCapFrac >= capFrac) {
2425 1 : this->thermosiphonStatus = 1;
2426 1 : this->powerUsage = 0.0;
2427 1 : return false;
2428 : }
2429 1 : return true;
2430 : } else {
2431 18 : return true;
2432 : }
2433 : }
2434 :
2435 0 : Real64 EIRPlantLoopHeatPump::getDynamicMaxCapacity(EnergyPlusData &state)
2436 : {
2437 0 : Real64 sourceInletTemp = state.dataLoopNodes->Node(this->sourceSideNodes.inlet).Temp;
2438 0 : Real64 loadSideOutletSetpointTemp = this->getLoadSideOutletSetPointTemp(state);
2439 : // evaluate capacity modifier curve and determine load side heat transfer
2440 0 : Real64 capacityModifierFuncTemp = Curve::CurveValue(state, this->capFuncTempCurveIndex, loadSideOutletSetpointTemp, sourceInletTemp);
2441 0 : return this->referenceCapacity * capacityModifierFuncTemp * heatingCapacityModifierASHP(state);
2442 : }
2443 :
2444 26 : void EIRPlantLoopHeatPump::report(EnergyPlusData &state)
2445 : {
2446 :
2447 26 : Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec;
2448 :
2449 26 : this->defrostEnergy = this->defrostEnergyRate * reportingInterval;
2450 26 : this->loadSideEnergy = this->loadSideHeatTransfer * reportingInterval;
2451 26 : this->powerEnergy = this->powerUsage * reportingInterval;
2452 26 : this->sourceSideEnergy = this->sourceSideHeatTransfer * reportingInterval;
2453 26 : this->heatRecoveryEnergy = this->heatRecoveryRate * reportingInterval;
2454 :
2455 : // update nodes
2456 26 : PlantUtilities::SafeCopyPlantNode(state, this->loadSideNodes.inlet, this->loadSideNodes.outlet);
2457 26 : state.dataLoopNodes->Node(this->loadSideNodes.outlet).Temp = this->loadSideOutletTemp;
2458 26 : if (this->waterSource) {
2459 10 : PlantUtilities::SafeCopyPlantNode(state, this->sourceSideNodes.inlet, this->sourceSideNodes.outlet);
2460 : }
2461 26 : state.dataLoopNodes->Node(this->sourceSideNodes.outlet).Temp = this->sourceSideOutletTemp;
2462 26 : if (this->heatRecoveryAvailable) {
2463 6 : PlantUtilities::SafeCopyPlantNode(state, this->heatRecoveryNodes.inlet, this->heatRecoveryNodes.outlet);
2464 6 : state.dataLoopNodes->Node(this->heatRecoveryNodes.outlet).Temp = this->heatRecoveryOutletTemp;
2465 : }
2466 26 : }
2467 :
2468 : // From here on, the Fuel Fired Heat Pump module EIRFuelFiredHeatPump
2469 : // Enum string definitions
2470 : static constexpr std::array<std::string_view, static_cast<int>(EIRFuelFiredHeatPump::OATempCurveVar::Num)> OATempCurveVarNamesUC = {"DRYBULB",
2471 : "WETBULB"};
2472 : static constexpr std::array<std::string_view, static_cast<int>(EIRFuelFiredHeatPump::WaterTempCurveVar::Num)> WaterTempCurveVarNamesUC = {
2473 : "ENTERINGCONDENSER", "LEAVINGCONDENSER", "ENTERINGEVAPORATOR", "LEAVINGEVAPORATOR"};
2474 : static constexpr std::array<std::string_view, static_cast<int>(EIRFuelFiredHeatPump::DefrostType::Num)> DefrostTypeNamesUC = {"TIMED", "ONDEMAND"};
2475 :
2476 12 : void EIRFuelFiredHeatPump::doPhysics(EnergyPlusData &state, Real64 currentLoad)
2477 : {
2478 12 : Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec;
2479 :
2480 : // ideally the plant is going to ensure that we don't have a runflag=true when the load is invalid, but
2481 : // I'm not sure we can count on that so we will do one check here to make sure we don't calculate things badly
2482 12 : if ((this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpFuelFiredCooling && currentLoad >= 0.0) ||
2483 12 : (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpFuelFiredHeating && currentLoad <= 0.0)) {
2484 4 : this->resetReportingVariables();
2485 4 : return;
2486 : }
2487 :
2488 : // get setpoint on the load side outlet
2489 : // Real64 loadSideOutletSetpointTemp = this->getLoadSideOutletSetPointTemp(state);
2490 :
2491 : // Use a logic similar to that for a boilder: If the specified load is 0.0 or the boiler should not run
2492 : // then we leave this subroutine. Before leaving
2493 : // if the component control is SERIESACTIVE we set the component flow to inlet flow so that flow resolver
2494 : // will not shut down the branch
2495 8 : auto &thisInletNode = state.dataLoopNodes->Node(this->loadSideNodes.inlet);
2496 8 : auto &thisOutletNode = state.dataLoopNodes->Node(this->loadSideNodes.outlet);
2497 8 : auto &thisSourceSideInletNode = state.dataLoopNodes->Node(this->sourceSideNodes.inlet); // OA Intake node
2498 8 : auto &sim_component = DataPlant::CompData::getPlantComponent(state, this->loadSidePlantLoc);
2499 8 : if ((this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpFuelFiredHeating && currentLoad <= 0.0)) {
2500 0 : if (sim_component.FlowCtrl == DataBranchAirLoopPlant::ControlType::SeriesActive) this->loadSideMassFlowRate = thisInletNode.MassFlowRate;
2501 0 : this->resetReportingVariables();
2502 0 : return;
2503 : }
2504 :
2505 8 : Real64 CpLoad = this->loadSidePlantLoc.loop->glycol->getSpecificHeat(state, thisInletNode.Temp, "PLFFHPEIR::simulate()");
2506 :
2507 : // Set the current load equal to the FFHP load
2508 8 : Real64 FFHPloadSideLoad = currentLoad; // this->loadSidePlantLoad = MyLoad;
2509 :
2510 8 : if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpFuelFiredHeating) {
2511 :
2512 : // Initialize the delta temperature to zero
2513 6 : Real64 FFHPDeltaTemp = 0.0; // C - FFHP inlet to outlet temperature difference, set in all necessary code paths so no initialization required
2514 :
2515 6 : if (this->loadSidePlantLoc.side->FlowLock == DataPlant::FlowLock::Unlocked) {
2516 : // Either set the flow to the Constant value or calculate the flow for the variable volume
2517 6 : if (this->flowMode == DataPlant::FlowMode::Constant) {
2518 : // Then find the flow rate and outlet temp
2519 0 : this->loadSideMassFlowRate = this->loadSideDesignMassFlowRate;
2520 0 : PlantUtilities::SetComponentFlowRate(
2521 0 : state, this->loadSideMassFlowRate, this->loadSideNodes.inlet, this->loadSideNodes.outlet, this->loadSidePlantLoc);
2522 :
2523 0 : if ((this->loadSideMassFlowRate != 0.0) &&
2524 0 : ((this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpFuelFiredHeating && currentLoad > 0.0))) {
2525 0 : FFHPDeltaTemp = currentLoad / (this->loadSideMassFlowRate * CpLoad);
2526 : } else {
2527 0 : FFHPDeltaTemp = 0.0;
2528 : }
2529 0 : this->loadSideOutletTemp = FFHPDeltaTemp + thisInletNode.Temp;
2530 :
2531 6 : } else if (this->flowMode == DataPlant::FlowMode::LeavingSetpointModulated) {
2532 : // Calculate the Delta Temp from the inlet temp to the FFHP outlet setpoint
2533 : // Then find the flow rate and outlet temp
2534 :
2535 0 : if (this->loadSidePlantLoc.loop->LoopDemandCalcScheme == DataPlant::LoopDemandCalcScheme::SingleSetPoint) {
2536 0 : FFHPDeltaTemp = thisOutletNode.TempSetPoint - thisInletNode.Temp;
2537 : } else { // DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand
2538 0 : FFHPDeltaTemp = thisOutletNode.TempSetPointLo - thisInletNode.Temp;
2539 : }
2540 :
2541 0 : this->loadSideOutletTemp = FFHPDeltaTemp + thisInletNode.Temp;
2542 :
2543 0 : if ((FFHPDeltaTemp > 0.0) && ((this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpFuelFiredHeating && currentLoad > 0.0))) {
2544 0 : this->loadSideMassFlowRate = currentLoad / (CpLoad * FFHPDeltaTemp);
2545 0 : this->loadSideMassFlowRate = min(this->loadSideDesignMassFlowRate, this->loadSideMassFlowRate);
2546 : } else {
2547 0 : this->loadSideMassFlowRate = 0.0;
2548 : }
2549 0 : PlantUtilities::SetComponentFlowRate(
2550 0 : state, this->loadSideMassFlowRate, this->loadSideNodes.inlet, this->loadSideNodes.outlet, this->loadSidePlantLoc);
2551 :
2552 : } // End of Constant/Variable Flow If Block
2553 : } else { // If FlowLock is True
2554 : // Set the boiler flow rate from inlet node and then check performance
2555 0 : this->loadSideMassFlowRate = thisInletNode.MassFlowRate;
2556 :
2557 0 : if ((this->loadSideMassFlowRate > 0.0) &&
2558 0 : ((this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpFuelFiredHeating && currentLoad > 0.0))) { // this FFHP has a heat load
2559 : // FFHPloadSideLoad = currentLoad;
2560 : // if (FFHPloadSideLoad > this->referenceCapacity * this->maxPLR) FFHPloadSideLoad = this->referenceCapacity * this->maxPLR;
2561 : // if (FFHPloadSideLoad < this->referenceCapacity * this->minPLR) FFHPloadSideLoad = this->referenceCapacity * this->minPLR;
2562 0 : FFHPloadSideLoad = std::clamp(FFHPloadSideLoad, this->referenceCapacity * this->minPLR, this->referenceCapacity * this->maxPLR);
2563 0 : this->loadSideOutletTemp = thisInletNode.Temp + FFHPloadSideLoad / (this->loadSideMassFlowRate * CpLoad);
2564 : } else {
2565 0 : FFHPloadSideLoad = 0.0;
2566 0 : this->loadSideOutletTemp = thisInletNode.Temp;
2567 : }
2568 : }
2569 2 : } else if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpFuelFiredCooling) {
2570 2 : if (this->loadSidePlantLoc.side->FlowLock == DataPlant::FlowLock::Unlocked) {
2571 : // this->PossibleSubcooling =
2572 : // !(state.dataPlnt->PlantLoop(PlantLoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Comp(CompNum).CurOpSchemeType ==
2573 : // DataPlant::OpScheme::CompSetPtBased);
2574 2 : Real64 evapDeltaTemp = 0.0; // Evaporator temperature difference [C]
2575 :
2576 : // Either set the flow to the Constant value or calculate the flow for the variable volume case
2577 2 : if (this->flowMode == DataPlant::FlowMode::Constant) {
2578 : // Set the evaporator mass flow rate to design
2579 : // Start by assuming max (design) flow
2580 0 : this->loadSideMassFlowRate = this->loadSideDesignMassFlowRate;
2581 : // Use PlantUtilities::SetComponentFlowRate to decide actual flow
2582 0 : PlantUtilities::SetComponentFlowRate(
2583 0 : state, this->loadSideMassFlowRate, this->loadSideNodes.inlet, this->loadSideNodes.outlet, this->loadSidePlantLoc);
2584 0 : if (this->loadSideMassFlowRate != 0.0) {
2585 0 : evapDeltaTemp = std::abs(currentLoad) / (this->loadSideMassFlowRate * CpLoad); // MyLoad = net evaporator capacity, QEvaporator
2586 : } else {
2587 0 : evapDeltaTemp = 0.0;
2588 : }
2589 0 : this->loadSideOutletTemp = thisInletNode.Temp - evapDeltaTemp;
2590 2 : } else if (this->flowMode == DataPlant::FlowMode::LeavingSetpointModulated) {
2591 0 : switch (this->loadSidePlantLoc.loop->LoopDemandCalcScheme) {
2592 0 : case DataPlant::LoopDemandCalcScheme::SingleSetPoint: {
2593 : // Calculate the Delta Temp from the inlet temp to the chiller outlet setpoint
2594 0 : evapDeltaTemp = thisInletNode.Temp - thisOutletNode.TempSetPoint;
2595 0 : } break;
2596 0 : case DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand: {
2597 0 : evapDeltaTemp = thisInletNode.Temp - thisOutletNode.TempSetPointHi;
2598 0 : } break;
2599 0 : default: {
2600 0 : assert(false);
2601 : } break;
2602 : }
2603 :
2604 0 : if (evapDeltaTemp != 0) {
2605 0 : this->loadSideMassFlowRate = max(0.0, (std::abs(currentLoad) / (CpLoad * evapDeltaTemp)));
2606 : // Check to see if the Maximum is exceeded, if so set to maximum
2607 0 : this->loadSideMassFlowRate = min(this->loadSideDesignMassFlowRate, this->loadSideMassFlowRate);
2608 : // Use PlantUtilities::SetComponentFlowRate to decide actual flow
2609 0 : PlantUtilities::SetComponentFlowRate(
2610 0 : state, this->loadSideMassFlowRate, this->loadSideNodes.inlet, this->loadSideNodes.outlet, this->loadSidePlantLoc);
2611 : // Should we recalculate this with the corrected setpoint?
2612 0 : switch (this->loadSidePlantLoc.loop->LoopDemandCalcScheme) {
2613 0 : case DataPlant::LoopDemandCalcScheme::SingleSetPoint: {
2614 0 : this->loadSideOutletTemp = thisOutletNode.TempSetPoint;
2615 0 : } break;
2616 0 : case DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand: {
2617 0 : this->loadSideOutletTemp = thisOutletNode.TempSetPointHi;
2618 0 : } break;
2619 0 : default:
2620 0 : break;
2621 : }
2622 : } else {
2623 : // Try to request zero flow
2624 0 : this->loadSideMassFlowRate = 0.0;
2625 : // Use PlantUtilities::SetComponentFlowRate to decide actual flow
2626 0 : PlantUtilities::SetComponentFlowRate(
2627 0 : state, this->loadSideMassFlowRate, this->loadSideNodes.inlet, this->loadSideNodes.outlet, this->loadSidePlantLoc);
2628 : // No deltaT since component is not running
2629 0 : this->loadSideOutletTemp = thisInletNode.Temp;
2630 : // this->QEvaporator = 0.0;
2631 : // PartLoadRat = 0.0;
2632 : // this->ChillerPartLoadRatio = 0.0;
2633 :
2634 : // if (this->DeltaTErrCount < 1 && !state.dataGlobal->WarmupFlag) {
2635 0 : if (!state.dataGlobal->WarmupFlag) {
2636 : // ++this->DeltaTErrCount;
2637 0 : ShowWarningError(state, "FFHP evaporator DeltaTemp = 0 in mass flow calculation (Tevapin = Tevapout setpoint temp).");
2638 0 : ShowContinueErrorTimeStamp(state, "");
2639 : // } else if (!state.dataGlobal->WarmupFlag) {
2640 : // ++this->ChillerCapFTError;
2641 0 : ShowWarningError( // RecurringWarningErrorAtEnd(
2642 : state,
2643 0 : format("{} \"{}\": FFHP evaporator DeltaTemp = 0 in mass flow calculation warning continues...",
2644 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
2645 0 : this->name));
2646 : // this->DeltaTErrCountIndex,
2647 : // evapDeltaTemp,
2648 : // evapDeltaTemp);
2649 : }
2650 : }
2651 : }
2652 : } else { // If FlowLock is True
2653 0 : this->loadSideMassFlowRate = thisInletNode.MassFlowRate;
2654 0 : PlantUtilities::SetComponentFlowRate(
2655 0 : state, this->loadSideMassFlowRate, this->loadSideNodes.inlet, this->loadSideNodes.outlet, this->loadSidePlantLoc);
2656 : // Some other component set the flow to 0. No reason to continue with calculations.
2657 0 : if (this->loadSideMassFlowRate == 0.0) {
2658 0 : FFHPloadSideLoad = 0.0;
2659 : // return;
2660 : }
2661 : } // This is the end of the FlowLock Block
2662 : }
2663 :
2664 : // Determine which air variable to use for GAHP:
2665 : // Source (air) side variable to use
2666 : // auto &thisloadsideinletnode = state.dataLoopNodes->Node(this->loadSideNodes.inlet);
2667 8 : Real64 oaTempforCurve = this->sourceSideInletTemp; // state.dataLoopNodes->Node(this->loadSideNodes.inlet).Temp;
2668 8 : if (this->oaTempCurveInputVar == OATempCurveVar::WetBulb) {
2669 2 : oaTempforCurve = Psychrometrics::PsyTwbFnTdbWPb(
2670 : state, thisSourceSideInletNode.Temp, thisSourceSideInletNode.HumRat, thisSourceSideInletNode.Press, "PLFFHPEIR::doPhysics()");
2671 : }
2672 :
2673 : // Load (water) side temperature variable
2674 8 : Real64 waterTempforCurve = this->loadSideInletTemp;
2675 8 : if (this->waterTempCurveInputVar == WaterTempCurveVar::LeavingCondenser || this->waterTempCurveInputVar == WaterTempCurveVar::LeavingEvaporator) {
2676 2 : waterTempforCurve = this->loadSideOutletTemp;
2677 : }
2678 :
2679 : // evaluate capacity modifier curve and determine load side heat transfer
2680 8 : Real64 capacityModifierFuncTemp = Curve::CurveValue(state, this->capFuncTempCurveIndex, waterTempforCurve, oaTempforCurve);
2681 :
2682 8 : if (capacityModifierFuncTemp < 0.0) {
2683 0 : if (this->capModFTErrorIndex == 0) {
2684 0 : ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)], this->name));
2685 0 : ShowContinueError(state,
2686 0 : format(" Capacity Modifier curve (function of Temperatures) output is negative ({:.3T}).", capacityModifierFuncTemp));
2687 0 : ShowContinueError(state,
2688 0 : format(" Negative value occurs using a water temperature of {:.2T}C and an outdoor air temperature of {:.2T}C.",
2689 : waterTempforCurve,
2690 : oaTempforCurve));
2691 0 : ShowContinueErrorTimeStamp(state, " Resetting curve output to zero and continuing simulation.");
2692 : }
2693 0 : ShowRecurringWarningErrorAtEnd(state,
2694 0 : format("{} \"{}\": Capacity Modifier curve (function of Temperatures) output is negative warning continues...",
2695 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
2696 0 : this->name),
2697 0 : this->capModFTErrorIndex,
2698 : capacityModifierFuncTemp,
2699 : capacityModifierFuncTemp);
2700 0 : capacityModifierFuncTemp = 0.0;
2701 : }
2702 :
2703 8 : Real64 availableCapacity = this->referenceCapacity * capacityModifierFuncTemp;
2704 8 : Real64 partLoadRatio = 0.0;
2705 8 : if (availableCapacity > 0) {
2706 16 : partLoadRatio = std::clamp(
2707 8 : std::abs(FFHPloadSideLoad) / availableCapacity, 0.0, 1.0); // max(0.0, min(std::abs(FFHPloadSideLoad) / availableCapacity, 1.0));
2708 : }
2709 :
2710 : // evaluate the actual current operating load side heat transfer rate
2711 :
2712 : // this->loadSideHeatTransfer = availableCapacity * partLoadRatio;
2713 8 : this->loadSideHeatTransfer = availableCapacity * partLoadRatio; // (partLoadRatio >= this->minPLR ? partLoadRatio : 0.0);
2714 :
2715 : // calculate load side outlet conditions
2716 8 : Real64 const loadMCp = this->loadSideMassFlowRate * CpLoad;
2717 8 : this->loadSideOutletTemp = this->calcLoadOutletTemp(this->loadSideInletTemp, this->loadSideHeatTransfer / loadMCp);
2718 :
2719 : // calculate power usage from EIR curves
2720 8 : Real64 eirModifierFuncTemp = Curve::CurveValue(state,
2721 : this->powerRatioFuncTempCurveIndex,
2722 : waterTempforCurve,
2723 8 : oaTempforCurve); // CurveManager::CurveValue(state, this->powerRatioFuncTempCurveIndex,
2724 : // this->loadSideOutletTemp, this->sourceSideInletTemp);
2725 :
2726 8 : if (eirModifierFuncTemp < 0.0) {
2727 0 : if (this->eirModFTErrorIndex == 0) {
2728 0 : ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)], this->name));
2729 0 : ShowContinueError(state, format(" EIR Modifier curve (function of Temperatures) output is negative ({:.3T}).", eirModifierFuncTemp));
2730 0 : ShowContinueError(state,
2731 0 : format(" Negative value occurs using a water temperature of {:.2T}C and an outdoor air temperature of {:.2T}C.",
2732 : waterTempforCurve,
2733 : oaTempforCurve));
2734 0 : ShowContinueErrorTimeStamp(state, " Resetting curve output to zero and continuing simulation.");
2735 : }
2736 0 : ShowRecurringWarningErrorAtEnd(state,
2737 0 : format("{} \"{}\": EIR Modifier curve (function of Temperatures) output is negative warning continues...",
2738 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
2739 0 : this->name),
2740 0 : this->eirModFTErrorIndex,
2741 : eirModifierFuncTemp,
2742 : eirModifierFuncTemp);
2743 0 : eirModifierFuncTemp = 0.0;
2744 : }
2745 :
2746 8 : Real64 miniPLR_mod = this->minPLR;
2747 8 : Real64 PLFf = max(miniPLR_mod, partLoadRatio);
2748 :
2749 8 : Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, PLFf);
2750 : // this->powerUsage = (this->loadSideHeatTransfer / this->referenceCOP) * eirModifierFuncPLR * eirModifierFuncTemp;
2751 : // this->powerEnergy = this->powerUsage * reportingInterval;
2752 :
2753 8 : if (eirModifierFuncPLR < 0.0) {
2754 0 : if (this->eirModFPLRErrorIndex == 0) {
2755 0 : ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)], this->name));
2756 0 : ShowContinueError(state, format(" EIR Modifier curve (function of PLR) output is negative ({:.3T}).", eirModifierFuncPLR));
2757 0 : ShowContinueError(state, format(" Negative value occurs using a Part Load Ratio of {:.2T}", PLFf));
2758 0 : ShowContinueErrorTimeStamp(state, " Resetting curve output to zero and continuing simulation.");
2759 : }
2760 0 : ShowRecurringWarningErrorAtEnd(state,
2761 0 : format("{} \"{}\": EIR Modifier curve (function of PLR) output is negative warning continues...",
2762 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
2763 0 : this->name),
2764 0 : this->eirModFPLRErrorIndex,
2765 : eirModifierFuncPLR,
2766 : eirModifierFuncPLR);
2767 0 : eirModifierFuncPLR = 0.0;
2768 : }
2769 :
2770 8 : constexpr Real64 minDefrostT = Fahrenheit2Celsius(16.0); // (16.0 - 32.0) * 5.0 / 9.0; // 16F
2771 8 : constexpr Real64 maxDefrostT = Fahrenheit2Celsius(38.0); // (38.0 - 32.0) * 5.0 / 9.0; // 38F
2772 :
2773 8 : Real64 oaTemp2 = std::clamp(oaTempforCurve, minDefrostT, maxDefrostT); // max(minDefrostT, min(maxDefrostT, oaTempforCurve));
2774 8 : Real64 eirDefrost = 1.0;
2775 :
2776 8 : if ((state.dataEnvrn->OutDryBulbTemp <= this->defrostMaxOADBT) && this->defrostType == DefrostType::OnDemand) {
2777 6 : if (this->defrostEIRCurveIndex > 0) {
2778 4 : eirDefrost = Curve::CurveValue(state, this->defrostEIRCurveIndex, oaTemp2);
2779 : }
2780 :
2781 6 : if (eirDefrost < 1.0) {
2782 0 : if (this->eirDefrostFTErrorIndex == 0) {
2783 0 : ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)], this->name));
2784 0 : ShowContinueError(state,
2785 0 : format(" EIR defrost Modifier curve (function of Temperature) output is less than 1.0 ({:.3T}).", eirDefrost));
2786 0 : ShowContinueError(state, format(" Negative value occurs using an outdoor air temperature of {:.2T}", oaTemp2));
2787 0 : ShowContinueErrorTimeStamp(state, " Resetting curve output to 1.0 and continuing simulation.");
2788 : }
2789 0 : ShowRecurringWarningErrorAtEnd(state,
2790 0 : format("{} \"{}\": EIR Modifier curve (function of PLR) output out of range warning continues...",
2791 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
2792 0 : this->name),
2793 0 : this->eirDefrostFTErrorIndex,
2794 : eirDefrost,
2795 : eirDefrost);
2796 0 : eirDefrost = 1.0;
2797 : }
2798 : }
2799 :
2800 : // Cycling Ratio
2801 8 : constexpr Real64 CR_min = 0.0;
2802 8 : constexpr Real64 CR_max = 1.0;
2803 8 : Real64 CR = std::clamp(max(this->minPLR, partLoadRatio) / miniPLR_mod,
2804 : CR_min,
2805 8 : CR_max); // min(max(0.0, max(this->minPLR, partLoadRatio) / miniPLR_mod), 1.0); // partLoadRatio / this->minPLR;
2806 :
2807 8 : constexpr Real64 CRF_Slope = 0.4167;
2808 8 : constexpr Real64 CRF_Intercept = 0.5833;
2809 8 : Real64 CRF = CRF_Slope * CR + CRF_Intercept; // Use the the fixed eqn in the paper as the default curve (or maybe choose constant 1 as default)
2810 8 : if (this->cycRatioCurveIndex > 0) {
2811 8 : CRF = Curve::CurveValue(state, this->cycRatioCurveIndex, CR);
2812 : }
2813 8 : if (CRF <= Constant::rTinyValue) CRF = CRF_Intercept; // What could a proper default for too tiny CRF?
2814 :
2815 : // aux elec
2816 8 : Real64 eirAuxElecFuncTemp = 0.0;
2817 8 : if (this->auxElecEIRFoTempCurveIndex > 0) {
2818 8 : eirAuxElecFuncTemp = Curve::CurveValue(state, this->auxElecEIRFoTempCurveIndex, waterTempforCurve, oaTempforCurve);
2819 : }
2820 :
2821 8 : if (eirAuxElecFuncTemp < 0.0) {
2822 0 : if (this->eirAuxElecFTErrorIndex == 0) {
2823 0 : ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)], this->name));
2824 0 : ShowContinueError(state,
2825 0 : format(" Auxillary EIR Modifier curve (function of Temperatures) output is negative ({:.3T}).", eirAuxElecFuncTemp));
2826 0 : ShowContinueError(state,
2827 0 : format(" Negative value occurs using a water temperature of {:.2T}C and an outdoor air temperature of {:.2T}C.",
2828 : waterTempforCurve,
2829 : oaTempforCurve));
2830 0 : ShowContinueErrorTimeStamp(state, " Resetting curve output to zero and continuing simulation.");
2831 : }
2832 0 : ShowRecurringWarningErrorAtEnd(
2833 : state,
2834 0 : format("{} \"{}\": Auxillary EIR Modifier curve (function of Temperatures) output is negative warning continues...",
2835 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
2836 0 : this->name),
2837 0 : this->eirAuxElecFTErrorIndex,
2838 : eirAuxElecFuncTemp,
2839 : eirAuxElecFuncTemp);
2840 0 : eirAuxElecFuncTemp = 0.0;
2841 : }
2842 :
2843 8 : Real64 eirAuxElecFuncPLR = 0.0;
2844 8 : if (this->auxElecEIRFoPLRCurveIndex > 0) {
2845 8 : eirAuxElecFuncPLR = Curve::CurveValue(state, this->auxElecEIRFoPLRCurveIndex, partLoadRatio);
2846 : }
2847 :
2848 8 : if (eirAuxElecFuncPLR < 0.0) {
2849 0 : if (this->eirAuxElecFPLRErrorIndex == 0) {
2850 0 : ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)], this->name));
2851 0 : ShowContinueError(state,
2852 0 : format(" Auxillary EIR Modifier curve (function of Temperatures) output is negative ({:.3T}).", eirAuxElecFuncPLR));
2853 0 : ShowContinueError(state, format(" Negative value occurs using a Part Load Ratio of {:.2T}.", partLoadRatio));
2854 0 : ShowContinueErrorTimeStamp(state, " Resetting curve output to zero and continuing simulation.");
2855 : }
2856 0 : ShowRecurringWarningErrorAtEnd(state,
2857 0 : format("{} \"{}\": Auxillary EIR Modifier curve (function of PLR) output is negative warning continues...",
2858 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
2859 0 : this->name),
2860 0 : this->eirAuxElecFPLRErrorIndex,
2861 : eirAuxElecFuncPLR,
2862 : eirAuxElecFuncPLR);
2863 0 : eirAuxElecFuncPLR = 0.0;
2864 : }
2865 :
2866 8 : if (partLoadRatio < this->minPLR) {
2867 0 : this->fuelRate = 0.0;
2868 0 : this->powerUsage = 0.0;
2869 : } else {
2870 8 : this->fuelRate = this->loadSideHeatTransfer / (this->referenceCOP * CRF) * eirModifierFuncPLR * eirModifierFuncTemp * eirDefrost;
2871 :
2872 8 : this->powerUsage = this->nominalAuxElecPower * eirAuxElecFuncTemp * eirAuxElecFuncPLR;
2873 8 : if (this->defrostType == DefrostType::Timed) {
2874 0 : this->powerUsage += this->defrostResistiveHeaterCap * this->defrostOpTimeFrac * reportingInterval;
2875 : }
2876 : }
2877 8 : this->powerUsage += this->standbyElecPower;
2878 :
2879 : // energy balance on heat pump
2880 : // this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage);
2881 8 : this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->fuelRate + this->powerUsage - this->standbyElecPower);
2882 :
2883 : // calculate source side outlet conditions
2884 8 : Real64 CpSrc = 0.0;
2885 8 : if (this->waterSource) {
2886 0 : auto &thisSourcePlantLoop = state.dataPlnt->PlantLoop(this->sourceSidePlantLoc.loopNum);
2887 0 : CpSrc = thisSourcePlantLoop.glycol->getSpecificHeat(state, this->sourceSideInletTemp, "PLFFHPEIR::simulate()");
2888 8 : } else if (this->airSource) {
2889 8 : CpSrc = Psychrometrics::PsyCpAirFnW(state.dataEnvrn->OutHumRat);
2890 : }
2891 : // this->sourceSideCp = CpSrc; // debuging variable
2892 : // Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc;
2893 8 : Real64 const sourceMCp = (this->sourceSideMassFlowRate < 1e-6 ? 1.0 : this->sourceSideMassFlowRate) * CpSrc;
2894 8 : this->sourceSideOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, this->sourceSideHeatTransfer / sourceMCp);
2895 : }
2896 :
2897 0 : void EIRFuelFiredHeatPump::sizeSrcSideASHP(EnergyPlusData &state)
2898 : {
2899 : // size the source-side for the air-source HP
2900 0 : bool errorsFound = false;
2901 :
2902 : // these variables will be used throughout this function as a temporary value of that physical state
2903 0 : Real64 tmpCapacity = this->referenceCapacity;
2904 0 : Real64 tmpLoadVolFlow = this->loadSideDesignVolFlowRate;
2905 0 : Real64 tmpSourceVolFlow = 0.0;
2906 :
2907 : // will leave like this for now
2908 : // need to update these to better values later
2909 0 : Real64 sourceSideInitTemp = 20.0;
2910 0 : Real64 sourceSideHumRat = 0.0;
2911 0 : if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating) {
2912 : // same here; update later
2913 0 : sourceSideInitTemp = 20.0;
2914 : }
2915 :
2916 0 : Real64 const rhoSrc = Psychrometrics::PsyRhoAirFnPbTdbW(state, state.dataEnvrn->StdBaroPress, sourceSideInitTemp, sourceSideHumRat);
2917 0 : Real64 const CpSrc = Psychrometrics::PsyCpAirFnW(sourceSideHumRat);
2918 :
2919 : // set the source-side flow rate
2920 0 : if (this->sourceSideDesignVolFlowRateWasAutoSized) {
2921 : // load-side capacity should already be set, so unless the flow rate is specified, we can set
2922 : // an assumed reasonable flow rate since this doesn't affect downstream components
2923 0 : Real64 DeltaT_src = 10.0;
2924 : // to get the source flow, we first must calculate the required heat impact on the source side
2925 : // First the definition of COP: COP = Qload/Power, therefore Power = Qload/COP
2926 : // Then the energy balance: Qsrc = Qload + Power
2927 : // Substituting for Power: Qsrc = Qload + Qload/COP, therefore Qsrc = Qload (1 + 1/COP)
2928 0 : Real64 const designSourceSideHeatTransfer = tmpCapacity * (1.0 + 1.0 / this->referenceCOP);
2929 : // To get the design source flow rate, just apply the sensible heat rate equation:
2930 : // Qsrc = rho_src * Vdot_src * Cp_src * DeltaT_src
2931 : // Vdot_src = Q_src / (rho_src * Cp_src * DeltaT_src)
2932 0 : tmpSourceVolFlow = designSourceSideHeatTransfer / (rhoSrc * CpSrc * DeltaT_src);
2933 0 : } else if (!this->sourceSideDesignVolFlowRateWasAutoSized && this->sourceSideDesignVolFlowRate > 0.0) {
2934 : // given the value by the user
2935 : // set it directly
2936 0 : tmpSourceVolFlow = this->sourceSideDesignVolFlowRate;
2937 : } else if (!this->sourceSideDesignVolFlowRateWasAutoSized && this->sourceSideDesignVolFlowRate == 0.0) { // LCOV_EXCL_LINE
2938 : // user gave a flow rate of 0
2939 : // protected by the input processor to be >0.0
2940 : // fatal out just in case
2941 : errorsFound = true; // LCOV_EXCL_LINE
2942 0 : ShowSevereError(state,
2943 0 : format("Invalid condenser flow rate for EIR PLHP (name={}; entered value: {}",
2944 0 : this->name,
2945 : this->sourceSideDesignVolFlowRate)); // LCOV_EXCL_LINE
2946 : } else {
2947 : // can't imagine how it would ever get to this point
2948 : // just assume it's the same as the load side if we don't have any sizing information
2949 : tmpSourceVolFlow = tmpLoadVolFlow; // LCOV_EXCL_LINE
2950 : }
2951 :
2952 0 : this->sourceSideDesignVolFlowRate = tmpSourceVolFlow;
2953 :
2954 0 : if (errorsFound) {
2955 : ShowFatalError(state, "Preceding sizing errors cause program termination"); // LCOV_EXCL_LINE
2956 : }
2957 0 : }
2958 :
2959 6 : void EIRFuelFiredHeatPump::resetReportingVariables()
2960 : {
2961 6 : this->loadSideHeatTransfer = 0.0;
2962 6 : this->loadSideEnergy = 0.0;
2963 6 : this->loadSideOutletTemp = this->loadSideInletTemp;
2964 6 : this->fuelRate = 0.0;
2965 6 : this->fuelEnergy = 0.0;
2966 6 : this->powerUsage = 0.0;
2967 6 : this->powerEnergy = 0.0;
2968 6 : this->sourceSideHeatTransfer = 0.0;
2969 6 : this->sourceSideOutletTemp = this->sourceSideInletTemp;
2970 6 : this->sourceSideEnergy = 0.0;
2971 6 : }
2972 :
2973 11 : PlantComponent *EIRFuelFiredHeatPump::factory(EnergyPlusData &state, DataPlant::PlantEquipmentType hp_type, const std::string &hp_name)
2974 : {
2975 11 : if (state.dataEIRFuelFiredHeatPump->getInputsFFHP) {
2976 6 : EIRFuelFiredHeatPump::processInputForEIRPLHP(state);
2977 6 : EIRFuelFiredHeatPump::pairUpCompanionCoils(state);
2978 6 : state.dataEIRFuelFiredHeatPump->getInputsFFHP = false;
2979 : }
2980 :
2981 16 : for (auto &plhp : state.dataEIRFuelFiredHeatPump->heatPumps) {
2982 12 : if (plhp.name == Util::makeUPPER(hp_name) && plhp.EIRHPType == hp_type) {
2983 7 : return &plhp;
2984 : }
2985 : }
2986 :
2987 8 : ShowFatalError(state, format("EIR Fuel-Fired Heat Pump factory: Error getting inputs for PLFFHP named: {}.", hp_name));
2988 : return nullptr; // LCOV_EXCL_LINE
2989 : }
2990 :
2991 6 : void EIRFuelFiredHeatPump::pairUpCompanionCoils(EnergyPlusData &state)
2992 : {
2993 13 : for (auto &thisHP : state.dataEIRFuelFiredHeatPump->heatPumps) {
2994 7 : if (!thisHP.companionCoilName.empty()) {
2995 2 : std::string thisCoilName = Util::makeUPPER(thisHP.name);
2996 2 : DataPlant::PlantEquipmentType thisCoilType = thisHP.EIRHPType;
2997 2 : std::string targetCompanionName = Util::makeUPPER(thisHP.companionCoilName);
2998 3 : for (auto &potentialCompanionCoil : state.dataEIRFuelFiredHeatPump->heatPumps) {
2999 3 : DataPlant::PlantEquipmentType potentialCompanionType = potentialCompanionCoil.EIRHPType;
3000 3 : std::string potentialCompanionName = Util::makeUPPER(potentialCompanionCoil.name);
3001 3 : if (potentialCompanionName == thisCoilName) {
3002 : // skip the current coil
3003 1 : continue;
3004 : }
3005 2 : if (potentialCompanionName == targetCompanionName) {
3006 2 : if (thisCoilType == potentialCompanionType) {
3007 0 : ShowSevereError(state,
3008 0 : format("Invalid companion specification for EIR Plant Loop Fuel-Fired Heat Pump named \"{}\"", thisCoilName));
3009 0 : ShowContinueError(state, "For heating objects, the companion must be a cooling object, and vice-versa");
3010 0 : ShowFatalError(state, "Invalid companion object causes program termination");
3011 : }
3012 2 : thisHP.companionHeatPumpCoil = &potentialCompanionCoil;
3013 2 : break;
3014 : }
3015 3 : }
3016 2 : if (!thisHP.companionHeatPumpCoil) {
3017 0 : ShowSevereError(state, "Could not find matching companion heat pump coil.");
3018 0 : ShowContinueError(state, format("Base coil: {}", thisCoilName));
3019 0 : ShowContinueError(state, format("Looking for companion coil named: {}", targetCompanionName));
3020 0 : ShowFatalError(state, "Simulation aborts due to previous severe error");
3021 : }
3022 2 : }
3023 : }
3024 6 : }
3025 :
3026 6 : void EIRFuelFiredHeatPump::processInputForEIRPLHP(EnergyPlusData &state)
3027 : {
3028 : struct ClassType
3029 : {
3030 : DataPlant::PlantEquipmentType thisType;
3031 : std::string nodesType;
3032 : std::function<Real64(Real64, Real64)> calcLoadOutletTemp;
3033 : std::function<Real64(Real64, Real64)> calcQsource;
3034 : std::function<Real64(Real64, Real64)> calcSourceOutletTemp;
3035 :
3036 12 : ClassType(DataPlant::PlantEquipmentType _thisType,
3037 : std::string _nodesType,
3038 : std::function<Real64(Real64, Real64)> _tLoadOutFunc,
3039 : std::function<Real64(Real64, Real64)> _qSrcFunc,
3040 : std::function<Real64(Real64, Real64)> _tSrcOutFunc)
3041 12 : : thisType(_thisType), nodesType(std::move(_nodesType)), calcLoadOutletTemp(_tLoadOutFunc), calcQsource(_qSrcFunc),
3042 12 : calcSourceOutletTemp(_tSrcOutFunc)
3043 : {
3044 12 : }
3045 : };
3046 : std::array<ClassType, 2> classesToInput = {
3047 : ClassType{DataPlant::PlantEquipmentType::HeatPumpFuelFiredCooling,
3048 : "Chilled Water Nodes",
3049 : EIRPlantLoopHeatPumps::EIRFuelFiredHeatPump::subtract,
3050 : EIRPlantLoopHeatPumps::EIRFuelFiredHeatPump::add,
3051 : EIRPlantLoopHeatPumps::EIRFuelFiredHeatPump::add},
3052 : ClassType{DataPlant::PlantEquipmentType::HeatPumpFuelFiredHeating,
3053 : "Hot Water Nodes",
3054 : EIRPlantLoopHeatPumps::EIRFuelFiredHeatPump::add,
3055 : EIRPlantLoopHeatPumps::EIRFuelFiredHeatPump::subtract,
3056 : EIRPlantLoopHeatPumps::EIRFuelFiredHeatPump::subtract},
3057 12 : };
3058 :
3059 6 : bool errorsFound = false;
3060 6 : std::string &cCurrentModuleObject = state.dataIPShortCut->cCurrentModuleObject;
3061 18 : for (auto &classToInput : classesToInput) {
3062 12 : cCurrentModuleObject = DataPlant::PlantEquipTypeNames[static_cast<int>(classToInput.thisType)];
3063 :
3064 : DataLoopNode::ConnectionObjectType objType = static_cast<DataLoopNode::ConnectionObjectType>(
3065 12 : getEnumValue(BranchNodeConnections::ConnectionObjectTypeNamesUC, Util::makeUPPER(cCurrentModuleObject)));
3066 :
3067 12 : auto const instances = state.dataInputProcessing->inputProcessor->epJSON.find(cCurrentModuleObject);
3068 12 : if (instances == state.dataInputProcessing->inputProcessor->epJSON.end()) continue;
3069 7 : auto &instancesValue = instances.value();
3070 14 : for (auto instance = instancesValue.begin(); instance != instancesValue.end(); ++instance) {
3071 7 : auto const &fields = instance.value();
3072 7 : auto const &thisObjectName = instance.key();
3073 7 : state.dataInputProcessing->inputProcessor->markObjectAsUsed(cCurrentModuleObject, thisObjectName);
3074 :
3075 7 : EIRFuelFiredHeatPump thisPLHP;
3076 :
3077 7 : thisPLHP.EIRHPType = classToInput.thisType;
3078 14 : std::string companionCoilFieldTag = "companion_heating_heat_pump_name";
3079 7 : std::string refCapFieldTag = "nominal_cooling_capacity";
3080 7 : if (thisPLHP.EIRHPType == DataPlant::PlantEquipmentType::HeatPumpFuelFiredHeating) {
3081 6 : companionCoilFieldTag = "companion_cooling_heat_pump_name";
3082 6 : refCapFieldTag = "nominal_heating_capacity";
3083 : }
3084 :
3085 : // A1-A3
3086 7 : thisPLHP.name = Util::makeUPPER(thisObjectName);
3087 14 : std::string loadSideInletNodeName = Util::makeUPPER(fields.at("water_inlet_node_name").get<std::string>());
3088 7 : std::string loadSideOutletNodeName = Util::makeUPPER(fields.at("water_outlet_node_name").get<std::string>());
3089 : // Implicit
3090 : // std::string condenserType = "AIRSOURCE"; // Util::makeUPPER(fields.at("condenser_type").get<std::string>());
3091 7 : thisPLHP.airSource = true;
3092 7 : thisPLHP.waterSource = false;
3093 :
3094 : // A4
3095 7 : std::string sourceSideInletNodeName = Util::makeUPPER(fields.at("air_source_node_name").get<std::string>());
3096 : // Util::makeUPPER(fields.at("source_side_outlet_node_name").get<std::string>());
3097 7 : std::string sourceSideOutletNodeName = format("{}_SOURCE_SIDE_OUTLET_NODE", thisPLHP.name);
3098 :
3099 : // A5
3100 7 : auto compCoilFound = fields.find(companionCoilFieldTag);
3101 7 : if (compCoilFound != fields.end()) { // optional field
3102 2 : thisPLHP.companionCoilName = Util::makeUPPER(compCoilFound.value().get<std::string>());
3103 : }
3104 :
3105 : // A6 Fuel Type
3106 7 : std::string tempRsrStr = Util::makeUPPER(fields.at("fuel_type").get<std::string>());
3107 7 : thisPLHP.fuelType = static_cast<Constant::eFuel>(getEnumValue(Constant::eFuelNamesUC, tempRsrStr));
3108 : // Validate fuel type input
3109 : static constexpr std::string_view RoutineName("processInputForEIRPLHP: ");
3110 7 : if (thisPLHP.fuelType == Constant::eFuel::Invalid) {
3111 0 : ShowSevereError(state, format("{}{}=\"{}\",", RoutineName, cCurrentModuleObject, thisPLHP.name));
3112 0 : ShowContinueError(state, format("Invalid Fuel Type = {}", tempRsrStr));
3113 0 : ShowContinueError(state, "Reset the Fuel Type to \"NaturalGas\".");
3114 0 : thisPLHP.fuelType = Constant::eFuel::NaturalGas;
3115 0 : errorsFound = true;
3116 : }
3117 :
3118 : // A7 End use category
3119 7 : thisPLHP.endUseSubcat = Util::makeUPPER(fields.at("end_use_subcategory").get<std::string>());
3120 7 : if (thisPLHP.endUseSubcat == "") {
3121 0 : thisPLHP.endUseSubcat = "Heat Pump Fuel Fired"; // or "General"?
3122 : }
3123 :
3124 : // N1 Nominal heating capacity
3125 7 : auto &tmpRefCapacity = fields.at(refCapFieldTag);
3126 :
3127 7 : if (tmpRefCapacity == "Autosize") {
3128 0 : thisPLHP.referenceCapacity = DataSizing::AutoSize;
3129 0 : thisPLHP.referenceCapacityWasAutoSized = true;
3130 : } else {
3131 7 : thisPLHP.referenceCapacity = tmpRefCapacity.get<Real64>();
3132 : }
3133 :
3134 : // N2 Nominal heating capacity
3135 7 : thisPLHP.referenceCOP = fields.at("nominal_cop").get<Real64>();
3136 7 : if (thisPLHP.referenceCOP <= 0.0) thisPLHP.referenceCOP = 1.0;
3137 :
3138 : // N3 Design flow rate
3139 7 : auto &tmpFlowRate = fields.at("design_flow_rate");
3140 7 : if (tmpFlowRate == "Autosize") {
3141 0 : thisPLHP.loadSideDesignVolFlowRate = DataSizing::AutoSize;
3142 0 : thisPLHP.loadSideDesignVolFlowRateWasAutoSized = true;
3143 : } else {
3144 7 : thisPLHP.loadSideDesignVolFlowRate = tmpFlowRate.get<Real64>();
3145 : }
3146 :
3147 : // GAHP: Add a default source side flow rate, not from input
3148 7 : Real64 defDummyASDesVolFlowRate = 1.0;
3149 7 : thisPLHP.sourceSideDesignVolFlowRate = defDummyASDesVolFlowRate;
3150 :
3151 : // N4 Design supply temperature
3152 7 : auto &tmpDesSupTemp = fields.at("design_supply_temperature");
3153 7 : if (tmpDesSupTemp == "Autosize") {
3154 : // sizing
3155 : } else {
3156 7 : thisPLHP.desSupplyTemp = tmpDesSupTemp.get<Real64>();
3157 : }
3158 :
3159 : // N5 Design temperature lift
3160 7 : auto &tmpDesTempLift = fields.at("design_temperature_lift");
3161 7 : if (tmpDesTempLift == "Autosize") {
3162 : // sizing
3163 : } else {
3164 7 : thisPLHP.desTempLift = tmpDesTempLift.get<Real64>();
3165 : }
3166 :
3167 : // N6 Sizing factor
3168 7 : auto sizeFactorFound = fields.find("sizing_factor");
3169 7 : if (sizeFactorFound != fields.end()) {
3170 7 : thisPLHP.sizingFactor = sizeFactorFound.value().get<Real64>();
3171 7 : if (thisPLHP.sizingFactor <= 0.0) thisPLHP.sizingFactor = 1.0;
3172 : } else {
3173 0 : Real64 defaultVal_sizeFactor = 1.0;
3174 0 : if (!state.dataInputProcessing->inputProcessor->getDefaultValue(
3175 : state, cCurrentModuleObject, "sizing_factor", defaultVal_sizeFactor)) {
3176 0 : ShowSevereError(state, "EIR FFHP: Sizing factor not entered and could not get default value");
3177 0 : errorsFound = true;
3178 : } else {
3179 0 : thisPLHP.sizingFactor = defaultVal_sizeFactor;
3180 : }
3181 : }
3182 :
3183 : // A8 flow mode
3184 7 : thisPLHP.flowMode = static_cast<DataPlant::FlowMode>(
3185 14 : getEnumValue(DataPlant::FlowModeNamesUC, Util::makeUPPER(fields.at("flow_mode").get<std::string>())));
3186 :
3187 : // A9 outdoor_air_temperature_curve_input_variable
3188 7 : std::string oaTempCurveInputVar = Util::makeUPPER(fields.at("outdoor_air_temperature_curve_input_variable").get<std::string>());
3189 7 : thisPLHP.oaTempCurveInputVar = static_cast<OATempCurveVar>(getEnumValue(OATempCurveVarNamesUC, oaTempCurveInputVar));
3190 :
3191 : // A10 water_temperature_curve_input_variable
3192 7 : std::string waterTempCurveInputVar = Util::makeUPPER(fields.at("water_temperature_curve_input_variable").get<std::string>());
3193 7 : thisPLHP.waterTempCurveInputVar = static_cast<WaterTempCurveVar>(getEnumValue(WaterTempCurveVarNamesUC, waterTempCurveInputVar));
3194 :
3195 : // A11 normalized_capacity_function_of_temperature_curve_name
3196 7 : std::string const &capFtName = Util::makeUPPER(fields.at("normalized_capacity_function_of_temperature_curve_name").get<std::string>());
3197 :
3198 7 : thisPLHP.capFuncTempCurveIndex = Curve::GetCurveIndex(state, capFtName);
3199 7 : if (thisPLHP.capFuncTempCurveIndex == 0) {
3200 0 : ShowSevereError(state, format("Invalid curve name for EIR PLFFHP (name={}; entered curve name: {}", thisPLHP.name, capFtName));
3201 0 : errorsFound = true;
3202 : }
3203 :
3204 : // A12 fuel_energy_input_ratio_function_of_temperature_curve_name
3205 : std::string const &eirFtName =
3206 7 : Util::makeUPPER(fields.at("fuel_energy_input_ratio_function_of_temperature_curve_name").get<std::string>());
3207 7 : thisPLHP.powerRatioFuncTempCurveIndex = Curve::GetCurveIndex(state, eirFtName);
3208 7 : if (thisPLHP.capFuncTempCurveIndex == 0) {
3209 0 : ShowSevereError(state, format("Invalid curve name for EIR PLFFHP (name={}; entered curve name: {}", thisPLHP.name, eirFtName));
3210 0 : errorsFound = true;
3211 : }
3212 : // A13 fuel_energy_input_ratio_function_of_plr_curve_name
3213 7 : std::string const &eirFplrName = Util::makeUPPER(fields.at("fuel_energy_input_ratio_function_of_plr_curve_name").get<std::string>());
3214 7 : thisPLHP.powerRatioFuncPLRCurveIndex = Curve::GetCurveIndex(state, eirFplrName);
3215 7 : if (thisPLHP.capFuncTempCurveIndex == 0) {
3216 0 : ShowSevereError(state, format("Invalid curve name for EIR PLFFHP (name={}; entered curve name: {}", thisPLHP.name, eirFplrName));
3217 0 : errorsFound = true;
3218 : }
3219 :
3220 : // N7 min PLR
3221 7 : auto minPLRFound = fields.find("minimum_part_load_ratio");
3222 7 : if (minPLRFound != fields.end()) {
3223 7 : thisPLHP.minPLR = minPLRFound.value().get<Real64>();
3224 : } else {
3225 0 : Real64 defaultVal = 0.1;
3226 0 : if (!state.dataInputProcessing->inputProcessor->getDefaultValue(state, cCurrentModuleObject, "minimum_part_load_ratio", defaultVal)) {
3227 0 : ShowSevereError(state, "EIR PLFFHP: minimum PLR not entered and could not get default value.");
3228 0 : errorsFound = true;
3229 : } else {
3230 0 : thisPLHP.minPLR = defaultVal;
3231 : }
3232 : }
3233 :
3234 : // N8 max PLR
3235 7 : auto maxPLRFound = fields.find("maximum_part_load_ratio");
3236 7 : if (maxPLRFound != fields.end()) {
3237 7 : thisPLHP.maxPLR = maxPLRFound.value().get<Real64>();
3238 : } else {
3239 0 : Real64 defaultVal = 1.0;
3240 0 : if (!state.dataInputProcessing->inputProcessor->getDefaultValue(state, cCurrentModuleObject, "maximum_part_load_ratio", defaultVal)) {
3241 0 : ShowSevereError(state, "EIR PLFFHP: maximum PLR not entered and could not get default value.");
3242 0 : errorsFound = true;
3243 : } else {
3244 0 : thisPLHP.maxPLR = defaultVal;
3245 : }
3246 : }
3247 :
3248 : // A14 fuel_energy_input_ratio_defrost_adjustment_curve_name
3249 7 : if (thisPLHP.EIRHPType == DataPlant::PlantEquipmentType::HeatPumpFuelFiredCooling) {
3250 1 : thisPLHP.defrostEIRCurveIndex = 0;
3251 : } else {
3252 6 : auto eirDefrostCurveFound = fields.find("fuel_energy_input_ratio_defrost_adjustment_curve_name");
3253 6 : if (eirDefrostCurveFound != fields.end()) {
3254 3 : std::string const eirDefrostCurveName = Util::makeUPPER(eirDefrostCurveFound.value().get<std::string>());
3255 3 : thisPLHP.defrostEIRCurveIndex = Curve::GetCurveIndex(state, eirDefrostCurveName);
3256 3 : if (thisPLHP.defrostEIRCurveIndex == 0) {
3257 0 : ShowSevereError(
3258 0 : state, format("Invalid curve name for EIR FFHP (name={}; entered curve name: {}", thisPLHP.name, eirDefrostCurveName));
3259 0 : errorsFound = true;
3260 : }
3261 3 : } else {
3262 3 : thisPLHP.defrostEIRCurveIndex = 0;
3263 : }
3264 : }
3265 :
3266 : // A15 defrost_control_type
3267 7 : if (thisPLHP.EIRHPType == DataPlant::PlantEquipmentType::HeatPumpFuelFiredCooling) {
3268 1 : thisPLHP.defrostType = DefrostType::Invalid;
3269 : } else {
3270 6 : thisPLHP.defrostType =
3271 6 : static_cast<DefrostType>(getEnumValue(DefrostTypeNamesUC, Util::makeUPPER(fields.at("defrost_control_type").get<std::string>())));
3272 6 : if (thisPLHP.defrostType == DefrostType::Invalid) {
3273 0 : thisPLHP.defrostType = DefrostType::OnDemand; // set to default
3274 0 : thisPLHP.defrostOpTimeFrac = 0.0;
3275 0 : ShowWarningError(state, format("Invalid Defrost Control Type for EIR PLFFHP ({} name={})", cCurrentModuleObject, thisPLHP.name));
3276 0 : ShowContinueError(state,
3277 0 : format("The Input Variable is reset to: {}", DefrostTypeNamesUC[static_cast<int>(thisPLHP.defrostType)]));
3278 : }
3279 : }
3280 :
3281 : // N9 defrost_operation_time_fraction
3282 7 : if (thisPLHP.EIRHPType == DataPlant::PlantEquipmentType::HeatPumpFuelFiredCooling) {
3283 1 : thisPLHP.defrostOpTimeFrac = 0.0;
3284 : } else {
3285 6 : auto defrostOpTimeFracFound = fields.find("defrost_operation_time_fraction");
3286 6 : if (defrostOpTimeFracFound != fields.end()) {
3287 1 : thisPLHP.defrostOpTimeFrac = defrostOpTimeFracFound.value().get<Real64>();
3288 : } else {
3289 5 : Real64 defaultVal = 0.0;
3290 15 : if (!state.dataInputProcessing->inputProcessor->getDefaultValue(
3291 : state, cCurrentModuleObject, "defrost_operation_time_fraction", defaultVal)) {
3292 0 : ShowSevereError(state, "EIR PLFFHP: defrost time fraction not entered and could not get default value.");
3293 0 : errorsFound = true;
3294 : } else {
3295 5 : thisPLHP.defrostOpTimeFrac = defaultVal;
3296 : }
3297 : }
3298 : }
3299 :
3300 : // N10 Resistive Defrost Heater Capacity
3301 7 : if (thisPLHP.EIRHPType == DataPlant::PlantEquipmentType::HeatPumpFuelFiredCooling) {
3302 1 : thisPLHP.defrostResistiveHeaterCap = 0.0;
3303 : } else {
3304 6 : auto resDefrostHeaterCapFound = fields.find("resistive_defrost_heater_capacity");
3305 6 : if (resDefrostHeaterCapFound != fields.end()) {
3306 3 : thisPLHP.defrostResistiveHeaterCap = resDefrostHeaterCapFound.value().get<Real64>();
3307 : } else {
3308 3 : Real64 defaultVal = 0.0;
3309 9 : if (!state.dataInputProcessing->inputProcessor->getDefaultValue(
3310 : state, cCurrentModuleObject, "resistive_defrost_heater_capacity", defaultVal)) {
3311 0 : ShowSevereError(state, "EIR PLFFHP: Resistive Defrost Heater Capacity not entered and could not get default value.");
3312 0 : errorsFound = true;
3313 : } else {
3314 3 : thisPLHP.defrostResistiveHeaterCap = defaultVal;
3315 : }
3316 : }
3317 : }
3318 :
3319 : // N11 maximum_outdoor_dry_bulb_temperature_for_defrost_operation
3320 7 : if (thisPLHP.EIRHPType == DataPlant::PlantEquipmentType::HeatPumpFuelFiredCooling) {
3321 1 : thisPLHP.defrostMaxOADBT = -99.0;
3322 : } else {
3323 6 : auto maxOADBTFound = fields.find("maximum_outdoor_dry_bulb_temperature_for_defrost_operation");
3324 6 : if (maxOADBTFound != fields.end()) {
3325 4 : thisPLHP.defrostMaxOADBT = maxOADBTFound.value().get<Real64>();
3326 : } else {
3327 2 : Real64 defaultVal = 5.0;
3328 6 : if (!state.dataInputProcessing->inputProcessor->getDefaultValue(
3329 : state, cCurrentModuleObject, "maximum_outdoor_dry_bulb_temperature_for_defrost_operation", defaultVal)) {
3330 0 : ShowSevereError(state, "EIR PLFFHP: max defrost operation OA temperature not entered and could not get default value.");
3331 0 : errorsFound = true;
3332 : } else {
3333 2 : thisPLHP.defrostMaxOADBT = defaultVal;
3334 : }
3335 : }
3336 : }
3337 :
3338 : // A16 cycling_ratio_factor_curve_name
3339 7 : auto crfCurveFound = fields.find("cycling_ratio_factor_curve_name");
3340 7 : if (crfCurveFound != fields.end()) {
3341 7 : std::string const cycRatioCurveName = Util::makeUPPER(crfCurveFound.value().get<std::string>());
3342 7 : thisPLHP.cycRatioCurveIndex = Curve::GetCurveIndex(state, cycRatioCurveName);
3343 7 : if (thisPLHP.cycRatioCurveIndex == 0) {
3344 0 : ShowSevereError(state,
3345 0 : format("Invalid curve name for EIR PLFFHP (name={}; entered curve name: {})", thisPLHP.name, cycRatioCurveName));
3346 0 : errorsFound = true;
3347 : }
3348 7 : } else {
3349 0 : thisPLHP.cycRatioCurveIndex = 0;
3350 : }
3351 :
3352 : // N12 nominal_auxiliary_electric_power
3353 7 : auto nomAuxElecPowerFound = fields.find("nominal_auxiliary_electric_power");
3354 7 : if (nomAuxElecPowerFound != fields.end()) {
3355 7 : thisPLHP.nominalAuxElecPower = nomAuxElecPowerFound.value().get<Real64>();
3356 : } else {
3357 0 : Real64 defaultVal = 0.0;
3358 0 : if (!state.dataInputProcessing->inputProcessor->getDefaultValue(
3359 : state, cCurrentModuleObject, "nominal_auxiliary_electric_power", defaultVal)) {
3360 0 : ShowSevereError(state, "EIR PLFFHP: nominal auxiliary electric power not entered and could not get default value.");
3361 0 : errorsFound = true;
3362 : } else {
3363 0 : thisPLHP.nominalAuxElecPower = defaultVal;
3364 : }
3365 : }
3366 :
3367 : // A17 auxiliary_electric_energy_input_ratio_function_of_temperature_curve_name
3368 7 : auto auxElecEIRFTCurveFound = fields.find("auxiliary_electric_energy_input_ratio_function_of_temperature_curve_name");
3369 7 : if (auxElecEIRFTCurveFound != fields.end()) {
3370 7 : std::string const &auxEIRFTName = Util::makeUPPER(auxElecEIRFTCurveFound.value().get<std::string>());
3371 7 : thisPLHP.auxElecEIRFoTempCurveIndex = Curve::GetCurveIndex(state, auxEIRFTName);
3372 7 : if (thisPLHP.auxElecEIRFoTempCurveIndex == 0) {
3373 0 : ShowSevereError(state, format("Invalid curve name for EIR FFHP (name={}; entered curve name: {}", thisPLHP.name, auxEIRFTName));
3374 0 : errorsFound = true;
3375 : }
3376 7 : } else {
3377 0 : thisPLHP.auxElecEIRFoTempCurveIndex = 0;
3378 : }
3379 :
3380 : // A18 auxiliary_electric_energy_input_ratio_function_of_plr_curve_name
3381 7 : auto auxElecEIRFPLRCurveFound = fields.find("auxiliary_electric_energy_input_ratio_function_of_plr_curve_name");
3382 7 : if (auxElecEIRFPLRCurveFound != fields.end()) {
3383 7 : std::string const &auxEIRFPLRName = Util::makeUPPER(auxElecEIRFPLRCurveFound.value().get<std::string>());
3384 7 : thisPLHP.auxElecEIRFoPLRCurveIndex = Curve::GetCurveIndex(state, auxEIRFPLRName);
3385 7 : if (thisPLHP.auxElecEIRFoPLRCurveIndex == 0) {
3386 0 : ShowSevereError(state, format("Invalid curve name for EIR FFHP (name={}; entered curve name: {}", thisPLHP.name, auxEIRFPLRName));
3387 0 : errorsFound = true;
3388 : }
3389 7 : } else {
3390 0 : thisPLHP.auxElecEIRFoPLRCurveIndex = 0;
3391 : }
3392 :
3393 : // N13 standby_electric_power
3394 7 : auto stdElecPwrFound = fields.find("standby_electric_power");
3395 7 : if (stdElecPwrFound != fields.end()) {
3396 7 : thisPLHP.standbyElecPower = stdElecPwrFound.value().get<Real64>();
3397 : } else {
3398 0 : Real64 defaultVal = 0.0;
3399 0 : if (!state.dataInputProcessing->inputProcessor->getDefaultValue(state, cCurrentModuleObject, "standby_electric_power", defaultVal)) {
3400 0 : ShowSevereError(state, "EIR FFHP: standby electric power not entered and could not get default value.");
3401 0 : errorsFound = true;
3402 : } else {
3403 0 : thisPLHP.standbyElecPower = defaultVal;
3404 : }
3405 : }
3406 :
3407 7 : bool nodeErrorsFound = false;
3408 7 : thisPLHP.loadSideNodes.inlet = NodeInputManager::GetOnlySingleNode(state,
3409 : loadSideInletNodeName,
3410 : nodeErrorsFound,
3411 : objType,
3412 : thisPLHP.name,
3413 : DataLoopNode::NodeFluidType::Water,
3414 : DataLoopNode::ConnectionType::Inlet,
3415 : NodeInputManager::CompFluidStream::Primary,
3416 : DataLoopNode::ObjectIsNotParent);
3417 7 : thisPLHP.loadSideNodes.outlet = NodeInputManager::GetOnlySingleNode(state,
3418 : loadSideOutletNodeName,
3419 : nodeErrorsFound,
3420 : objType,
3421 : thisPLHP.name,
3422 : DataLoopNode::NodeFluidType::Water,
3423 : DataLoopNode::ConnectionType::Outlet,
3424 : NodeInputManager::CompFluidStream::Primary,
3425 : DataLoopNode::ObjectIsNotParent);
3426 :
3427 7 : thisPLHP.airSource = true; // this is always true, at least for now, for Fuel-Fired PlantLoop Heat Pump
3428 7 : thisPLHP.waterSource = false; // this is always false, at least for now, for Fuel-Fired PlantLoop Heat Pump
3429 7 : thisPLHP.sourceSideNodes.inlet = NodeInputManager::GetOnlySingleNode(state,
3430 : sourceSideInletNodeName,
3431 : nodeErrorsFound,
3432 : objType,
3433 : thisPLHP.name,
3434 : DataLoopNode::NodeFluidType::Air,
3435 : DataLoopNode::ConnectionType::OutsideAir,
3436 : NodeInputManager::CompFluidStream::Secondary,
3437 : DataLoopNode::ObjectIsNotParent);
3438 7 : thisPLHP.sourceSideNodes.outlet = NodeInputManager::GetOnlySingleNode(state,
3439 : sourceSideOutletNodeName,
3440 : nodeErrorsFound,
3441 : objType,
3442 : thisPLHP.name,
3443 : DataLoopNode::NodeFluidType::Air,
3444 : DataLoopNode::ConnectionType::OutsideAir,
3445 : NodeInputManager::CompFluidStream::Secondary,
3446 : DataLoopNode::ObjectIsNotParent);
3447 :
3448 7 : if (nodeErrorsFound) errorsFound = true;
3449 7 : BranchNodeConnections::TestCompSet(
3450 7 : state, cCurrentModuleObject, thisPLHP.name, loadSideInletNodeName, loadSideOutletNodeName, classToInput.nodesType);
3451 :
3452 : // store the worker functions that generalized the heating/cooling sides
3453 7 : thisPLHP.calcLoadOutletTemp = classToInput.calcLoadOutletTemp;
3454 7 : thisPLHP.calcQsource = classToInput.calcQsource;
3455 7 : thisPLHP.calcSourceOutletTemp = classToInput.calcSourceOutletTemp;
3456 :
3457 7 : if (!errorsFound) {
3458 7 : state.dataEIRFuelFiredHeatPump->heatPumps.push_back(thisPLHP);
3459 : }
3460 7 : }
3461 : }
3462 6 : if (errorsFound) {
3463 : ShowFatalError(state, "Previous EIR PLFFHP errors cause program termination."); // LCOV_EXCL_LINE
3464 : }
3465 6 : }
3466 :
3467 5 : void EIRFuelFiredHeatPump::oneTimeInit(EnergyPlusData &state)
3468 : {
3469 : // This function does all the one-time initialization
3470 5 : constexpr std::string_view routineName = "EIRFuelFiredHeatPump : oneTimeInit"; // + __FUNCTION__;
3471 :
3472 5 : if (this->oneTimeInitFlag) {
3473 5 : bool errFlag = false;
3474 :
3475 : // setup output variables
3476 10 : SetupOutputVariable(state,
3477 : "Fuel-fired Absorption HeatPump Load Side Heat Transfer Rate",
3478 : Constant::Units::W,
3479 5 : this->loadSideHeatTransfer,
3480 : OutputProcessor::TimeStepType::System,
3481 : OutputProcessor::StoreType::Average,
3482 5 : this->name);
3483 10 : SetupOutputVariable(state,
3484 : "Fuel-fired Absorption HeatPump Load Side Heat Transfer Energy",
3485 : Constant::Units::J,
3486 5 : this->loadSideEnergy,
3487 : OutputProcessor::TimeStepType::System,
3488 : OutputProcessor::StoreType::Sum,
3489 5 : this->name,
3490 : Constant::eResource::EnergyTransfer,
3491 : OutputProcessor::Group::Plant);
3492 : // Setup Output Variable(state,
3493 : // "Fuel-fired Absorption Heat Pump Source Side Heat Transfer Rate",
3494 : // Constant::Units::W,
3495 : // this->sourceSideHeatTransfer,
3496 : // OutputProcessor::TimeStepType::System,
3497 : // OutputProcessor::StoreType::Average,
3498 : // this->name);
3499 : // Setup Output Variable(state,
3500 : // "Fuel-fired Absorption Heat Pump Source Side Heat Transfer Energy",
3501 : // Constant::Units::J,
3502 : // this->sourceSideEnergy,
3503 : // OutputProcessor::TimeStepType::System,
3504 : // OutputProcessor::StoreType::Sum,
3505 : // this->name);
3506 10 : SetupOutputVariable(state,
3507 : "Fuel-fired Absorption HeatPump Inlet Temperature", // "Heat Pump Load Side Inlet Temperature",
3508 : Constant::Units::C,
3509 5 : this->loadSideInletTemp,
3510 : OutputProcessor::TimeStepType::System,
3511 : OutputProcessor::StoreType::Average,
3512 5 : this->name);
3513 10 : SetupOutputVariable(state,
3514 : "Fuel-fired Absorption HeatPump Outlet Temperature", // "Heat Pump Load Side Outlet Temperature",
3515 : Constant::Units::C,
3516 5 : this->loadSideOutletTemp,
3517 : OutputProcessor::TimeStepType::System,
3518 : OutputProcessor::StoreType::Average,
3519 5 : this->name);
3520 : // Setup Output Variable(state,
3521 : // "Fuel-fired Absorption Heat Pump Source Side Inlet Temperature",
3522 : // Constant::Units::C,
3523 : // this->sourceSideInletTemp,
3524 : // OutputProcessor::TimeStepType::System,
3525 : // OutputProcessor::StoreType::Average,
3526 : // this->name);
3527 : // Setup Output Variable(state,
3528 : // "Heat Pump Source Side Outlet Temperature",
3529 : // Constant::Units::C,
3530 : // this->sourceSideOutletTemp,
3531 : // OutputProcessor::TimeStepType::System,
3532 : // OutputProcessor::StoreType::Average,
3533 : // this->name);
3534 10 : SetupOutputVariable(state,
3535 : "Fuel-fired Absorption HeatPump Fuel Rate",
3536 : Constant::Units::W,
3537 5 : this->fuelRate,
3538 : OutputProcessor::TimeStepType::System,
3539 : OutputProcessor::StoreType::Average,
3540 5 : this->name);
3541 10 : SetupOutputVariable(state,
3542 : "Fuel-fired Absorption HeatPump Electricity Rate",
3543 : Constant::Units::W,
3544 5 : this->powerUsage,
3545 : OutputProcessor::TimeStepType::System,
3546 : OutputProcessor::StoreType::Average,
3547 5 : this->name);
3548 5 : if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpFuelFiredCooling) { // energy from HeatPump:AirToWater:FuelFired:Cooling object
3549 2 : SetupOutputVariable(state,
3550 : "Fuel-fired Absorption HeatPump Fuel Energy",
3551 : Constant::Units::J,
3552 1 : this->fuelEnergy,
3553 : OutputProcessor::TimeStepType::System,
3554 : OutputProcessor::StoreType::Sum,
3555 1 : this->name,
3556 1 : Constant::eFuel2eResource[(int)this->fuelType],
3557 : OutputProcessor::Group::Plant,
3558 : OutputProcessor::EndUseCat::Cooling,
3559 : this->endUseSubcat); //"Heat Pump",
3560 2 : SetupOutputVariable(state,
3561 : "Fuel-fired Absorption HeatPump Electricity Energy",
3562 : Constant::Units::J,
3563 1 : this->powerEnergy,
3564 : OutputProcessor::TimeStepType::System,
3565 : OutputProcessor::StoreType::Sum,
3566 1 : this->name,
3567 : Constant::eResource::Electricity,
3568 : OutputProcessor::Group::Plant,
3569 : OutputProcessor::EndUseCat::Cooling,
3570 : this->endUseSubcat); // "Heat Pump",
3571 4 : } else if (this->EIRHPType ==
3572 : DataPlant::PlantEquipmentType::HeatPumpFuelFiredHeating) { // energy from HeatPump:AirToWater:FuelFired:Heating object
3573 8 : SetupOutputVariable(state,
3574 : "Fuel-fired Absorption HeatPump Fuel Energy",
3575 : Constant::Units::J,
3576 4 : this->fuelEnergy,
3577 : OutputProcessor::TimeStepType::System,
3578 : OutputProcessor::StoreType::Sum,
3579 4 : this->name,
3580 4 : Constant::eFuel2eResource[(int)this->fuelType],
3581 : OutputProcessor::Group::Plant,
3582 : OutputProcessor::EndUseCat::Heating,
3583 : this->endUseSubcat); // "Heat Pump",
3584 8 : SetupOutputVariable(state,
3585 : "Fuel-fired Absorption HeatPump Electricity Energy",
3586 : Constant::Units::J,
3587 4 : this->powerEnergy,
3588 : OutputProcessor::TimeStepType::System,
3589 : OutputProcessor::StoreType::Sum,
3590 4 : this->name,
3591 : Constant::eResource::Electricity,
3592 : OutputProcessor::Group::Plant,
3593 : OutputProcessor::EndUseCat::Heating,
3594 : this->endUseSubcat); // "Heat Pump",
3595 : }
3596 10 : SetupOutputVariable(state,
3597 : "Fuel-fired Absorption HeatPump Mass Flow Rate",
3598 : Constant::Units::kg_s,
3599 5 : this->loadSideMassFlowRate,
3600 : OutputProcessor::TimeStepType::System,
3601 : OutputProcessor::StoreType::Average,
3602 5 : this->name);
3603 10 : SetupOutputVariable(state,
3604 : "Fuel-fired Absorption HeatPump Volumetric Flow Rate",
3605 : Constant::Units::m3_s,
3606 5 : this->loadSideVolumeFlowRate,
3607 : OutputProcessor::TimeStepType::System,
3608 : OutputProcessor::StoreType::Average,
3609 5 : this->name);
3610 :
3611 : // find this component on the plant
3612 5 : bool thisErrFlag = false;
3613 15 : PlantUtilities::ScanPlantLoopsForObject(
3614 10 : state, this->name, this->EIRHPType, this->loadSidePlantLoc, thisErrFlag, _, _, _, this->loadSideNodes.inlet, _);
3615 :
3616 5 : if (thisErrFlag) {
3617 0 : ShowSevereError(state,
3618 0 : format("{}: Plant topology problem for {} name = \"{}\"",
3619 : routineName,
3620 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
3621 0 : this->name));
3622 0 : ShowContinueError(state, "Could not locate component's load side connections on a plant loop");
3623 0 : errFlag = true;
3624 5 : } else if (this->loadSidePlantLoc.loopSideNum != DataPlant::LoopSideLocation::Supply) { // only check if !thisErrFlag
3625 0 : ShowSevereError(state,
3626 0 : format("{}: Invalid connections for {} name = \"{}\"",
3627 : routineName,
3628 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
3629 0 : this->name));
3630 0 : ShowContinueError(state, "The load side connections are not on the Supply Side of a plant loop");
3631 0 : errFlag = true;
3632 : }
3633 :
3634 5 : thisErrFlag = false;
3635 5 : if (this->waterSource) {
3636 0 : PlantUtilities::ScanPlantLoopsForObject(
3637 0 : state, this->name, this->EIRHPType, this->sourceSidePlantLoc, thisErrFlag, _, _, _, this->sourceSideNodes.inlet, _);
3638 :
3639 0 : if (thisErrFlag) {
3640 0 : ShowSevereError(state,
3641 0 : format("{}: Plant topology problem for {} name = \"{}\"",
3642 : routineName,
3643 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
3644 0 : this->name));
3645 0 : ShowContinueError(state, "Could not locate component's source side connections on a plant loop.");
3646 0 : errFlag = true;
3647 0 : } else if (this->sourceSidePlantLoc.loopSideNum != DataPlant::LoopSideLocation::Demand) { // only check if !thisErrFlag
3648 0 : ShowSevereError(state,
3649 0 : format("{}: Invalid connections for {} name = \"{}\"",
3650 : routineName,
3651 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
3652 0 : this->name));
3653 0 : ShowContinueError(state, "The source side connections are not on the Demand Side of a plant loop.");
3654 0 : errFlag = true;
3655 : }
3656 :
3657 : // make sure it is not the same loop on both sides.
3658 0 : if (this->loadSidePlantLoc.loopNum == this->sourceSidePlantLoc.loopNum) { // user is being too tricky, don't allow
3659 0 : ShowSevereError(state,
3660 0 : format("{}: Invalid connections for {} name = \"{}\"",
3661 : routineName,
3662 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->EIRHPType)],
3663 0 : this->name));
3664 0 : ShowContinueError(state, "The load and source sides need to be on different loops.");
3665 0 : errFlag = true;
3666 : } else {
3667 :
3668 0 : PlantUtilities::InterConnectTwoPlantLoopSides(state, this->loadSidePlantLoc, this->sourceSidePlantLoc, this->EIRHPType, true);
3669 : }
3670 5 : } else if (this->airSource) {
3671 : // nothing to do here ?
3672 : }
3673 :
3674 5 : if (errFlag) {
3675 0 : ShowFatalError(state, format("{}: Program terminated due to previous condition(s).", routineName));
3676 : }
3677 5 : this->oneTimeInitFlag = false;
3678 : }
3679 5 : }
3680 :
3681 14 : void EIRFuelFiredHeatPump::report(EnergyPlusData &state)
3682 : {
3683 14 : Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec;
3684 :
3685 14 : this->fuelEnergy = this->fuelRate * reportingInterval;
3686 14 : this->loadSideEnergy = this->loadSideHeatTransfer * reportingInterval;
3687 14 : this->powerEnergy = this->powerUsage * reportingInterval;
3688 14 : this->sourceSideEnergy = this->sourceSideHeatTransfer * reportingInterval;
3689 :
3690 : // update nodes
3691 14 : PlantUtilities::SafeCopyPlantNode(state, this->loadSideNodes.inlet, this->loadSideNodes.outlet);
3692 14 : state.dataLoopNodes->Node(this->loadSideNodes.outlet).Temp = this->loadSideOutletTemp;
3693 14 : state.dataLoopNodes->Node(this->sourceSideNodes.outlet).Temp = this->sourceSideOutletTemp;
3694 14 : }
3695 :
3696 0 : Real64 EIRFuelFiredHeatPump::getDynamicMaxCapacity(EnergyPlusData &state)
3697 : {
3698 : // Source (air) side temperature variable
3699 0 : auto &thisSourceSideInletNode = state.dataLoopNodes->Node(this->sourceSideNodes.inlet);
3700 : Real64 oaTempforCurve =
3701 0 : (this->oaTempCurveInputVar == OATempCurveVar::WetBulb)
3702 0 : ? Psychrometrics::PsyTwbFnTdbWPb(
3703 : state, thisSourceSideInletNode.Temp, thisSourceSideInletNode.HumRat, thisSourceSideInletNode.Press, "PLFFHPEIR::doPhysics()")
3704 0 : : thisSourceSideInletNode.Temp;
3705 :
3706 : // Load (water) side temperature variable
3707 0 : Real64 waterTempforCurve = state.dataLoopNodes->Node(this->loadSideNodes.inlet).Temp;
3708 0 : if (this->waterTempCurveInputVar == WaterTempCurveVar::LeavingCondenser || this->waterTempCurveInputVar == WaterTempCurveVar::LeavingEvaporator) {
3709 0 : if (this->flowMode == DataPlant::FlowMode::LeavingSetpointModulated) {
3710 0 : auto &thisLoadSideOutletNode = state.dataLoopNodes->Node(this->loadSideNodes.outlet);
3711 0 : if (this->loadSidePlantLoc.loop->LoopDemandCalcScheme == DataPlant::LoopDemandCalcScheme::SingleSetPoint) {
3712 0 : waterTempforCurve = thisLoadSideOutletNode.TempSetPoint;
3713 : } else {
3714 0 : if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpFuelFiredCooling) {
3715 0 : waterTempforCurve = thisLoadSideOutletNode.TempSetPointHi;
3716 : } else {
3717 0 : waterTempforCurve = thisLoadSideOutletNode.TempSetPointLo;
3718 : }
3719 : }
3720 : } else {
3721 : // If not SP modulated then use actual outlet temp from last iteration?
3722 0 : waterTempforCurve = state.dataLoopNodes->Node(this->loadSideNodes.outlet).Temp;
3723 : }
3724 : }
3725 :
3726 : // evaluate capacity modifier curve and determine load side heat transfer
3727 0 : Real64 capacityModifierFuncTemp = Curve::CurveValue(state, this->capFuncTempCurveIndex, waterTempforCurve, oaTempforCurve);
3728 0 : return this->referenceCapacity * capacityModifierFuncTemp;
3729 : }
3730 :
3731 : } // namespace EnergyPlus::EIRPlantLoopHeatPumps
|