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