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 : #include <utility>
49 :
50 : #include <EnergyPlus/Autosizing/CoolingAirFlowSizing.hh>
51 : #include <EnergyPlus/Autosizing/CoolingCapacitySizing.hh>
52 : #include <EnergyPlus/Autosizing/CoolingSHRSizing.hh>
53 : #include <EnergyPlus/Coils/CoilCoolingDXCurveFitSpeed.hh>
54 : #include <EnergyPlus/CurveManager.hh>
55 : #include <EnergyPlus/Data/EnergyPlusData.hh>
56 : #include <EnergyPlus/DataEnvironment.hh>
57 : #include <EnergyPlus/DataHVACGlobals.hh>
58 : #include <EnergyPlus/DataIPShortCuts.hh>
59 : #include <EnergyPlus/DataPrecisionGlobals.hh>
60 : #include <EnergyPlus/DataSizing.hh>
61 : #include <EnergyPlus/General.hh>
62 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
63 : #include <EnergyPlus/Psychrometrics.hh>
64 :
65 : using namespace EnergyPlus;
66 :
67 58 : void CoilCoolingDXCurveFitSpeed::instantiateFromInputSpec(EnergyPlus::EnergyPlusData &state,
68 : const CoilCoolingDXCurveFitSpeedInputSpecification &input_data)
69 : {
70 58 : bool errorsFound(false);
71 : static constexpr std::string_view routineName = "CoilCoolingDXCurveFitSpeed::instantiateFromInputSpec: ";
72 : static constexpr std::string_view fieldName = "Part Load Fraction Correlation Curve Name";
73 58 : this->original_input_specs = input_data;
74 58 : this->name = input_data.name;
75 58 : this->active_fraction_of_face_coil_area = input_data.active_fraction_of_coil_face_area;
76 58 : if (this->active_fraction_of_face_coil_area < 1.0) this->adjustForFaceArea = true;
77 58 : this->rated_evap_fan_power_per_volume_flow_rate = input_data.rated_evaporator_fan_power_per_volume_flow_rate;
78 58 : this->rated_evap_fan_power_per_volume_flow_rate_2023 = input_data.rated_evaporator_fan_power_per_volume_flow_rate_2023;
79 58 : this->evap_condenser_pump_power_fraction = input_data.rated_evaporative_condenser_pump_power_fraction;
80 58 : this->evap_condenser_effectiveness = input_data.evaporative_condenser_effectiveness;
81 58 : this->ratedWasteHeatFractionOfPowerInput = input_data.rated_waste_heat_fraction_of_power_input;
82 58 : this->ratedCOP = input_data.gross_rated_cooling_COP;
83 348 : errorsFound |= this->processCurve(state,
84 58 : input_data.total_cooling_capacity_function_of_temperature_curve_name,
85 58 : this->indexCapFT,
86 : {1, 2}, // MULTIPLECURVEDIMS
87 : routineName,
88 : "Total Cooling Capacity Function of Temperature Curve Name",
89 : RatedInletWetBulbTemp,
90 58 : RatedOutdoorAirTemp);
91 :
92 348 : errorsFound |= this->processCurve(state,
93 58 : input_data.total_cooling_capacity_function_of_air_flow_fraction_curve_name,
94 58 : this->indexCapFFF,
95 : {1},
96 : routineName,
97 : "Total Cooling Capacity Function of Air Flow Fraction Curve Name",
98 : 1.0);
99 :
100 348 : errorsFound |= this->processCurve(state,
101 58 : input_data.energy_input_ratio_function_of_temperature_curve_name,
102 58 : this->indexEIRFT,
103 : {1, 2}, // MULTIPLECURVEDIMS
104 : routineName,
105 : "Energy Input Ratio Function of Temperature Curve Name",
106 : RatedInletWetBulbTemp,
107 58 : RatedOutdoorAirTemp);
108 :
109 348 : errorsFound |= this->processCurve(state,
110 58 : input_data.energy_input_ratio_function_of_air_flow_fraction_curve_name,
111 58 : this->indexEIRFFF,
112 : {1},
113 : routineName,
114 : "Energy Input Ratio Function of Air Flow Fraction Curve Name",
115 : 1.0);
116 :
117 348 : errorsFound |= this->processCurve(state,
118 58 : input_data.sensible_heat_ratio_modifier_function_of_temperature_curve_name,
119 58 : this->indexSHRFT,
120 : {2}, // Only allow bivariate functions since curve inputs are different from other f(Temp) functions
121 : routineName,
122 : "Sensible Heat Ratio Modifier Function of Temperature Curve Name",
123 : RatedInletWetBulbTemp,
124 58 : RatedOutdoorAirTemp);
125 :
126 348 : errorsFound |= this->processCurve(state,
127 58 : input_data.sensible_heat_ratio_modifier_function_of_flow_fraction_curve_name,
128 58 : this->indexSHRFFF,
129 : {1},
130 : routineName,
131 : "Sensible Heat Ratio Modifier Function of Air Flow Fraction Curve Name",
132 : 1.0);
133 :
134 348 : errorsFound |= this->processCurve(state,
135 58 : input_data.waste_heat_function_of_temperature_curve_name,
136 58 : this->indexWHFT,
137 : {2},
138 : routineName,
139 : "Waste Heat Modifier Function of Temperature Curve Name",
140 : RatedOutdoorAirTemp,
141 58 : RatedInletAirTemp);
142 :
143 58 : if (!errorsFound && !input_data.waste_heat_function_of_temperature_curve_name.empty()) {
144 26 : Real64 CurveVal = Curve::CurveValue(state, this->indexWHFT, RatedOutdoorAirTemp, RatedInletAirTemp);
145 26 : if (CurveVal > 1.10 || CurveVal < 0.90) {
146 0 : ShowWarningError(state, std::string{routineName} + this->object_name + "=\"" + this->name + "\", curve values");
147 0 : ShowContinueError(state,
148 0 : "Waste Heat Modifier Function of Temperature Curve Name = " + input_data.waste_heat_function_of_temperature_curve_name);
149 0 : ShowContinueError(
150 : state, "...Waste Heat Modifier Function of Temperature Curve Name output is not equal to 1.0 (+ or - 10%) at rated conditions.");
151 0 : ShowContinueError(state, format("...Curve output at rated conditions = {:.3T}", CurveVal));
152 : }
153 : }
154 :
155 348 : errorsFound |= this->processCurve(state,
156 58 : input_data.part_load_fraction_correlation_curve_name,
157 58 : this->indexPLRFPLF,
158 : {1},
159 : routineName,
160 : "Part Load Fraction Correlation Curve Name",
161 : 1.0);
162 :
163 58 : if (this->indexPLRFPLF > 0 && !errorsFound) {
164 : // Test PLF curve minimum and maximum. Cap if less than 0.7 or greater than 1.0.
165 50 : Real64 MinCurveVal = 999.0;
166 50 : Real64 MaxCurveVal = -999.0;
167 50 : Real64 CurveInput = 0.0;
168 50 : Real64 MinCurvePLR = 0.0, MaxCurvePLR = 0.0;
169 5050 : while (CurveInput <= 1.0) {
170 5000 : Real64 CurveVal = Curve::CurveValue(state, this->indexPLRFPLF, CurveInput);
171 5000 : if (CurveVal < MinCurveVal) {
172 50 : MinCurveVal = CurveVal;
173 50 : MinCurvePLR = CurveInput;
174 : }
175 5000 : if (CurveVal > MaxCurveVal) {
176 2905 : MaxCurveVal = CurveVal;
177 2905 : MaxCurvePLR = CurveInput;
178 : }
179 5000 : CurveInput += 0.01;
180 : }
181 50 : if (MinCurveVal < 0.7) {
182 0 : ShowWarningError(state, format("{}{}=\"{}\", invalid", routineName, this->object_name, this->name));
183 0 : ShowContinueError(state, format("...{}=\"{}\" has out of range value.", fieldName, input_data.part_load_fraction_correlation_curve_name));
184 0 : ShowContinueError(state, format("...Curve minimum must be >= 0.7, curve min at PLR = {:.2T} is {:.3T}", MinCurvePLR, MinCurveVal));
185 0 : ShowContinueError(state, "...Setting curve minimum to 0.7 and simulation continues.");
186 0 : Curve::SetCurveOutputMinValue(state, this->indexPLRFPLF, errorsFound, 0.7);
187 : }
188 :
189 50 : if (MaxCurveVal > 1.0) {
190 0 : ShowWarningError(state, format("{}{}=\"{}\", invalid", routineName, this->object_name, this->name));
191 0 : ShowContinueError(state, format("...{}=\"{}\" has out of range value.", fieldName, input_data.part_load_fraction_correlation_curve_name));
192 0 : ShowContinueError(state, format("...Curve maximum must be <= 1.0, curve max at PLR = {:.2T} is {:.3T}", MaxCurvePLR, MaxCurveVal));
193 0 : ShowContinueError(state, "...Setting curve maximum to 1.0 and simulation continues.");
194 0 : Curve::SetCurveOutputMaxValue(state, this->indexPLRFPLF, errorsFound, 1.0);
195 : }
196 : }
197 :
198 58 : if (errorsFound) {
199 0 : ShowFatalError(
200 0 : state, std::string{routineName} + "Errors found in getting " + this->object_name + " input. Preceding condition(s) causes termination.");
201 : }
202 58 : }
203 :
204 464 : bool CoilCoolingDXCurveFitSpeed::processCurve(EnergyPlus::EnergyPlusData &state,
205 : const std::string &curveName,
206 : int &curveIndex,
207 : std::vector<int> validDims,
208 : std::string_view const routineName,
209 : const std::string &fieldName,
210 : Real64 const Var1, // required 1st independent variable
211 : ObjexxFCL::Optional<Real64 const> Var2) // 2nd independent variable
212 : {
213 464 : if (curveName.empty()) {
214 154 : return false;
215 : } else {
216 310 : curveIndex = Curve::GetCurveIndex(state, curveName);
217 310 : if (curveIndex == 0) {
218 0 : ShowSevereError(state, std::string{routineName} + this->object_name + "=\"" + this->name + "\", invalid");
219 0 : ShowContinueError(state, "...not found " + fieldName + "=\"" + curveName + "\".");
220 0 : return true;
221 : } else {
222 : // Verify Curve Object dimensions
223 620 : bool errorFound = Curve::CheckCurveDims(state,
224 : curveIndex, // Curve index
225 310 : std::move(validDims), // Valid dimensions
226 : routineName, // Routine name
227 : this->object_name, // Object Type
228 : this->name, // Object Name
229 : fieldName); // Field Name
230 310 : if (!errorFound) {
231 310 : if (Var2.present()) {
232 286 : Curve::checkCurveIsNormalizedToOne(
233 572 : state, std::string{routineName} + this->object_name, this->name, curveIndex, fieldName, curveName, Var1, Var2);
234 : } else {
235 167 : Curve::checkCurveIsNormalizedToOne(
236 668 : state, std::string{routineName} + this->object_name, this->name, curveIndex, fieldName, curveName, Var1);
237 : }
238 : }
239 310 : return errorFound;
240 : }
241 : }
242 : }
243 :
244 58 : CoilCoolingDXCurveFitSpeed::CoilCoolingDXCurveFitSpeed(EnergyPlus::EnergyPlusData &state,
245 58 : const std::string &name_to_find)
246 : : // model inputs
247 58 : indexCapFT(0), indexCapFFF(0), indexEIRFT(0), indexEIRFFF(0), indexPLRFPLF(0), indexWHFT(0), indexSHRFT(0), indexSHRFFF(0),
248 :
249 : // speed class inputs
250 58 : RatedAirMassFlowRate(0.0), // rated air mass flow rate at speed {kg/s}
251 58 : RatedCondAirMassFlowRate(0.0), // condenser air mass flow rate at speed {kg/s}
252 58 : grossRatedSHR(0.0), // rated sensible heat ratio at speed
253 58 : RatedCBF(0.0), // rated coil bypass factor at speed
254 58 : RatedEIR(0.0), // rated energy input ratio at speed {W/W}
255 58 : ratedCOP(0.0), rated_total_capacity(0.0), rated_evap_fan_power_per_volume_flow_rate(0.0),
256 58 : ratedWasteHeatFractionOfPowerInput(0.0), // rated waste heat fraction of power input
257 58 : evap_condenser_pump_power_fraction(0.0), evap_condenser_effectiveness(0.0),
258 :
259 58 : parentModeRatedGrossTotalCap(0.0), parentModeRatedEvapAirFlowRate(0.0), parentModeRatedCondAirFlowRate(0.0), parentOperatingMode(0),
260 :
261 58 : ambPressure(0.0), // outdoor pressure {Pa}
262 58 : PLR(0.0), // coil operating part load ratio
263 58 : AirFF(0.0), // ratio of air mass flow rate to rated air mass flow rate
264 : // RatedTotCap( 0.0 ), // rated total capacity at speed {W}
265 :
266 58 : fullLoadPower(0.0), // full load power at speed {W}
267 58 : fullLoadWasteHeat(0.0), // full load waste heat at speed {W}
268 58 : RTF(0.0), // coil runtime fraction at speed
269 58 : AirMassFlow(0.0), // coil inlet air mass flow rate {kg/s}
270 :
271 : // other data members
272 58 : evap_air_flow_rate(0.0), condenser_air_flow_rate(0.0), active_fraction_of_face_coil_area(0.0),
273 58 : ratedLatentCapacity(0.0), // Latent capacity at rated conditions {W}
274 :
275 : // rating data
276 58 : RatedInletAirTemp(26.6667), // 26.6667C or 80F
277 58 : RatedInletWetBulbTemp(19.4444), // 19.44 or 67F
278 58 : RatedInletAirHumRat(0.0111847), // Humidity ratio corresponding to 80F dry bulb/67F wet bulb
279 58 : RatedOutdoorAirTemp(35.0), // 35 C or 95F
280 174 : DryCoilOutletHumRatioMin(0.00001) // dry coil outlet minimum hum ratio kgH2O/kgdry air
281 :
282 : {
283 58 : int numSpeeds = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CoilCoolingDXCurveFitSpeed::object_name);
284 : if (numSpeeds <= 0) {
285 : // error
286 : }
287 58 : bool found_it = false;
288 121 : for (int speedNum = 1; speedNum <= numSpeeds; ++speedNum) {
289 : int NumAlphas; // Number of Alphas for each GetObjectItem call
290 : int NumNumbers; // Number of Numbers for each GetObjectItem call
291 : int IOStatus;
292 242 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
293 : CoilCoolingDXCurveFitSpeed::object_name,
294 : speedNum,
295 121 : state.dataIPShortCut->cAlphaArgs,
296 : NumAlphas,
297 121 : state.dataIPShortCut->rNumericArgs,
298 : NumNumbers,
299 : IOStatus);
300 121 : if (!Util::SameString(name_to_find, state.dataIPShortCut->cAlphaArgs(1))) {
301 63 : continue;
302 : }
303 58 : found_it = true;
304 :
305 58 : CoilCoolingDXCurveFitSpeedInputSpecification input_specs;
306 :
307 58 : input_specs.name = state.dataIPShortCut->cAlphaArgs(1);
308 58 : input_specs.gross_rated_total_cooling_capacity_ratio_to_nominal = state.dataIPShortCut->rNumericArgs(1);
309 58 : input_specs.evaporator_air_flow_fraction = state.dataIPShortCut->rNumericArgs(2);
310 58 : input_specs.condenser_air_flow_fraction = state.dataIPShortCut->rNumericArgs(3);
311 58 : input_specs.gross_rated_sensible_heat_ratio = state.dataIPShortCut->rNumericArgs(4);
312 58 : input_specs.gross_rated_cooling_COP = state.dataIPShortCut->rNumericArgs(5);
313 58 : input_specs.active_fraction_of_coil_face_area = state.dataIPShortCut->rNumericArgs(6);
314 58 : input_specs.rated_evaporator_fan_power_per_volume_flow_rate = state.dataIPShortCut->rNumericArgs(7);
315 58 : input_specs.rated_evaporator_fan_power_per_volume_flow_rate_2023 = state.dataIPShortCut->rNumericArgs(8);
316 58 : input_specs.rated_evaporative_condenser_pump_power_fraction = state.dataIPShortCut->rNumericArgs(9);
317 58 : input_specs.evaporative_condenser_effectiveness = state.dataIPShortCut->rNumericArgs(10);
318 58 : input_specs.total_cooling_capacity_function_of_temperature_curve_name = state.dataIPShortCut->cAlphaArgs(2);
319 58 : input_specs.total_cooling_capacity_function_of_air_flow_fraction_curve_name = state.dataIPShortCut->cAlphaArgs(3);
320 58 : input_specs.energy_input_ratio_function_of_temperature_curve_name = state.dataIPShortCut->cAlphaArgs(4);
321 58 : input_specs.energy_input_ratio_function_of_air_flow_fraction_curve_name = state.dataIPShortCut->cAlphaArgs(5);
322 58 : input_specs.part_load_fraction_correlation_curve_name = state.dataIPShortCut->cAlphaArgs(6);
323 58 : input_specs.rated_waste_heat_fraction_of_power_input = state.dataIPShortCut->rNumericArgs(11);
324 58 : input_specs.waste_heat_function_of_temperature_curve_name = state.dataIPShortCut->cAlphaArgs(7);
325 58 : input_specs.sensible_heat_ratio_modifier_function_of_temperature_curve_name = state.dataIPShortCut->cAlphaArgs(8);
326 58 : input_specs.sensible_heat_ratio_modifier_function_of_flow_fraction_curve_name = state.dataIPShortCut->cAlphaArgs(9);
327 :
328 58 : this->instantiateFromInputSpec(state, input_specs);
329 58 : break;
330 58 : }
331 :
332 58 : if (!found_it) {
333 0 : ShowFatalError(state, "Could not find Coil:Cooling:DX:CurveFit:Speed object with name: " + name_to_find);
334 : }
335 58 : }
336 :
337 44 : void CoilCoolingDXCurveFitSpeed::size(EnergyPlus::EnergyPlusData &state)
338 : {
339 :
340 : static constexpr std::string_view RoutineName = "sizeSpeed";
341 :
342 44 : this->rated_total_capacity = this->original_input_specs.gross_rated_total_cooling_capacity_ratio_to_nominal * this->parentModeRatedGrossTotalCap;
343 44 : this->evap_air_flow_rate = this->original_input_specs.evaporator_air_flow_fraction * this->parentModeRatedEvapAirFlowRate;
344 44 : this->condenser_air_flow_rate = this->original_input_specs.condenser_air_flow_fraction * this->parentModeRatedCondAirFlowRate;
345 44 : this->grossRatedSHR = this->original_input_specs.gross_rated_sensible_heat_ratio;
346 :
347 44 : this->RatedAirMassFlowRate =
348 88 : this->evap_air_flow_rate *
349 44 : Psychrometrics::PsyRhoAirFnPbTdbW(state, state.dataEnvrn->StdBaroPress, RatedInletAirTemp, RatedInletAirHumRat, RoutineName);
350 44 : this->RatedCondAirMassFlowRate =
351 88 : this->condenser_air_flow_rate *
352 44 : Psychrometrics::PsyRhoAirFnPbTdbW(state, state.dataEnvrn->StdBaroPress, RatedInletAirTemp, RatedInletAirHumRat, RoutineName);
353 :
354 44 : bool PrintFlag = true;
355 44 : bool errorsFound = false;
356 44 : std::string CompType = this->object_name;
357 44 : std::string CompName = this->name;
358 :
359 44 : CoolingAirFlowSizer sizingCoolingAirFlow;
360 44 : std::string stringOverride = "Rated Air Flow Rate [m3/s]";
361 44 : if (state.dataGlobal->isEpJSON) stringOverride = "rated_air_flow_rate [m3/s]";
362 44 : std::string preFixString;
363 : // if (maxSpeeds > 1) preFixString = "Speed " + std::to_string(speedNum + 1) + " ";
364 : // stringOverride = preFixString + stringOverride;
365 44 : sizingCoolingAirFlow.overrideSizingString(stringOverride);
366 44 : if (this->original_input_specs.evaporator_air_flow_fraction < 1.0) {
367 24 : state.dataSize->DataDXCoolsLowSpeedsAutozize = true;
368 24 : state.dataSize->DataFractionUsedForSizing = this->original_input_specs.evaporator_air_flow_fraction;
369 : }
370 44 : sizingCoolingAirFlow.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
371 44 : this->evap_air_flow_rate = sizingCoolingAirFlow.size(state, this->evap_air_flow_rate, errorsFound);
372 :
373 44 : std::string SizingString = preFixString + "Gross Cooling Capacity [W]";
374 44 : CoolingCapacitySizer sizerCoolingCapacity;
375 44 : sizerCoolingCapacity.overrideSizingString(SizingString);
376 44 : if (this->original_input_specs.gross_rated_total_cooling_capacity_ratio_to_nominal < 1.0) {
377 26 : state.dataSize->DataDXCoolsLowSpeedsAutozize = true;
378 26 : state.dataSize->DataConstantUsedForSizing = -999.0;
379 26 : state.dataSize->DataFlowUsedForSizing = this->parentModeRatedEvapAirFlowRate;
380 26 : state.dataSize->DataFractionUsedForSizing = this->original_input_specs.gross_rated_total_cooling_capacity_ratio_to_nominal;
381 : }
382 44 : sizerCoolingCapacity.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
383 44 : this->rated_total_capacity = sizerCoolingCapacity.size(state, this->rated_total_capacity, errorsFound);
384 :
385 : // DataSizing::DataEMSOverrideON = DXCoil( DXCoilNum ).RatedSHREMSOverrideOn( Mode );
386 : // DataSizing::DataEMSOverride = DXCoil( DXCoilNum ).RatedSHREMSOverrideValue( Mode );
387 44 : state.dataSize->DataFlowUsedForSizing = this->evap_air_flow_rate;
388 44 : state.dataSize->DataCapacityUsedForSizing = this->rated_total_capacity;
389 44 : bool errorFound = false;
390 44 : CoolingSHRSizer sizerCoolingSHR;
391 44 : sizerCoolingSHR.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
392 44 : if (this->grossRatedSHR == DataSizing::AutoSize && this->parentOperatingMode == 2) {
393 0 : state.dataSize->DataSizingFraction = 0.667;
394 0 : this->grossRatedSHR = sizerCoolingSHR.size(state, this->grossRatedSHR, errorFound);
395 44 : } else if (this->grossRatedSHR == DataSizing::AutoSize && this->parentOperatingMode == 3) {
396 0 : state.dataSize->DataSizingFraction = 0.333;
397 0 : this->grossRatedSHR = sizerCoolingSHR.size(state, this->grossRatedSHR, errorFound);
398 : } else {
399 44 : this->grossRatedSHR = sizerCoolingSHR.size(state, this->grossRatedSHR, errorFound);
400 : }
401 44 : state.dataSize->DataFlowUsedForSizing = 0.0;
402 44 : state.dataSize->DataCapacityUsedForSizing = 0.0;
403 44 : state.dataSize->DataTotCapCurveIndex = 0;
404 44 : state.dataSize->DataSizingFraction = 1.0;
405 : // DataSizing::DataEMSOverrideON = false;
406 : // DataSizing::DataEMSOverride = 0.0;
407 :
408 44 : if (this->indexSHRFT > 0 && this->indexSHRFFF > 0) {
409 4 : this->RatedCBF = 0.001;
410 : } else {
411 :
412 40 : this->RatedCBF = CalcBypassFactor(state,
413 : RatedInletAirTemp,
414 : RatedInletAirHumRat,
415 : this->rated_total_capacity,
416 : this->grossRatedSHR,
417 : Psychrometrics::PsyHFnTdbW(RatedInletAirTemp, RatedInletAirHumRat),
418 : DataEnvironment::StdPressureSeaLevel);
419 : }
420 44 : this->RatedEIR = 1.0 / this->original_input_specs.gross_rated_cooling_COP;
421 44 : this->ratedLatentCapacity = this->rated_total_capacity * (1.0 - this->grossRatedSHR);
422 :
423 : // reset for next speed or coil
424 44 : state.dataSize->DataConstantUsedForSizing = 0.0;
425 44 : state.dataSize->DataDXCoolsLowSpeedsAutozize = false;
426 44 : }
427 :
428 170 : void CoilCoolingDXCurveFitSpeed::CalcSpeedOutput(EnergyPlus::EnergyPlusData &state,
429 : const DataLoopNode::NodeData &inletNode,
430 : DataLoopNode::NodeData &outletNode,
431 : Real64 const PLR,
432 : HVAC::FanOp const fanOp,
433 : const Real64 condInletTemp)
434 : {
435 :
436 : // SUBROUTINE PARAMETER DEFINITIONS:
437 : static constexpr std::string_view RoutineName("CalcSpeedOutput: ");
438 :
439 170 : if ((PLR == 0.0) || (AirMassFlow == 0.0)) {
440 2 : outletNode.Temp = inletNode.Temp;
441 2 : outletNode.HumRat = inletNode.HumRat;
442 2 : outletNode.Enthalpy = inletNode.Enthalpy;
443 2 : outletNode.Press = inletNode.Press;
444 2 : fullLoadPower = 0.0;
445 2 : fullLoadWasteHeat = 0.0;
446 2 : RTF = 0.0;
447 2 : return;
448 : }
449 :
450 : Real64 hDelta; // enthalpy difference across cooling coil
451 : Real64 A0; // ratio of UA to Cp
452 : Real64 CBF; // adjusted coil bypass factor
453 168 : if (RatedCBF > 0.0) {
454 168 : A0 = -std::log(RatedCBF) * RatedAirMassFlowRate;
455 : } else {
456 : // This is bad - results in CBF = 1.0 which results in divide by zero below: hADP = inletState.h - hDelta / (1.0 - CBF)
457 0 : ShowFatalError(state, format("{}Rated CBF={:.6R} is <= 0.0 for {}={}", RoutineName, RatedCBF, object_name, name));
458 0 : A0 = 0.0;
459 : }
460 168 : Real64 ADiff = -A0 / AirMassFlow;
461 168 : if (ADiff >= DataPrecisionGlobals::EXP_LowerLimit) {
462 168 : CBF = std::exp(ADiff);
463 : } else {
464 0 : CBF = 0.0;
465 : }
466 :
467 168 : assert(ambPressure > 0.0);
468 168 : Real64 inletWetBulb = Psychrometrics::PsyTwbFnTdbWPb(state, inletNode.Temp, inletNode.HumRat, ambPressure);
469 168 : Real64 inletw = inletNode.HumRat;
470 :
471 168 : int Counter = 0; // iteration counter for dry coil condition
472 168 : int constexpr MaxIter(30); // iteration limit
473 168 : Real64 constexpr Tolerance(0.01); // iteration convergence limit
474 168 : Real64 RF = 0.4; // relaxation factor for holding back changes in value during iteration
475 : Real64 TotCap;
476 : Real64 SHR;
477 : while (true) {
478 :
479 168 : Real64 TotCapTempModFac = 1.0;
480 168 : if (indexCapFT > 0) {
481 158 : if (state.dataCurveManager->curves(indexCapFT)->numDims == 2) {
482 158 : TotCapTempModFac = Curve::CurveValue(state, indexCapFT, inletWetBulb, condInletTemp);
483 : } else {
484 0 : TotCapTempModFac = Curve::CurveValue(state, indexCapFT, condInletTemp);
485 : }
486 : }
487 168 : Real64 TotCapFlowModFac = 1.0;
488 168 : if (indexCapFFF > 0) {
489 158 : TotCapFlowModFac = Curve::CurveValue(state, indexCapFFF, AirFF);
490 : }
491 :
492 168 : TotCap = this->rated_total_capacity * TotCapFlowModFac * TotCapTempModFac;
493 168 : hDelta = TotCap / AirMassFlow;
494 :
495 168 : if (indexSHRFT > 0 && indexSHRFFF > 0) {
496 90 : Real64 SHRTempModFrac = max(Curve::CurveValue(state, indexSHRFT, inletWetBulb, inletNode.Temp), 0.0);
497 :
498 90 : Real64 SHRFlowModFrac = max(Curve::CurveValue(state, indexSHRFFF, AirFF), 0.0);
499 :
500 90 : SHR = this->grossRatedSHR * SHRTempModFrac * SHRFlowModFrac;
501 90 : SHR = max(min(SHR, 1.0), 0.0);
502 90 : break;
503 : } else {
504 : // Calculate apparatus dew point conditions using TotCap and CBF
505 78 : Real64 hADP = inletNode.Enthalpy - hDelta / (1.0 - CBF);
506 78 : Real64 tADP = Psychrometrics::PsyTsatFnHPb(state, hADP, ambPressure, RoutineName);
507 78 : Real64 wADP = Psychrometrics::PsyWFnTdbH(state, tADP, hADP, RoutineName);
508 78 : Real64 hTinwADP = Psychrometrics::PsyHFnTdbW(inletNode.Temp, wADP);
509 78 : if ((inletNode.Enthalpy - hADP) > 1.e-10) {
510 78 : SHR = min((hTinwADP - hADP) / (inletNode.Enthalpy - hADP), 1.0);
511 : } else {
512 0 : SHR = 1.0;
513 : }
514 : // Check for dry evaporator conditions (win < wadp)
515 78 : if (wADP > inletw || (Counter >= 1 && Counter < MaxIter)) {
516 0 : if (inletw == 0.0) inletw = 0.00001;
517 0 : Real64 werror = (inletw - wADP) / inletw;
518 : // Increase InletAirHumRatTemp at constant InletAirTemp to find coil dry-out point. Then use the
519 : // capacity at the dry-out point to determine exiting conditions from coil. This is required
520 : // since the TotCapTempModFac doesn't work properly with dry-coil conditions.
521 0 : inletw = RF * wADP + (1.0 - RF) * inletw;
522 0 : inletWetBulb = Psychrometrics::PsyTwbFnTdbWPb(state, inletNode.Temp, inletw, ambPressure);
523 0 : ++Counter;
524 0 : if (std::abs(werror) > Tolerance) continue; // Recalculate with modified inlet conditions
525 0 : break;
526 : } else {
527 : break;
528 : }
529 : }
530 0 : }
531 :
532 168 : assert(SHR >= 0.0);
533 :
534 168 : Real64 PLF = 1.0; // part load factor as a function of PLR, RTF = PLR / PLF
535 168 : if (indexPLRFPLF > 0) {
536 158 : PLF = Curve::CurveValue(state, indexPLRFPLF, PLR); // Calculate part-load factor
537 : }
538 168 : if (fanOp == HVAC::FanOp::Cycling) state.dataHVACGlobal->OnOffFanPartLoadFraction = PLF;
539 :
540 168 : Real64 EIRTempModFac = 1.0; // EIR as a function of temperature curve result
541 168 : if (indexEIRFT > 0) {
542 158 : if (state.dataCurveManager->curves(indexEIRFT)->numDims == 2) {
543 158 : EIRTempModFac = Curve::CurveValue(state, indexEIRFT, inletWetBulb, condInletTemp);
544 : } else {
545 0 : EIRTempModFac = Curve::CurveValue(state, indexEIRFT, condInletTemp);
546 : }
547 : }
548 168 : Real64 EIRFlowModFac = 1.0; // EIR as a function of flow fraction curve result
549 168 : if (indexEIRFFF > 0) {
550 158 : EIRFlowModFac = Curve::CurveValue(state, indexEIRFFF, AirFF);
551 : }
552 :
553 168 : Real64 wasteHeatTempModFac = 1.0; // waste heat fraction as a function of temperature curve result
554 168 : if (indexWHFT > 0) {
555 46 : wasteHeatTempModFac = Curve::CurveValue(state, indexWHFT, condInletTemp, inletNode.Temp);
556 : }
557 :
558 168 : Real64 EIR = RatedEIR * EIRFlowModFac * EIRTempModFac;
559 168 : RTF = PLR / PLF;
560 168 : fullLoadPower = TotCap * EIR;
561 168 : fullLoadWasteHeat = ratedWasteHeatFractionOfPowerInput * wasteHeatTempModFac * fullLoadPower;
562 :
563 168 : outletNode.Enthalpy = inletNode.Enthalpy - hDelta;
564 168 : Real64 hTinwout = inletNode.Enthalpy - ((1.0 - SHR) * hDelta);
565 168 : outletNode.HumRat = Psychrometrics::PsyWFnTdbH(state, inletNode.Temp, hTinwout);
566 168 : outletNode.Temp = Psychrometrics::PsyTdbFnHW(outletNode.Enthalpy, outletNode.HumRat);
567 :
568 : // If constant fan with cycling compressor, call function to determine "effective SHR"
569 : // which includes the part-load degradation on latent capacity
570 168 : if (this->doLatentDegradation && (fanOp == HVAC::FanOp::Continuous)) {
571 0 : Real64 QLatActual = TotCap * (1.0 - SHR);
572 : // TODO: Figure out HeatingRTF for this
573 0 : Real64 HeatingRTF = 0.0;
574 0 : SHR = calcEffectiveSHR(inletNode, inletWetBulb, SHR, RTF, ratedLatentCapacity, QLatActual, HeatingRTF);
575 : // Calculate full load output conditions
576 0 : if (SHR > 1.0) SHR = 1.0;
577 0 : hTinwout = inletNode.Enthalpy - (1.0 - SHR) * hDelta;
578 0 : if (SHR < 1.0) {
579 0 : outletNode.HumRat = Psychrometrics::PsyWFnTdbH(state, inletNode.Temp, hTinwout, RoutineName);
580 : } else {
581 0 : outletNode.HumRat = inletNode.HumRat;
582 : }
583 0 : outletNode.Temp = Psychrometrics::PsyTdbFnHW(outletNode.Enthalpy, outletNode.HumRat);
584 : }
585 : // Check for saturation error and modify temperature at constant enthalpy
586 168 : Real64 tsat = Psychrometrics::PsyTsatFnHPb(state, outletNode.Enthalpy, inletNode.Press, RoutineName);
587 168 : if (outletNode.Temp < tsat) {
588 66 : outletNode.Temp = tsat;
589 66 : outletNode.HumRat = Psychrometrics::PsyWFnTdbH(state, tsat, outletNode.Enthalpy);
590 : }
591 : }
592 :
593 40 : Real64 CoilCoolingDXCurveFitSpeed::CalcBypassFactor(EnergyPlus::EnergyPlusData &state,
594 : Real64 const tdb, // Inlet dry-bulb temperature {C}
595 : Real64 const w, // Inlet humidity ratio {kg-H2O/kg-dryair}
596 : Real64 const q, // Total capacity {W}
597 : Real64 const shr, // SHR
598 : Real64 const h, // Inlet enthalpy {J/kg-dryair}
599 : Real64 const p) // Outlet node pressure {Pa}
600 : {
601 :
602 : static constexpr std::string_view RoutineName("CalcBypassFactor: ");
603 40 : Real64 constexpr SmallDifferenceTest(0.00000001);
604 :
605 : // Bypass factors are calculated at rated conditions at sea level (make sure in.p is Standard Pressure)
606 : Real64 calcCBF;
607 :
608 40 : Real64 airMassFlowRate = this->evap_air_flow_rate * Psychrometrics::PsyRhoAirFnPbTdbW(state, p, tdb, w);
609 40 : Real64 deltaH = q / airMassFlowRate;
610 40 : Real64 outp = p;
611 40 : Real64 outh = h - deltaH;
612 40 : Real64 outw = Psychrometrics::PsyWFnTdbH(state, tdb, h - (1.0 - shr) * deltaH); // enthalpy at Tdb,in and Wout
613 40 : Real64 outtdb = Psychrometrics::PsyTdbFnHW(outh, outw);
614 40 : Real64 outrh = Psychrometrics::PsyRhFnTdbWPb(state, outtdb, outw, outp);
615 :
616 40 : if (outrh >= 1.0) {
617 4 : ShowWarningError(state, std::string{RoutineName} + ": For object = " + this->object_name + ", name = \"" + this->name + "\"");
618 4 : ShowContinueError(state, "Calculated outlet air relative humidity greater than 1. The combination of");
619 4 : ShowContinueError(state, "rated air volume flow rate, total cooling capacity and sensible heat ratio yields coil exiting");
620 4 : ShowContinueError(state, "air conditions above the saturation curve. Possible fixes are to reduce the rated total cooling");
621 4 : ShowContinueError(state, "capacity, increase the rated air volume flow rate, or reduce the rated sensible heat ratio for this coil.");
622 4 : ShowContinueError(state, "If autosizing, it is recommended that all three of these values be autosized.");
623 4 : ShowContinueError(state, "...Inputs used for calculating cooling coil bypass factor.");
624 2 : ShowContinueError(state, format("...Inlet Air Temperature = {:.2R} C", tdb));
625 2 : ShowContinueError(state, format("...Outlet Air Temperature = {:.2R} C", outtdb));
626 2 : ShowContinueError(state, format("...Inlet Air Humidity Ratio = {:.6R} kgWater/kgDryAir", w));
627 2 : ShowContinueError(state, format("...Outlet Air Humidity Ratio = {:.6R} kgWater/kgDryAir", outw));
628 2 : ShowContinueError(state, format("...Total Cooling Capacity used in calculation = {:.2R} W", q));
629 2 : ShowContinueError(state, format("...Air Mass Flow Rate used in calculation = {:.6R} kg/s", airMassFlowRate));
630 2 : ShowContinueError(state, format("...Air Volume Flow Rate used in calculation = {:.6R} m3/s", this->evap_air_flow_rate));
631 2 : if (q > 0.0) {
632 2 : if (((this->minRatedVolFlowPerRatedTotCap - this->evap_air_flow_rate / q) > SmallDifferenceTest) ||
633 0 : ((this->evap_air_flow_rate / q - this->maxRatedVolFlowPerRatedTotCap) > SmallDifferenceTest)) {
634 4 : ShowContinueError(state,
635 4 : format("...Air Volume Flow Rate per Watt of Rated Cooling Capacity is also out of bounds at = {:.7R} m3/s/W",
636 4 : this->evap_air_flow_rate / q));
637 : }
638 : }
639 2 : Real64 outletAirTempSat = Psychrometrics::PsyTsatFnHPb(state, outh, outp, RoutineName);
640 2 : if (outtdb < outletAirTempSat) { // Limit to saturated conditions at OutletAirEnthalpy
641 2 : outtdb = outletAirTempSat + 0.005;
642 2 : outw = Psychrometrics::PsyWFnTdbH(state, outtdb, outh, RoutineName);
643 2 : Real64 adjustedSHR = (Psychrometrics::PsyHFnTdbW(tdb, outw) - outh) / deltaH;
644 4 : ShowWarningError(state,
645 6 : std::string{RoutineName} + object_name + " \"" + name +
646 : "\", SHR adjusted to achieve valid outlet air properties and the simulation continues.");
647 2 : ShowContinueError(state, format("Initial SHR = {:.5R}", this->grossRatedSHR));
648 2 : ShowContinueError(state, format("Adjusted SHR = {:.5R}", adjustedSHR));
649 : }
650 : }
651 :
652 : // ADP conditions
653 40 : Real64 adp_tdb = Psychrometrics::PsyTdpFnWPb(state, outw, outp);
654 :
655 40 : Real64 deltaT = tdb - outtdb;
656 40 : Real64 deltaHumRat = w - outw;
657 40 : Real64 slopeAtConds = 0.0;
658 40 : if (deltaT > 0.0) slopeAtConds = deltaHumRat / deltaT;
659 40 : if (slopeAtConds <= 0.0) {
660 0 : ShowSevereError(state, this->object_name + " \"" + this->name + "\"");
661 0 : ShowContinueError(state, "...Invalid slope or outlet air condition when calculating cooling coil bypass factor.");
662 0 : ShowContinueError(state, format("...Slope = {:.8R}", slopeAtConds));
663 0 : ShowContinueError(state, format("...Inlet Air Temperature = {:.2R} C", tdb));
664 0 : ShowContinueError(state, format("...Outlet Air Temperature = {:.2R} C", outtdb));
665 0 : ShowContinueError(state, format("...Inlet Air Humidity Ratio = {:.6R} kgWater/kgDryAir", w));
666 0 : ShowContinueError(state, format("...Outlet Air Humidity Ratio = {:.6R} kgWater/kgDryAir", outw));
667 0 : ShowContinueError(state, format("...Total Cooling Capacity used in calculation = {:.2R} W", q));
668 0 : ShowContinueError(state, format("...Air Mass Flow Rate used in calculation = {:.6R} kg/s", airMassFlowRate));
669 0 : ShowContinueError(state, format("...Air Volume Flow Rate used in calculation = {:.6R} m3/s", this->evap_air_flow_rate));
670 0 : if (q > 0.0) {
671 0 : if (((this->minRatedVolFlowPerRatedTotCap - this->evap_air_flow_rate / q) > SmallDifferenceTest) ||
672 0 : ((this->evap_air_flow_rate / q - this->maxRatedVolFlowPerRatedTotCap) > SmallDifferenceTest)) {
673 0 : ShowContinueError(state,
674 0 : format("...Air Volume Flow Rate per Watt of Rated Cooling Capacity is also out of bounds at = {:.7R} m3/s/W",
675 0 : this->evap_air_flow_rate / q));
676 : }
677 : }
678 0 : ShowFatalError(state, "Errors found in calculating coil bypass factors");
679 : }
680 :
681 40 : Real64 adp_w = min(outw, Psychrometrics::PsyWFnTdpPb(state, adp_tdb, DataEnvironment::StdPressureSeaLevel));
682 :
683 40 : int iter = 0;
684 40 : int constexpr maxIter(50);
685 40 : Real64 errorLast = 100.0;
686 40 : Real64 deltaADPTemp = 5.0;
687 40 : Real64 tolerance = 1.0; // initial conditions for iteration
688 40 : bool cbfErrors = false;
689 398 : while ((iter <= maxIter) && (tolerance > 0.001)) {
690 :
691 : // Do for IterMax iterations or until the error gets below .1%
692 358 : if (iter > 0) adp_tdb += deltaADPTemp;
693 358 : ++iter;
694 : // Find new slope using guessed Tadp
695 358 : adp_w = min(outw, Psychrometrics::PsyWFnTdpPb(state, adp_tdb, DataEnvironment::StdPressureSeaLevel));
696 358 : Real64 slope = (w - adp_w) / max(0.001, (tdb - adp_tdb));
697 : // check for convergence (slopes are equal to within error tolerance)
698 358 : Real64 error = (slope - slopeAtConds) / slopeAtConds;
699 358 : if ((error > 0.0) && (errorLast < 0.0)) {
700 112 : deltaADPTemp = -deltaADPTemp / 2.0;
701 246 : } else if ((error < 0.0) && (errorLast > 0.0)) {
702 136 : deltaADPTemp = -deltaADPTemp / 2.0;
703 110 : } else if (std::abs(error) > std::abs(errorLast)) {
704 0 : deltaADPTemp = -deltaADPTemp / 2.0;
705 : }
706 358 : errorLast = error;
707 358 : tolerance = std::abs(error);
708 : }
709 :
710 : // Calculate Bypass Factor from Enthalpies
711 40 : Real64 adp_h = Psychrometrics::PsyHFnTdbW(adp_tdb, adp_w);
712 40 : calcCBF = min(1.0, (outh - adp_h) / (h - adp_h));
713 :
714 40 : if (iter > maxIter) {
715 0 : ShowSevereError(state,
716 0 : std::string{RoutineName} + object_name + " \"" + name +
717 : "\" -- coil bypass factor calculation did not converge after max iterations.");
718 0 : ShowContinueError(state, format("The RatedSHR of [{:.3R}], entered by the user or autosized (see *.eio file),", this->grossRatedSHR));
719 0 : ShowContinueError(state, "may be causing this. The line defined by the coil rated inlet air conditions");
720 0 : ShowContinueError(state, "(26.7C drybulb and 19.4C wetbulb) and the RatedSHR (i.e., slope of the line) must intersect");
721 0 : ShowContinueError(state, "the saturation curve of the psychrometric chart. If the RatedSHR is too low, then this");
722 0 : ShowContinueError(state, "intersection may not occur and the coil bypass factor calculation will not converge.");
723 0 : ShowContinueError(state, "If autosizing the SHR, recheck the design supply air humidity ratio and design supply air");
724 0 : ShowContinueError(state, "temperature values in the Sizing:System and Sizing:Zone objects. In general, the temperatures");
725 0 : ShowContinueError(state, "and humidity ratios specified in these two objects should be the same for each system");
726 0 : ShowContinueError(state, "and the zones that it serves.");
727 0 : ShowContinueErrorTimeStamp(state, "");
728 0 : cbfErrors = true; // Didn't converge within MaxIter iterations
729 : }
730 40 : if (calcCBF < 0.0) {
731 0 : ShowSevereError(state, std::string{RoutineName} + object_name + " \"" + name + "\" -- negative coil bypass factor calculated.");
732 0 : ShowContinueErrorTimeStamp(state, "");
733 0 : cbfErrors = true; // Negative CBF not valid
734 : }
735 : // Show fatal error for specific coil that caused a CBF error
736 40 : if (cbfErrors) {
737 0 : ShowFatalError(state, std::string{RoutineName} + object_name + " \"" + name + "\" Errors found in calculating coil bypass factors");
738 : }
739 40 : return calcCBF;
740 : }
741 :
742 0 : Real64 CoilCoolingDXCurveFitSpeed::calcEffectiveSHR(const DataLoopNode::NodeData &inletNode,
743 : Real64 const inletWetBulb,
744 : Real64 const SHRss, // Steady-state sensible heat ratio
745 : Real64 const RTF, // Compressor run-time fraction
746 : Real64 const QLatRated, // Rated latent capacity
747 : Real64 const QLatActual, // Actual latent capacity
748 : Real64 const HeatingRTF // Used to recalculate Toff for cycling fan systems
749 : )
750 : {
751 : // PURPOSE OF THIS FUNCTION:
752 : // Adjust sensible heat ratio to account for degradation of DX coil latent
753 : // capacity at part-load (cycling) conditions.
754 :
755 : // METHODOLOGY EMPLOYED:
756 : // With model parameters entered by the user, the part-load latent performance
757 : // of a DX cooling coil is determined for a constant air flow system with
758 : // a cooling coil that cycles on/off. The model calculates the time
759 : // required for condensate to begin falling from the cooling coil.
760 : // Runtimes greater than this are integrated to a "part-load" latent
761 : // capacity which is used to determine the "part-load" sensible heat ratio.
762 : // See reference below for additional details (linear decay model, Eq. 8b).
763 : // REFERENCES:
764 : // "A Model to Predict the Latent Capacity of Air Conditioners and
765 : // Heat Pumps at Part-Load Conditions with Constant Fan Operation"
766 : // 1996 ASHRAE Transactions, Volume 102, Part 1, Pp. 266 - 274,
767 : // Hugh I. Henderson, Jr., P.E., Kannan Rengarajan, P.E.
768 :
769 : // Return value
770 : Real64 SHReff; // Effective sensible heat ratio, includes degradation due to cycling effects
771 :
772 : // FUNCTION LOCAL VARIABLE DECLARATIONS:
773 : Real64 Twet; // Nominal time for condensate to begin leaving the coil's condensate drain line
774 : // at the current operating conditions (sec)
775 : Real64 Gamma; // Initial moisture evaporation rate divided by steady-state AC latent capacity
776 : // at the current operating conditions
777 : Real64 Twet_max; // Maximum allowed value for Twet
778 : Real64 Ton; // Coil on time (sec)
779 : Real64 Toff; // Coil off time (sec)
780 : Real64 Toffa; // Actual coil off time (sec). Equations valid for Toff <= (2.0 * Twet/Gamma)
781 : Real64 aa; // Intermediate variable
782 : Real64 To1; // Intermediate variable (first guess at To). To = time to the start of moisture removal
783 : Real64 To2; // Intermediate variable (second guess at To). To = time to the start of moisture removal
784 : Real64 Error; // Error for iteration (DO) loop
785 : Real64 LHRmult; // Latent Heat Ratio (LHR) multiplier. The effective latent heat ratio LHR = (1-SHRss)*LHRmult
786 : Real64 Ton_heating;
787 : Real64 Toff_heating;
788 :
789 0 : Real64 Twet_Rated = parentModeTimeForCondensateRemoval; // Time wet at rated conditions (sec)
790 0 : Real64 Gamma_Rated = parentModeEvapRateRatio; // Gamma at rated conditions
791 0 : Real64 Nmax = parentModeMaxCyclingRate; // Maximum ON/OFF cycles for the compressor (cycles/hr)
792 0 : Real64 Tcl = parentModeLatentTimeConst; // Time constant for latent capacity to reach steady state after startup(sec)
793 :
794 : // No moisture evaporation (latent degradation) occurs for runtime fraction of 1.0
795 : // All latent degradation model parameters cause divide by 0.0 if not greater than 0.0
796 : // Latent degradation model parameters initialize to 0.0 meaning no evaporation model used.
797 0 : if (RTF >= 1.0) {
798 0 : SHReff = SHRss;
799 0 : return SHReff;
800 : }
801 :
802 0 : Twet_max = 9999.0; // high limit for Twet
803 :
804 : // Calculate the model parameters at the actual operating conditions
805 0 : Twet = min(Twet_Rated * QLatRated / (QLatActual + 1.e-10), Twet_max);
806 0 : Gamma = Gamma_Rated * QLatRated * (inletNode.Temp - inletWetBulb) / ((26.7 - 19.4) * QLatActual + 1.e-10);
807 :
808 : // Calculate the compressor on and off times using a converntional thermostat curve
809 0 : Ton = 3600.0 / (4.0 * Nmax * (1.0 - RTF)); // duration of cooling coil on-cycle (sec)
810 0 : Toff = 3600.0 / (4.0 * Nmax * RTF); // duration of cooling coil off-cycle (sec)
811 :
812 : // Cap Toff to meet the equation restriction
813 0 : if (Gamma > 0.0) {
814 0 : Toffa = min(Toff, 2.0 * Twet / Gamma);
815 : } else {
816 0 : Toffa = Toff;
817 : }
818 :
819 : // Need to include the reheat coil operation to account for actual fan run time. E+ uses a
820 : // separate heating coil for heating and reheat (to separate the heating and reheat loads)
821 : // and real world applications would use a single heating coil for both purposes, the actual
822 : // fan operation is based on HeatingPLR + ReheatPLR. For cycling fan RH control, latent
823 : // degradation only occurs when a heating load exists, in this case the reheat load is
824 : // equal to and oposite in magnitude to the cooling coil sensible output but the reheat
825 : // coil is not always active. This additional fan run time has not been accounted for at this time.
826 : // Recalculate Toff for cycling fan systems when heating is active
827 0 : if (HeatingRTF > 0.0) {
828 0 : if (HeatingRTF < 1.0 && HeatingRTF > RTF) {
829 0 : Ton_heating = 3600.0 / (4.0 * Nmax * (1.0 - HeatingRTF));
830 0 : Toff_heating = 3600.0 / (4.0 * Nmax * HeatingRTF);
831 : // add additional heating coil operation during cooling coil off cycle (due to cycling rate difference of coils)
832 0 : Ton_heating += max(0.0, min(Ton_heating, (Ton + Toffa) - (Ton_heating + Toff_heating)));
833 0 : Toffa = min(Toffa, Ton_heating - Ton);
834 : }
835 : }
836 :
837 : // Use sucessive substitution to solve for To
838 0 : aa = (Gamma * Toffa) - (0.25 / Twet) * pow_2(Gamma) * pow_2(Toffa);
839 0 : To1 = aa + Tcl;
840 0 : Error = 1.0;
841 0 : while (Error > 0.001) {
842 0 : To2 = aa - Tcl * std::expm1(-To1 / Tcl);
843 0 : Error = std::abs((To2 - To1) / To1);
844 0 : To1 = To2;
845 : }
846 :
847 : // Adjust Sensible Heat Ratio (SHR) using Latent Heat Ratio (LHR) multiplier
848 : // Floating underflow errors occur when -Ton/Tcl is a large negative number.
849 : // Cap lower limit at -700 to avoid the underflow errors.
850 0 : aa = std::exp(max(-700.0, -Ton / Tcl));
851 : // Calculate latent heat ratio multiplier
852 0 : LHRmult = max(((Ton - To2) / (Ton + Tcl * (aa - 1.0))), 0.0);
853 :
854 : // Calculate part-load or "effective" sensible heat ratio
855 0 : SHReff = 1.0 - (1.0 - SHRss) * LHRmult;
856 :
857 0 : if (SHReff < SHRss) SHReff = SHRss; // Effective SHR can be less than the steady-state SHR
858 0 : if (SHReff > 1.0) SHReff = 1.0; // Effective sensible heat ratio can't be greater than 1.0
859 :
860 0 : return SHReff;
861 : }
|