diff --git a/examples/generate-205.py b/examples/generate-205.py index c45740c..921b9bc 100644 --- a/examples/generate-205.py +++ b/examples/generate-205.py @@ -5,36 +5,13 @@ # import cbor2 import json +import resdx.rating_solver + seer2 = 14.3 hspf2 = 7.5 -cop_c, solution_c = optimize.newton( - lambda x: resdx.DXUnit( - staging_type=resdx.StagingType.TWO_STAGE, - rated_gross_cooling_cop=x, - input_seer=seer2, - rating_standard=resdx.AHRIVersion.AHRI_210_240_2023, - ).seer() - - seer2, - seer2 / 3.33, - full_output=True, -) -cop_h, solution_h = optimize.newton( - lambda x: resdx.DXUnit( - staging_type=resdx.StagingType.TWO_STAGE, - rated_gross_heating_cop=x, - input_hspf=hspf2, - rating_standard=resdx.AHRIVersion.AHRI_210_240_2023, - ).hspf() - - hspf2, - hspf2 / 2.0, - full_output=True, -) -dx_unit = resdx.DXUnit( - staging_type=resdx.StagingType.TWO_STAGE, - rated_gross_cooling_cop=cop_c, - rated_gross_heating_cop=cop_h, - input_seer=seer2, - input_hspf=hspf2, + +dx_unit = resdx.rating_solver.make_rating_unit( + staging_type=resdx.StagingType.TWO_STAGE, seer=seer2, hspf=hspf2 ) size = resdx.to_u(dx_unit.rated_net_total_cooling_capacity[0], "ton_ref") diff --git a/examples/inverse-calculations.py b/examples/inverse-calculations.py index 88511d0..a2f9337 100644 --- a/examples/inverse-calculations.py +++ b/examples/inverse-calculations.py @@ -1,4 +1,6 @@ # %% +import csv +from copy import deepcopy import numpy as np from scipy import optimize @@ -12,16 +14,95 @@ linear_string, quadratic, quadratic_string, - cubic, - cubic_string, - quartic, - quartic_string, + # cubic, + # cubic_string, + # quartic, + # quartic_string, calculate_r_squared, ) import resdx +class CurveFit: + def __init__(self, function, regression_string, initial_coefficient_guesses): + self.function = function + self.regression_string = regression_string + self.initial_coefficient_guesses = initial_coefficient_guesses + + +linear_curve_fit = CurveFit(linear, linear_string, (1, 1)) +quadratic_curve_fit = CurveFit(quadratic, quadratic_string, (1, 1, 1)) + + +class RatingRegression: + def __init__( + self, + staging_type: resdx.StagingType, + calculation, + target_title: str, + initial_guess, + rating_range: DisplayData, + secondary_range: DisplayData, + curve_fit: CurveFit, + ): + self.staging_type = staging_type + self.calculation = calculation + self.target_title = target_title + self.initial_guess = initial_guess + self.rating_range = rating_range + self.secondary_range = secondary_range + self.curve_fit = curve_fit + + def evaluate(self, output_name): + display_data = [] + for secondary_value in self.secondary_range.data_values: + series_name = f"{self.secondary_range.name}={secondary_value:.2}" + + print(f"Evaluating {self.staging_type.name} ({series_name})") + try: + inputs, coefficients = get_inverse_values( + self.rating_range, + lambda x, target: self.calculation( + x, target, self.staging_type, secondary_value + ), + self.initial_guess, + curve_fit_function=self.curve_fit.function, + curve_fit_guesses=self.curve_fit.initial_coefficient_guesses, + ) + except RuntimeError as e: + raise RuntimeError( + f"Unable to find solution for {self.staging_type.name} ({series_name}): {e}" + ) + + display_data.append( + DisplayData( + inputs, + name=series_name, + native_units="W/W", + line_properties=MarkersOnly(), + ) + ) + curve_fit_string = self.curve_fit.regression_string( + self.rating_range.name, *coefficients + ) + curve_fit_data = [ + self.curve_fit.function(rating, *coefficients) + for rating in self.rating_range.data_values + ] + r2 = calculate_r_squared(inputs, curve_fit_data) + display_data.append( + DisplayData( + curve_fit_data, + name=f"{series_name}: {curve_fit_string}, R2={r2:.4g}", + native_units="W/W", + line_properties=LinesOnly(), + ) + ) + + plot(self.rating_range, display_data, self.target_title, output_name) + + def plot(x, ys, y_axis_name, figure_name): plot = DimensionalPlot(x) for y in ys: @@ -61,182 +142,77 @@ def get_inverse_values( return inverse_values, curve_fit_coefficients -def make_plot(target_range, systems, axis_title, figure_name): - display_data = [] - for system_name, system in systems.items(): - print(f"Calculating system: {system_name}") - try: - inputs, coefficients = get_inverse_values( - target_range, - system["calculation"], - initial_guess=system["initial_guess"], - curve_fit_function=system["curve_fit"]["function"], - curve_fit_guesses=system["curve_fit"]["guesses"], - ) - except RuntimeError as e: - raise RuntimeError(f"Unable to find solution for {system_name}: {e}") - - display_data.append( - DisplayData( - inputs, - name=system_name, - native_units="W/W", - line_properties=MarkersOnly(), - ) - ) - curve_fit_string = system["curve_fit"]["string"]( - target_range.name, *coefficients - ) - curve_fit_data = [ - system["curve_fit"]["function"](metric, *coefficients) - for metric in target_range.data_values - ] - r2 = calculate_r_squared(inputs, curve_fit_data) - display_data.append( - DisplayData( - curve_fit_data, - name=f"{system_name}: {curve_fit_string}, R2={r2:.4g}", - native_units="W/W", - line_properties=LinesOnly(), - ) - ) - - plot(target_range, display_data, axis_title, figure_name) - - # Cooling - -cooling_systems = { - "Single Speed": { - "calculation": lambda cop, seer: resdx.DXUnit( - rated_gross_cooling_cop=cop, input_seer=seer - ).seer(), - "initial_guess": lambda x: x / 3.0, - "curve_fit": { - "function": quadratic, - "string": quadratic_string, - "guesses": (1, 1, 1), - }, - }, - "Two Speed": { - "calculation": lambda cop, seer: resdx.DXUnit( - staging_type=resdx.StagingType.TWO_STAGE, - rated_gross_cooling_cop=cop, - input_seer=seer, - ).seer(), - "initial_guess": lambda x: x / 3.0, - "curve_fit": { - "function": quadratic, - "string": quadratic_string, - "guesses": (1, 1, 1), - }, - }, -} - - -make_plot( - DimensionalData(np.linspace(6, 26.5, 10), name="SEER2", native_units="Btu/Wh"), - cooling_systems, - "Gross COP (at Afull conditions)", - "cooling-cop-v-seer", +def seer_function(cop_82_min, seer, staging_type, seer_eer_ratio): + return resdx.DXUnit( + staging_type=staging_type, + input_seer=seer, + input_eer=seer / seer_eer_ratio, + rated_net_total_cooling_cop_82_min=cop_82_min, + input_hspf=10.0, + ).seer() + + +two_speed_cooling_regression = RatingRegression( + staging_type=resdx.StagingType.TWO_STAGE, + calculation=seer_function, + target_title="Net COP (at B low conditions)", + initial_guess=lambda target: target / 3.0, + rating_range=DimensionalData( + np.linspace(6, 26.5, 2), name="SEER2", native_units="Btu/Wh" + ), # All straight lines don't need more than two points + secondary_range=DimensionalData( + np.linspace(1.2, 2.0, 10), name="SEER2/EER2", native_units="W/W" + ), + curve_fit=linear_curve_fit, ) -seer_eer_ratio_range = np.linspace(1.2, 2.0, 10) - - -vs_cooling_systems = {} - -for seer_eer_ratio in seer_eer_ratio_range: - vs_cooling_systems[f"SEER2/EER2={seer_eer_ratio:.2}"] = { - "calculation": lambda eir_r, seer, ratio=seer_eer_ratio: resdx.DXUnit( - staging_type=resdx.StagingType.VARIABLE_SPEED, - min_net_total_cooling_eir_ratio_82=eir_r, - input_eer=seer / ratio, - input_seer=seer, - input_hspf=10.0, - ).seer(), - "initial_guess": lambda _: 0.75, - "curve_fit": { - "function": linear, - "string": linear_string, - "guesses": (1, 1), - }, - } - -make_plot( - DimensionalData(np.linspace(14, 35, 10), name="SEER2", native_units="Btu/Wh"), - vs_cooling_systems, - "Net COP 82 max / Net COP 82 min", - "cooling-vs-eir_r-v-seer", +variable_speed_cooling_regression = deepcopy(two_speed_cooling_regression) +variable_speed_cooling_regression.staging_type = resdx.StagingType.VARIABLE_SPEED +variable_speed_cooling_regression.rating_range = DimensionalData( + np.linspace(14, 35, 3), name="SEER2", native_units="Btu/Wh" ) +# two_speed_cooling_regression.evaluate("cooling-two-speed-cop82-v-seer") +# variable_speed_cooling_regression.evaluate("cooling-variable-speed-cop82-v-seer") + + # Heating -heating_systems = { - "Single Speed": { - "calculation": lambda cop, hspf: resdx.DXUnit( - rated_gross_heating_cop=cop, input_hspf=hspf - ).hspf(), - "initial_guess": lambda x: x / 2.0, - "curve_fit": { - "function": quartic, - "string": quartic_string, - "guesses": (1, 1, 1, 1, 1), - }, - }, - "Two Speed": { - "calculation": lambda cop, hspf: resdx.DXUnit( - staging_type=resdx.StagingType.TWO_STAGE, - rated_gross_heating_cop=cop, - input_hspf=hspf, - ).hspf(), - "initial_guess": lambda x: x / 2.0, - "curve_fit": { - "function": cubic, - "string": cubic_string, - "guesses": (1, 1, 1, 1), - }, - }, -} - -hspf_range = DimensionalData( - np.linspace(5, 16, 10), name="HSPF2", native_units="Btu/Wh" +def hspf_function(cop_47, hspf, staging_type, cap17m): + return resdx.DXUnit( + staging_type=staging_type, + rated_net_heating_capacity=fr_u(3.0, "ton_ref"), + rated_net_heating_capacity_17=fr_u(3.0, "ton_ref") * cap17m, + rated_net_heating_cop=cop_47, + input_hspf=hspf, + input_seer=19.0, + input_eer=10.0, + ).hspf() + + +single_speed_heating_regression = RatingRegression( + staging_type=resdx.StagingType.SINGLE_STAGE, + calculation=hspf_function, + target_title="Net COP (at H1 full conditions)", + initial_guess=lambda target: target / 2.0, + rating_range=DimensionalData( + np.linspace(5, 11, 5), name="HSPF2", native_units="Btu/Wh" + ), + secondary_range=DimensionalData( + [0.5, 0.55, 0.6, 0.7, 0.8, 1.1], name="Q17/Q47", native_units="Btu/Btu" + ), + curve_fit=quadratic_curve_fit, ) +two_speed_heating_regression = deepcopy(single_speed_heating_regression) +two_speed_heating_regression.staging_type = resdx.StagingType.TWO_STAGE -make_plot( - hspf_range, - heating_systems, - "Gross COP (at H1full conditions)", - "heating-cop-v-hspf", +variable_speed_heating_regression = deepcopy(single_speed_heating_regression) +variable_speed_heating_regression.staging_type = resdx.StagingType.VARIABLE_SPEED +variable_speed_cooling_regression.rating_range = DimensionalData( + np.linspace(5, 16, 5), name="HSPF2", native_units="Btu/Wh" ) -cap_17_maintenance_range = [0.5, 0.55, 0.6, 0.7, 0.8, 1.1] - - -vs_heating_systems = {} - -for cap_17_maintenance in cap_17_maintenance_range: - vs_heating_systems[f"Q17/Q47={cap_17_maintenance:.2}"] = { - "calculation": lambda cop, hspf, cap_m=cap_17_maintenance: resdx.DXUnit( - staging_type=resdx.StagingType.VARIABLE_SPEED, - rated_net_heating_capacity=fr_u(3.0, "ton_ref"), - rated_net_heating_capacity_17=fr_u(3.0, "ton_ref") * cap_m, - rated_net_heating_cop=cop, - input_eer=10, - input_seer=19.0, - input_hspf=hspf, - ).hspf(), - "initial_guess": lambda x: x / 2.0, - "curve_fit": { - "function": quadratic, - "string": quadratic_string, - "guesses": (1, 1, 1), - }, - } - -make_plot( - hspf_range, - vs_heating_systems, - "Net COP (at H1full conditions)", - "heating-vs-cop-v-hspf", -) +single_speed_heating_regression.evaluate("heating-single-speed-cop47-v-hspf") +two_speed_heating_regression.evaluate("heating-two-speed-cop47-v-hspf") +variable_speed_heating_regression.evaluate("heating-variable-speed-cop47-v-hspf") diff --git a/examples/neep-examples.py b/examples/neep-examples.py index 14b1290..de06e84 100644 --- a/examples/neep-examples.py +++ b/examples/neep-examples.py @@ -40,7 +40,7 @@ dx_units[name] = resdx.DXUnit( - neep_data=resdx.models.neep_data.make_neep_model_data( + tabular_data=resdx.models.tabular_data.make_neep_model_data( cooling_capacities, cooling_powers, heating_capacities, heating_powers ), input_seer=seer2, @@ -90,7 +90,7 @@ ] dx_units[name] = resdx.DXUnit( - neep_data=resdx.models.neep_data.make_neep_model_data( + tabular_data=resdx.models.tabular_data.make_neep_model_data( cooling_capacities, cooling_powers, heating_capacities, heating_powers ), input_seer=seer2, @@ -140,7 +140,7 @@ ] dx_units[name] = resdx.DXUnit( - neep_data=resdx.models.neep_data.make_neep_model_data( + tabular_data=resdx.models.tabular_data.make_neep_model_data( cooling_capacities, cooling_powers, heating_capacities, heating_powers ), input_seer=seer2, @@ -190,7 +190,7 @@ ] dx_units[name] = resdx.DXUnit( - neep_data=resdx.models.neep_data.make_neep_model_data( + tabular_data=resdx.models.tabular_data.make_neep_model_data( cooling_capacities, cooling_powers, heating_capacities, heating_powers ), input_seer=seer2, diff --git a/examples/pthp-equivalence.py b/examples/pthp-equivalence.py index 89949ea..9d40a90 100644 --- a/examples/pthp-equivalence.py +++ b/examples/pthp-equivalence.py @@ -7,17 +7,19 @@ fr_u = resdx.fr_u -cooling_capacity = 9600 +cooling_capacity = 9700 cooling_eer = 12.2 -heating_capacity = 8000 +heating_capacity = 8100 heating_cop = 3.7 dx_unit = resdx.DXUnit( is_ducted=False, rated_net_cooling_cop=fr_u(cooling_eer, "Btu/(W*h)"), + rated_gross_cooling_cop=None, rated_net_heating_cop=heating_cop, - rated_net_total_cooling_capacity=fr_u(cooling_capacity, "Btu/h"), - rated_net_heating_capacity=fr_u(heating_capacity, "Btu/h"), + rated_gross_heating_cop=None, + rated_net_total_cooling_capacity=[fr_u(cooling_capacity, "Btu/h")], + rated_net_heating_capacity=[fr_u(heating_capacity, "Btu/h")], rating_standard=AHRIVersion.AHRI_210_240_2017, ) diff --git a/resdx/dx_unit.py b/resdx/dx_unit.py index 363202b..b330db7 100644 --- a/resdx/dx_unit.py +++ b/resdx/dx_unit.py @@ -156,7 +156,7 @@ def __init__( rated_net_heating_cop=None, rated_heating_airflow_per_rated_net_capacity=None, c_d_heating=None, - heating_off_temperature=fr_u(0.0, "°F"), + heating_off_temperature=None, heating_on_temperature=None, # default to heating_off_temperature defrost=None, # Fan @@ -200,6 +200,14 @@ def __init__( self.input_hspf = input_hspf self.input_eer = input_eer + if self.input_seer is not None: + if self.input_eer is None: + self.input_eer = ( + 10.0 + 0.84 * (self.input_seer - 11.5) + if self.input_seer < 13.0 + else 11.3 + 0.57 * (self.input_seer - 13.0) + ) + self.input_rated_net_heating_cop = rated_net_heating_cop self.cycling_method = cycling_method @@ -216,10 +224,7 @@ def __init__( crankcase_heater_setpoint_temperature ) self.rating_standard = rating_standard - self.heating_off_temperature = heating_off_temperature self.refrigerant_charge_deviation = refrigerant_charge_deviation - if heating_on_temperature == None: - self.heating_on_temperature = self.heating_off_temperature # Placeholder for additional data set specific to the model self.model_data: dict = {} @@ -279,6 +284,16 @@ def __init__( else: self.staging_type = staging_type + if heating_off_temperature is None: + if self.staging_type == StagingType.VARIABLE_SPEED: + self.heating_off_temperature = fr_u(-20.0, "degF") + else: + self.heating_off_temperature = fr_u(0.0, "degF") + else: + self.heating_off_temperature = heating_off_temperature + if heating_on_temperature is None: + self.heating_on_temperature = self.heating_off_temperature + # Placeholders for derived staging array values self.set_placeholder_arrays() @@ -294,6 +309,12 @@ def __init__( rated_heating_airflow_per_rated_net_capacity ) + # Degradation coefficients + self.c_d_cooling: float + self.c_d_heating: float + self.model.set_c_d_cooling(c_d_cooling) + self.model.set_c_d_heating(c_d_heating) + # Set net capacities and fan self.rated_net_total_cooling_capacity: List[float] self.rated_net_heating_capacity: List[float] @@ -302,12 +323,6 @@ def __init__( rated_net_total_cooling_capacity, rated_net_heating_capacity, fan ) - # Degradation coefficients - self.c_d_cooling: float - self.c_d_heating: float - self.model.set_c_d_cooling(c_d_cooling) - self.model.set_c_d_heating(c_d_heating) - self.rated_full_flow_external_static_pressure = ( self.get_rated_full_flow_rated_pressure() ) diff --git a/resdx/models/nrel.py b/resdx/models/nrel.py index b549923..65c2cca 100644 --- a/resdx/models/nrel.py +++ b/resdx/models/nrel.py @@ -17,6 +17,50 @@ class NRELDXModel(DXModel): """Also, some assumptions from: https://github.com/NREL/OpenStudio-ERI/blob/master/hpxml-measures/HPXMLtoOpenStudio/resources/hvac.rb""" + COOLING_EIR_FT_COEFFICIENTS = [ + -3.400341169, + 0.135184783, + -0.001037932, + -0.007852322, + 0.000183438, + -0.000142548, + ] + + COOLING_EIR_FF_COEFFICIENTS = [1.143487507, -0.13943972, -0.004047787] + + COOLING_CAP_FT_COEFFICIENTS = [ + 3.717717741, + -0.09918866, + 0.000964488, + 0.005887776, + -1.2808e-05, + -0.000132822, + ] + + COOLING_CAP_FF_COEFFICIENTS = [0.718664047, 0.41797409, -0.136638137] + + HEATING_EIR_FT_COEFFICIENTS = [ + 0.722917608, + 0.003520184, + 0.000143097, + -0.005760341, + 0.000141736, + -0.000216676, + ] + + HEATING_EIR_FF_COEFFICIENTS = [2.185418751, -1.942827919, 0.757409168] + + HEATING_CAP_FT_COEFFICIENTS = [ + 0.568706266, + -0.000747282, + -1.03432e-05, + 0.00945408, + 5.0812e-05, + -6.77828e-06, + ] + + HEATING_CAP_FF_COEFFICIENTS = [0.694045465, 0.474207981, -0.168253446] + def gross_cooling_power(self, conditions): """From Cutler et al.""" T_iwb = bracket( @@ -26,34 +70,20 @@ def gross_cooling_power(self, conditions): to_u(conditions.outdoor.db, "°F"), min=75.0 ) # Cutler curves use °F eir_FT = calc_biquad( - [ - -3.437356399, - 0.136656369, - -0.001049231, - -0.0079378, - 0.000185435, - -0.0001441, - ], + NRELDXModel.COOLING_EIR_FT_COEFFICIENTS, T_iwb, T_odb, ) eir_FF = calc_quad( - [1.143487507, -0.13943972, -0.004047787], conditions.mass_airflow_ratio + NRELDXModel.COOLING_EIR_FF_COEFFICIENTS, conditions.mass_airflow_ratio ) cap_FT = calc_biquad( - [ - 3.68637657, - -0.098352478, - 0.000956357, - 0.005838141, - -0.0000127, - -0.000131702, - ], + NRELDXModel.COOLING_CAP_FT_COEFFICIENTS, T_iwb, T_odb, ) cap_FF = calc_quad( - [0.718664047, 0.41797409, -0.136638137], conditions.mass_airflow_ratio + NRELDXModel.COOLING_CAP_FF_COEFFICIENTS, conditions.mass_airflow_ratio ) return limit_check( eir_FF @@ -76,19 +106,12 @@ def gross_total_cooling_capacity(self, conditions): to_u(conditions.outdoor.db, "°F"), min=75.0 ) # Cutler curves use °F cap_FT = calc_biquad( - [ - 3.68637657, - -0.098352478, - 0.000956357, - 0.005838141, - -0.0000127, - -0.000131702, - ], + NRELDXModel.COOLING_CAP_FT_COEFFICIENTS, T_iwb, T_odb, ) # Note: Equals 0.9915 at rating conditions (not 1.0) cap_FF = calc_quad( - [0.718664047, 0.41797409, -0.136638137], conditions.mass_airflow_ratio + NRELDXModel.COOLING_CAP_FF_COEFFICIENTS, conditions.mass_airflow_ratio ) return ( cap_FF @@ -103,34 +126,20 @@ def gross_steady_state_heating_power(self, conditions): T_idb = to_u(conditions.indoor.db, "°F") # Cutler curves use °F T_odb = to_u(conditions.outdoor.db, "°F") # Cutler curves use °F eir_FT = calc_biquad( - [ - 0.718398423, - 0.003498178, - 0.000142202, - -0.005724331, - 0.00014085, - -0.000215321, - ], + NRELDXModel.HEATING_EIR_FT_COEFFICIENTS, T_idb, T_odb, ) eir_FF = calc_quad( - [2.185418751, -1.942827919, 0.757409168], conditions.mass_airflow_ratio + NRELDXModel.HEATING_EIR_FF_COEFFICIENTS, conditions.mass_airflow_ratio ) cap_FT = calc_biquad( - [ - 0.566333415, - -0.000744164, - -0.0000103, - 0.009414634, - 0.0000506, - -0.00000675, - ], + NRELDXModel.HEATING_CAP_FT_COEFFICIENTS, T_idb, T_odb, ) cap_FF = calc_quad( - [0.694045465, 0.474207981, -0.168253446], conditions.mass_airflow_ratio + NRELDXModel.HEATING_CAP_FF_COEFFICIENTS, conditions.mass_airflow_ratio ) return ( eir_FF @@ -146,19 +155,12 @@ def gross_steady_state_heating_capacity(self, conditions): T_idb = to_u(conditions.indoor.db, "°F") # Cutler curves use °F T_odb = to_u(conditions.outdoor.db, "°F") # Cutler curves use °F cap_FT = calc_biquad( - [ - 0.566333415, - -0.000744164, - -0.0000103, - 0.009414634, - 0.0000506, - -0.00000675, - ], + NRELDXModel.HEATING_CAP_FT_COEFFICIENTS, T_idb, T_odb, ) cap_FF = calc_quad( - [0.694045465, 0.474207981, -0.168253446], conditions.mass_airflow_ratio + NRELDXModel.HEATING_CAP_FF_COEFFICIENTS, conditions.mass_airflow_ratio ) return ( cap_FF diff --git a/resdx/models/neep_data.py b/resdx/models/tabular_data.py similarity index 62% rename from resdx/models/neep_data.py rename to resdx/models/tabular_data.py index b131502..767a28a 100644 --- a/resdx/models/neep_data.py +++ b/resdx/models/tabular_data.py @@ -6,10 +6,12 @@ from koozie import fr_u -from ..util import bracket +from ..util import bracket, calc_biquad +from .nrel import NRELDXModel -class NEEPPerformanceTable: + +class TemperatureSpeedPerformanceTable: def __init__( self, temperatures: List[float], @@ -76,7 +78,16 @@ def set_by_interpolation(self, speed: int, temperature: float) -> None: self.data[t1][s2] - self.data[t1][s0] ) - def add_temperature(self, temperature: float, extrapolate: bool = True) -> None: + def add_temperature( + self, + temperature: float, + extrapolate: bool = True, + extrapolation_limit: Union[float, None] = None, + ) -> None: + if temperature in self.temperatures: + raise RuntimeError( + f"Temperature, {temperature:.2f}, already exists. Unable to add temperature." + ) insort(self.temperatures, temperature) t_i = self.temperatures.index(temperature) # temperature index @@ -90,11 +101,17 @@ def add_temperature(self, temperature: float, extrapolate: bool = True) -> None: for speed in self.speeds: s_i = speed - 1 # speed index if extrapolate: - self.data[t_i][s_i] = self.data[t_i + 1][s_i] - ( + value = self.data[t_i + 1][s_i] - ( self.data[t_i + 2][s_i] - self.data[t_i + 1][s_i] ) / (t_2 - t_1) * (t_1 - t_0) + if extrapolation_limit is not None: + value = max( + value, self.data[t_i + 1][s_i] * extrapolation_limit + ) else: - self.data[t_i][s_i] = self.data[t_i + 1][s_i] + value = self.data[t_i + 1][s_i] + + self.data[t_i][s_i] = value elif t_i == len(self.temperatures) - 1: # Extrapolate above @@ -104,11 +121,17 @@ def add_temperature(self, temperature: float, extrapolate: bool = True) -> None: for speed in self.speeds: s_i = speed - 1 # speed index if extrapolate: - self.data[t_i][s_i] = self.data[t_i - 1][s_i] + ( + value = self.data[t_i - 1][s_i] + ( self.data[t_i - 1][s_i] - self.data[t_i - 2][s_i] ) / (t_m1 - t_m2) * (t - t_m1) + if extrapolation_limit is not None: + value = min( + value, self.data[t_i - 1][s_i] * extrapolation_limit + ) else: - self.data[t_i][s_i] = self.data[t_i - 1][s_i] + value = self.data[t_i - 1][s_i] + + self.data[t_i][s_i] = value else: # Interpolate @@ -159,7 +182,7 @@ def apply_fan_power_correction(self, fan_powers: List[float]) -> None: self.set_interpolator() -class NEEPCoolingPerformanceTable(NEEPPerformanceTable): +class TemperatureSpeedCoolingPerformanceTable(TemperatureSpeedPerformanceTable): def set_by_maintenance( self, speed: int, temperature: float, reference_temperature: float, ratio: float ) -> None: @@ -176,7 +199,7 @@ def set_by_maintenance( ) -class NEEPHeatingPerformanceTable(NEEPPerformanceTable): +class TemperatureSpeedHeatingPerformanceTable(TemperatureSpeedPerformanceTable): def set_by_maintenance( self, speed: int, temperature: float, reference_temperature: float, ratio: float ) -> None: @@ -193,14 +216,14 @@ def set_by_maintenance( ) -class NEEPPerformance: +class TemperatureSpeedPerformance: def __init__( self, - cooling_capacities: NEEPCoolingPerformanceTable, - cooling_powers: NEEPCoolingPerformanceTable, - heating_capacities: NEEPHeatingPerformanceTable, - heating_powers: NEEPHeatingPerformanceTable, + cooling_capacities: TemperatureSpeedCoolingPerformanceTable, + cooling_powers: TemperatureSpeedCoolingPerformanceTable, + heating_capacities: TemperatureSpeedHeatingPerformanceTable, + heating_powers: TemperatureSpeedHeatingPerformanceTable, ) -> None: self.number_of_cooling_speeds = len(cooling_capacities.speeds) @@ -267,31 +290,28 @@ def make_neep_statistical_model_data( heating_capacity_17: float, hspf2: float, max_cooling_temperature: float = fr_u(125, "degF"), - min_heating_temperature: float = fr_u(0, "degF"), + min_heating_temperature: float = fr_u(-20, "degF"), cooling_capacity_ratio: Union[float, None] = None, # min/max capacity ratio at 95F - cooling_eir_ratio: Union[float, None] = None, # min/max eir ratio at 82F + cooling_cop_82_min: Union[float, None] = None, heating_cop_47: Union[float, None] = None, -) -> NEEPPerformance: +) -> TemperatureSpeedPerformance: Qmin = 1 Qrated = 2 Qmax = 3 + t_60 = fr_u(60.0, "degF") + # COOLING - t_82 = fr_u(82, "degF") - t_95 = fr_u(95, "degF") + t_82 = fr_u(82.0, "degF") + t_95 = fr_u(95.0, "degF") t_c = [ t_82, t_95, ] - if cooling_eir_ratio is not None: - EIRr82min = max(cooling_eir_ratio, 0.2) - else: - EIRr82min = bracket(1.305 - 0.324 * seer2 / eer2, 0.2, 1.0) - Qr95rated = 0.934 Qm95max = 0.940 Qm95min = 0.948 @@ -299,31 +319,21 @@ def make_neep_statistical_model_data( EIRm95max = 1.326 EIRm95min = 1.315 - if cooling_capacity_ratio is not None: - Qr95min = cooling_capacity_ratio - else: - Qr95min = bracket( - 0.510 - 0.119 * (EIRr82min - 1.305) / (-0.324), 0.1, Qr95rated - ) - - Q_c = NEEPCoolingPerformanceTable(t_c, 3) - P_c = NEEPCoolingPerformanceTable(t_c, 3) + Q_c = TemperatureSpeedCoolingPerformanceTable(t_c, 3) + P_c = TemperatureSpeedCoolingPerformanceTable(t_c, 3) # Net Total Capacity # 95 F Q_c.set(Qrated, t_95, cooling_capacity_95) Q_c.set_by_ratio(Qmax, t_95, Qr95rated) - Q_c.set_by_ratio(Qmin, t_95, Qr95min) # 82 F Q_c.set_by_maintenance(Qmax, t_82, t_95, Qm95max) - Q_c.set_by_maintenance(Qmin, t_82, t_95, Qm95min) - Q_c.set_by_interpolation(Qrated, t_82) + # Other speeds calculated later # Net Power Pr95rated = Qr95rated * EIRr95rated - Pr82min = Q_c.get_ratio(Qmin, t_82) * EIRr82min Pm95min = Qm95min * EIRm95min Pm95max = Qm95max * EIRm95max @@ -331,13 +341,34 @@ def make_neep_statistical_model_data( P_c.set(Qrated, t_95, Q_c.get(Qrated, t_95) / fr_u(eer2, "Btu/Wh")) P_c.set_by_ratio(Qmax, t_95, Pr95rated) P_c.set_by_maintenance(Qmax, t_82, t_95, Pm95max) - P_c.set_by_ratio(Qmin, t_82, Pr82min) + if cooling_cop_82_min is None: + EIRr82min = bracket( + 1.305 - 0.324 * seer2 / eer2, 0.2, 1.0 + ) # TODO: Replace with new regression + cooling_cop_82_min = (Q_c.get(Qmax, t_82) / P_c.get(Qmax, t_82)) / EIRr82min + else: + EIRr82min = (Q_c.get(Qmax, t_82) / P_c.get(Qmax, t_82)) / cooling_cop_82_min + + if cooling_capacity_ratio is not None: + Qr95min = cooling_capacity_ratio + else: + Qr95min = bracket( + 0.510 - 0.119 * (EIRr82min - 1.305) / (-0.324), 0.1, Qr95rated + ) + + # Back to capacities + Q_c.set_by_ratio(Qmin, t_95, Qr95min) + Q_c.set_by_maintenance(Qmin, t_82, t_95, Qm95min) + Q_c.set_by_interpolation(Qrated, t_82) + + P_c.set(Qmin, t_82, Q_c.get(Qmin, t_82) / cooling_cop_82_min) + P_c.set_by_maintenance(Qmin, t_95, t_82, Pm95min) P_c.set_by_interpolation(Qrated, t_82) # Tmin - Q_c.add_temperature(fr_u(60.0, "degF")) - P_c.add_temperature(fr_u(60.0, "degF")) + Q_c.add_temperature(t_60, extrapolation_limit=0.5) + P_c.add_temperature(t_60, extrapolation_limit=0.5) # Tmax Q_c.add_temperature(max_cooling_temperature) @@ -382,8 +413,8 @@ def make_neep_statistical_model_data( else: Qm17rated = 0.689 - Q_h = NEEPHeatingPerformanceTable(t_h, 3) - P_h = NEEPHeatingPerformanceTable(t_h, 3) + Q_h = TemperatureSpeedHeatingPerformanceTable(t_h, 3) + P_h = TemperatureSpeedHeatingPerformanceTable(t_h, 3) # Net Total Capacity @@ -411,7 +442,7 @@ def make_neep_statistical_model_data( Q_h.set_by_interpolation(Qrated, t_min) # Tmax - Q_h.add_temperature(fr_u(60.0, "degF"), False) + Q_h.add_temperature(t_60, False) # Net Power if heating_cop_47 is None: @@ -453,14 +484,14 @@ def make_neep_statistical_model_data( P_h.set_by_interpolation(Qrated, t_min) # Tmax - P_h.add_temperature(fr_u(60.0, "degF"), False) + P_h.add_temperature(t_60, False) Q_c.set_interpolator() P_c.set_interpolator() Q_h.set_interpolator() P_h.set_interpolator() - return NEEPPerformance(Q_c, P_c, Q_h, P_h) + return TemperatureSpeedPerformance(Q_c, P_c, Q_h, P_h) def make_neep_model_data( @@ -508,17 +539,25 @@ def make_neep_model_data( t_lct = fr_u(lct, "degF") heating_temperatures = [t_lct] + heating_temperatures - Q_c = NEEPCoolingPerformanceTable(cooling_temperatures, 3, cooling_capacities) - P_c = NEEPCoolingPerformanceTable(cooling_temperatures, 3, cooling_powers) - Q_h = NEEPHeatingPerformanceTable(heating_temperatures, 3, heating_capacities) - P_h = NEEPHeatingPerformanceTable(heating_temperatures, 3, heating_powers) + Q_c = TemperatureSpeedCoolingPerformanceTable( + cooling_temperatures, 3, cooling_capacities + ) + P_c = TemperatureSpeedCoolingPerformanceTable( + cooling_temperatures, 3, cooling_powers + ) + Q_h = TemperatureSpeedHeatingPerformanceTable( + heating_temperatures, 3, heating_capacities + ) + P_h = TemperatureSpeedHeatingPerformanceTable( + heating_temperatures, 3, heating_powers + ) # Interpolate for missing rated conditions, and extrapolate to extreme temperatures Q_c.set_by_interpolation(2, t_82) P_c.set_by_interpolation(2, t_82) - Q_c.add_temperature(t_60) - P_c.add_temperature(t_60) + Q_c.add_temperature(t_60, extrapolation_limit=0.5) + P_c.add_temperature(t_60, extrapolation_limit=0.5) Q_c.add_temperature(max_cooling_temperature) P_c.add_temperature(max_cooling_temperature) @@ -547,4 +586,271 @@ def make_neep_model_data( Q_h.set_interpolator() P_h.set_interpolator() - return NEEPPerformance(Q_c, P_c, Q_h, P_h) + return TemperatureSpeedPerformance(Q_c, P_c, Q_h, P_h) + + +def make_single_speed_model_data( + cooling_capacity_95: float, # Net total cooling capacity at 95F and rated speed + seer2: float, + eer2: float, + heating_capacity_47: float, + heating_capacity_17: Union[float, None], + hspf2: float, + max_cooling_temperature: float = fr_u(125, "degF"), + min_heating_temperature: float = fr_u(-20, "degF"), + heating_cop_47: Union[float, None] = None, + cycling_degradation_coefficient: float = 0.15, +) -> TemperatureSpeedPerformance: + Qrated = 1 + + t_60 = fr_u(60.0, "degF") + + # COOLING + + t_82 = fr_u(82.0, "degF") + t_95 = fr_u(95.0, "degF") + + t_c = [ + t_82, + t_95, + ] + + Qm95rated = 1.0 / calc_biquad(NRELDXModel.COOLING_CAP_FT_COEFFICIENTS, 67.0, 82.0) + + Q_c = TemperatureSpeedCoolingPerformanceTable(t_c, 1) + P_c = TemperatureSpeedCoolingPerformanceTable(t_c, 1) + + # Net Total Capacity + + # 95 F + Q_c.set(Qrated, t_95, cooling_capacity_95) + + # 82 F + Q_c.set_by_maintenance(Qrated, t_82, t_95, Qm95rated) + + # Net Power + + # 95/82 F + P_c.set(Qrated, t_95, Q_c.get(Qrated, t_95) / fr_u(eer2, "Btu/Wh")) + eer2_b = seer2 / ( + 1.0 - 0.5 * cycling_degradation_coefficient + ) # EER2 at B (82F) conditions + P_c.set(Qrated, t_82, Q_c.get(Qrated, t_82) / fr_u(eer2_b, "Btu/Wh")) + + # Tmin + Q_c.add_temperature(t_60, extrapolation_limit=0.5) + P_c.add_temperature(t_60, extrapolation_limit=0.5) + + # Tmax + Q_c.add_temperature(max_cooling_temperature) + P_c.add_temperature(max_cooling_temperature) + + # HEATING + + t_min = min_heating_temperature + t_5 = fr_u(5, "degF") + t_17 = fr_u(17, "degF") + t_47 = fr_u(47, "degF") + + t_h = [ + t_17, + t_47, + ] + + EIRm17rated = calc_biquad(NRELDXModel.HEATING_EIR_FT_COEFFICIENTS, 70.0, 17.0) + + if heating_capacity_17 is not None: + Qm17rated = heating_capacity_17 / heating_capacity_47 + else: + Qm17rated = ( + 0.626 # Based on AHRI directory units believed to be single speed (4/4/24) + ) + + Q_h = TemperatureSpeedHeatingPerformanceTable(t_h, 1) + P_h = TemperatureSpeedHeatingPerformanceTable(t_h, 1) + + # Net Total Capacity + + # 47 F + Q_h.set(Qrated, t_47, heating_capacity_47) + + # 17 F + Q_h.set_by_maintenance(Qrated, t_17, t_47, Qm17rated) + + # 5 F + Q_h.add_temperature(t_5, True) + + # Tmin + Q_h.add_temperature(t_min, True) + + # Tmax + Q_h.add_temperature(t_60, False) + + # Net Power + if heating_cop_47 is None: + heating_cop_47 = 2.837 + 0.066 * hspf2 # TODO: Replace with inverse correlation + + Pm17rated = Qm17rated * EIRm17rated + + # 47 F + P_h.set(Qrated, t_47, Q_h.get(Qrated, t_47) / heating_cop_47) + + # 17 F + P_h.set_by_maintenance(Qrated, t_17, t_47, Pm17rated) + + # 5 F + P_h.add_temperature(t_5) + + # Tmin + P_h.add_temperature(t_min) + + # Tmax + P_h.add_temperature(t_60, extrapolate=False) + + Q_c.set_interpolator() + P_c.set_interpolator() + Q_h.set_interpolator() + P_h.set_interpolator() + + return TemperatureSpeedPerformance(Q_c, P_c, Q_h, P_h) + + +def make_two_speed_model_data( + cooling_capacity_95: float, # Net total cooling capacity at 95F and rated speed + seer2: float, + eer2: float, + heating_capacity_47: float, + heating_capacity_17: Union[float, None], + hspf2: float, + max_cooling_temperature: float = fr_u(125, "degF"), + min_heating_temperature: float = fr_u(-20, "degF"), + cooling_cop_82_min: Union[float, None] = None, + heating_cop_47: Union[float, None] = None, +) -> TemperatureSpeedPerformance: + Qmin = 1 + Qrated = 2 + + t_60 = fr_u(60.0, "degF") + + # COOLING + + t_82 = fr_u(82.0, "degF") + t_95 = fr_u(95.0, "degF") + + t_c = [ + t_82, + t_95, + ] + + Qm95rated = 1.0 / calc_biquad(NRELDXModel.COOLING_CAP_FT_COEFFICIENTS, 67.0, 82.0) + EIRm95rated = 1.0 / calc_biquad(NRELDXModel.COOLING_EIR_FT_COEFFICIENTS, 67.0, 82.0) + + QrCmin = 0.728 # Converted from gross 0.72 + # EIRrCmin = 0.869 # Converted from gross 0.91 (Not used. Kept for completeness.) + + Q_c = TemperatureSpeedCoolingPerformanceTable(t_c, 2) + P_c = TemperatureSpeedCoolingPerformanceTable(t_c, 2) + + # Net Total Capacity + + # 95 F + Q_c.set(Qrated, t_95, cooling_capacity_95) + Q_c.set_by_ratio(Qmin, t_95, QrCmin) + + # 82 F + Q_c.set_by_maintenance(Qrated, t_82, t_95, Qm95rated) + Q_c.set_by_ratio(Qmin, t_82, QrCmin) + + # Net Power + Pm95rated = Qm95rated * EIRm95rated + + if cooling_cop_82_min is None: + cooling_cop_82_min = seer2 / 2.0 # TODO: Replace with regression + + # 82 / 95 F + P_c.set(Qrated, t_95, Q_c.get(Qrated, t_95) / fr_u(eer2, "Btu/Wh")) + P_c.set_by_maintenance(Qrated, t_82, t_95, Pm95rated) + P_c.set(Qmin, t_82, Q_c.get(Qmin, t_82) / cooling_cop_82_min) + P_c.set_by_maintenance(Qmin, t_95, t_82, Pm95rated) + + # Tmin + Q_c.add_temperature(t_60, extrapolation_limit=0.5) + P_c.add_temperature(t_60, extrapolation_limit=0.5) + + # Tmax + Q_c.add_temperature(max_cooling_temperature) + P_c.add_temperature(max_cooling_temperature) + + # HEATING + + t_min = min_heating_temperature + t_5 = fr_u(5, "degF") + t_17 = fr_u(17, "degF") + t_47 = fr_u(47, "degF") + + t_h = [ + t_17, + t_47, + ] + + QrHmin = 0.712 # Converted from gross 0.72 + EIRrHmin = 0.850 # Converted from gross 0.87 + EIRm17rated = calc_biquad(NRELDXModel.HEATING_EIR_FT_COEFFICIENTS, 70.0, 17.0) + + if heating_capacity_17 is not None: + Qm17rated = heating_capacity_17 / heating_capacity_47 + else: + Qm17rated = 0.626 # Based on AHRI directory units believed to be single speed (4/4/24) TODO: Switch to Cutler curve + + Q_h = TemperatureSpeedHeatingPerformanceTable(t_h, 2) + P_h = TemperatureSpeedHeatingPerformanceTable(t_h, 2) + + # Net Total Capacity + + # 47 F + Q_h.set(Qrated, t_47, heating_capacity_47) + Q_h.set_by_ratio(Qmin, t_47, QrHmin) + + # 17 F + Q_h.set_by_maintenance(Qrated, t_17, t_47, Qm17rated) + Q_h.set_by_ratio(Qmin, t_17, QrHmin) + + # 5 F + Q_h.add_temperature(t_5, True) + + # Tmin + Q_h.add_temperature(t_min, True) + + # Tmax + Q_h.add_temperature(t_60, False) + + # Net Power + if heating_cop_47 is None: + heating_cop_47 = 2.837 + 0.066 * hspf2 # TODO: Replace with inverse correlation + + Pm17rated = Qm17rated * EIRm17rated + PrHmin = QrHmin * EIRrHmin + + # 47 F + P_h.set(Qrated, t_47, Q_h.get(Qrated, t_47) / heating_cop_47) + P_h.set_by_ratio(Qmin, t_47, PrHmin) + + # 17 F + P_h.set_by_maintenance(Qrated, t_17, t_47, Pm17rated) + P_h.set_by_ratio(Qmin, t_17, PrHmin) + + # 5 F + P_h.add_temperature(t_5) + + # Tmin + P_h.add_temperature(t_min) + + # Tmax + P_h.add_temperature(t_60, extrapolate=False) + + Q_c.set_interpolator() + P_c.set_interpolator() + Q_h.set_interpolator() + P_h.set_interpolator() + + return TemperatureSpeedPerformance(Q_c, P_c, Q_h, P_h) diff --git a/resdx/models/unified_resnet.py b/resdx/models/unified_resnet.py index 5d699db..ede76f7 100644 --- a/resdx/models/unified_resnet.py +++ b/resdx/models/unified_resnet.py @@ -9,7 +9,12 @@ from .title24 import Title24DXModel from .carrier_defrost_model import CarrierDefrostModel from ..fan import RESNETPSCFan, RESNETBPMFan -from .neep_data import NEEPPerformance, make_neep_statistical_model_data +from .tabular_data import ( + TemperatureSpeedPerformance, + make_neep_statistical_model_data, + make_single_speed_model_data, + make_two_speed_model_data, +) from ..enums import StagingType from ..conditions import CoolingConditions, HeatingConditions from ..psychrometrics import PsychState @@ -25,27 +30,26 @@ class RESNETDXModel(DXModel): def __init__(self) -> None: super().__init__() self.allowed_kwargs += [ - "neep_data", "rated_net_heating_capacity_17", + "rated_net_total_cooling_cop_82_min", "min_net_total_cooling_capacity_ratio_95", - "min_net_total_cooling_eir_ratio_82", - "neep_data", + "tabular_data", "motor_type", ] - self.net_neep_data: Union[NEEPPerformance, None] = None - self.gross_neep_data: Union[NEEPPerformance, None] = None + self.net_tabular_data: Union[TemperatureSpeedPerformance, None] = None + self.gross_tabular_data: Union[TemperatureSpeedPerformance, None] = None self.motor_type: Union[FanMotorType, None] = None self.rated_net_heating_capacity_17: Union[float, None] = None self.min_net_total_cooling_capacity_ratio_95: Union[float, None] = None - self.min_net_total_cooling_eir_ratio_82: Union[float, None] = None + self.rated_net_total_cooling_cop_82_min: Union[float, None] = None - self.neep_cooling_speed_map: Dict[int, float] - self.neep_heating_speed_map: Dict[int, float] - self.inverse_neep_cooling_speed_map: Dict[int, int] - self.inverse_neep_heating_speed_map: Dict[int, int] + self.tabular_cooling_speed_map: Dict[int, float] + self.tabular_heating_speed_map: Dict[int, float] + self.inverse_tabular_cooling_speed_map: Dict[int, int] + self.inverse_tabular_heating_speed_map: Dict[int, int] def process_kwargs(self) -> None: - self.net_neep_data = self.get_kwarg_value("neep_data") + self.net_tabular_data = self.get_kwarg_value("tabular_data") self.motor_type = self.get_kwarg_value("motor_type") self.rated_net_heating_capacity_17 = self.get_kwarg_value( "rated_net_heating_capacity_17" @@ -56,12 +60,15 @@ def process_kwargs(self) -> None: self.min_net_total_cooling_eir_ratio_82 = self.get_kwarg_value( "min_net_total_cooling_eir_ratio_82" ) + self.rated_net_total_cooling_cop_82_min = self.get_kwarg_value( + "rated_net_total_cooling_cop_82_min" + ) # Power and capacity def gross_cooling_power(self, conditions: CoolingConditions): - if self.net_neep_data is not None: - return self.gross_neep_data.cooling_power( - self.neep_cooling_speed_map[conditions.compressor_speed], + if self.net_tabular_data is not None: + return self.gross_tabular_data.cooling_power( + self.tabular_cooling_speed_map[conditions.compressor_speed], conditions.outdoor.db, ) * self.get_cooling_correction_factor( conditions, NRELDXModel.gross_cooling_power @@ -70,9 +77,9 @@ def gross_cooling_power(self, conditions: CoolingConditions): return NRELDXModel.gross_cooling_power(self, conditions) def gross_total_cooling_capacity(self, conditions: CoolingConditions): - if self.net_neep_data is not None: - return self.gross_neep_data.cooling_capacity( - self.neep_cooling_speed_map[conditions.compressor_speed], + if self.net_tabular_data is not None: + return self.gross_tabular_data.cooling_capacity( + self.tabular_cooling_speed_map[conditions.compressor_speed], conditions.outdoor.db, ) * self.get_cooling_correction_factor( conditions, NRELDXModel.gross_total_cooling_capacity @@ -87,9 +94,9 @@ def gross_shr(self, conditions): return Title24DXModel.gross_shr(self, conditions) def gross_steady_state_heating_capacity(self, conditions): - if self.net_neep_data is not None: - return self.gross_neep_data.heating_capacity( - self.neep_heating_speed_map[conditions.compressor_speed], + if self.net_tabular_data is not None: + return self.gross_tabular_data.heating_capacity( + self.tabular_heating_speed_map[conditions.compressor_speed], conditions.outdoor.db, ) * self.get_heating_correction_factor( conditions, NRELDXModel.gross_steady_state_heating_capacity @@ -109,9 +116,9 @@ def gross_integrated_heating_capacity(self, conditions: HeatingConditions): return self.system.gross_steady_state_heating_capacity(conditions) def gross_steady_state_heating_power(self, conditions): - if self.net_neep_data is not None: - return self.gross_neep_data.heating_power( - self.neep_heating_speed_map[conditions.compressor_speed], + if self.net_tabular_data is not None: + return self.gross_tabular_data.heating_power( + self.tabular_heating_speed_map[conditions.compressor_speed], conditions.outdoor.db, ) * self.get_heating_correction_factor( conditions, NRELDXModel.gross_steady_state_heating_power @@ -149,9 +156,9 @@ def gross_steady_state_heating_power_charge_factor(self, conditions): def set_net_capacities_and_fan( self, rated_net_total_cooling_capacity, rated_net_heating_capacity, fan ): - if self.net_neep_data is not None: + if self.net_tabular_data is not None: self.system.staging_type = StagingType( - min(self.net_neep_data.number_of_cooling_speeds, 3) + min(self.net_tabular_data.number_of_cooling_speeds, 3) ) self.system.number_of_cooling_speeds = ( self.system.number_of_heating_speeds @@ -162,45 +169,87 @@ def set_net_capacities_and_fan( ) self.system.set_placeholder_arrays() - if self.system.staging_type != StagingType.VARIABLE_SPEED: - # Set high speed capacities - self.system.rated_net_total_cooling_capacity = self.make_list( - rated_net_total_cooling_capacity - ) - - ## Heating - rated_net_heating_capacity = self.set_heating_default( - rated_net_heating_capacity, - self.system.rated_net_total_cooling_capacity[0] * 0.98 - + fr_u(180.0, "Btu/h"), - ) # From Title24 - self.system.rated_net_heating_capacity = self.make_list( - rated_net_heating_capacity - ) - - else: - if not isinstance(rated_net_heating_capacity, list): + if not isinstance(rated_net_heating_capacity, list): + if self.system.staging_type != StagingType.VARIABLE_SPEED: + self.system.rated_net_total_cooling_capacity = self.make_list( + rated_net_total_cooling_capacity + ) rated_net_heating_capacity = self.set_heating_default( rated_net_heating_capacity, - rated_net_total_cooling_capacity * 1.022 + fr_u(607.0, "Btu/h"), + self.system.rated_net_total_cooling_capacity[0] * 0.98 + + fr_u(180.0, "Btu/h"), + ) # From Title24 + self.system.rated_net_heating_capacity = self.make_list( + rated_net_heating_capacity ) + if self.system.staging_type == StagingType.SINGLE_STAGE: + if self.net_tabular_data is None: + self.net_tabular_data = make_single_speed_model_data( + cooling_capacity_95=rated_net_total_cooling_capacity, + seer2=self.system.input_seer, + eer2=self.system.input_eer, + heating_capacity_47=rated_net_heating_capacity, + heating_capacity_17=self.rated_net_heating_capacity_17, + hspf2=self.system.input_hspf, + max_cooling_temperature=self.system.cooling_off_temperature, + min_heating_temperature=self.system.heating_off_temperature, + heating_cop_47=self.system.input_rated_net_heating_cop, + cycling_degradation_coefficient=self.system.c_d_cooling, + ) + + self.system.cooling_full_load_speed = 0 + self.system.heating_full_load_speed = 0 + + self.tabular_cooling_speed_map = self.tabular_heating_speed_map = { + 0: 1.0, + } + elif self.system.staging_type == StagingType.TWO_STAGE: + if self.net_tabular_data is None: + self.net_tabular_data = make_two_speed_model_data( + cooling_capacity_95=rated_net_total_cooling_capacity, + seer2=self.system.input_seer, + eer2=self.system.input_eer, + heating_capacity_47=rated_net_heating_capacity, + heating_capacity_17=self.rated_net_heating_capacity_17, + hspf2=self.system.input_hspf, + max_cooling_temperature=self.system.cooling_off_temperature, + min_heating_temperature=self.system.heating_off_temperature, + cooling_cop_82_min=self.rated_net_total_cooling_cop_82_min, + heating_cop_47=self.system.input_rated_net_heating_cop, + ) + + self.system.cooling_full_load_speed = 0 + self.system.cooling_low_speed = 1 + + self.system.heating_full_load_speed = 0 + self.system.heating_low_speed = 1 + + self.tabular_cooling_speed_map = self.tabular_heating_speed_map = { + 0: 2.0, + 1: 1.0, + } + else: + rated_net_heating_capacity = self.set_heating_default( + rated_net_heating_capacity, + rated_net_total_cooling_capacity * 1.022 + fr_u(607.0, "Btu/h"), + ) # From NEEP Database regression if self.rated_net_heating_capacity_17 is None: self.rated_net_heating_capacity_17 = ( 0.689 * rated_net_heating_capacity ) - if self.net_neep_data is None: - self.net_neep_data = make_neep_statistical_model_data( - rated_net_total_cooling_capacity, - self.system.input_seer, - self.system.input_eer, - rated_net_heating_capacity, - self.rated_net_heating_capacity_17, - self.system.input_hspf, - self.system.cooling_off_temperature, - self.system.heating_off_temperature, - self.min_net_total_cooling_capacity_ratio_95, - self.min_net_total_cooling_eir_ratio_82, - self.system.input_rated_net_heating_cop, + if self.net_tabular_data is None: + self.net_tabular_data = make_neep_statistical_model_data( + cooling_capacity_95=rated_net_total_cooling_capacity, + seer2=self.system.input_seer, + eer2=self.system.input_eer, + heating_capacity_47=rated_net_heating_capacity, + heating_capacity_17=self.rated_net_heating_capacity_17, + hspf2=self.system.input_hspf, + max_cooling_temperature=self.system.cooling_off_temperature, + min_heating_temperature=self.system.heating_off_temperature, + cooling_capacity_ratio=self.min_net_total_cooling_capacity_ratio_95, + cooling_cop_82_min=self.rated_net_total_cooling_cop_82_min, + heating_cop_47=self.system.input_rated_net_heating_cop, ) self.system.cooling_boost_speed = 0 @@ -213,71 +262,74 @@ def set_net_capacities_and_fan( self.system.heating_intermediate_speed = 2 self.system.heating_low_speed = 3 - self.neep_cooling_speed_map = self.neep_heating_speed_map = { + self.tabular_cooling_speed_map = self.tabular_heating_speed_map = { 0: 3.0, 1: 2.0, 2: 1.5, 3: 1.0, } - self.inverse_neep_cooling_speed_map = { - v: k - for k, v in self.neep_cooling_speed_map.items() - if v.is_integer() - } - self.inverse_neep_heating_speed_map = { - v: k - for k, v in self.neep_heating_speed_map.items() - if v.is_integer() - } - - self.system.rated_net_total_cooling_capacity = [ - self.net_neep_data.cooling_capacity(self.neep_cooling_speed_map[i]) - for i in range(4) - ] - - self.system.rated_net_heating_capacity = [ - self.net_neep_data.heating_capacity(self.neep_heating_speed_map[i]) - for i in range(4) - ] + self.inverse_tabular_cooling_speed_map = { + v: k + for k, v in self.tabular_cooling_speed_map.items() + if v.is_integer() + } + self.inverse_tabular_heating_speed_map = { + v: k + for k, v in self.tabular_heating_speed_map.items() + if v.is_integer() + } + + self.system.rated_net_total_cooling_capacity = [ + self.net_tabular_data.cooling_capacity( + self.tabular_cooling_speed_map[i] + ) + for i in range(len(self.tabular_cooling_speed_map)) + ] - else: - self.system.rated_net_total_cooling_capacity = ( - rated_net_total_cooling_capacity + self.system.rated_net_heating_capacity = [ + self.net_tabular_data.heating_capacity( + self.tabular_heating_speed_map[i] ) - self.system.rated_net_heating_capacity = rated_net_heating_capacity + for i in range(len(self.tabular_heating_speed_map)) + ] + else: + self.system.rated_net_total_cooling_capacity = ( + rated_net_total_cooling_capacity + ) + self.system.rated_net_heating_capacity = rated_net_heating_capacity self.set_fan(fan) - # Setup gross neep data - if self.net_neep_data is not None: - self.gross_neep_data = deepcopy(self.net_neep_data) + # Setup gross tabular data + if self.net_tabular_data is not None: + self.gross_tabular_data = deepcopy(self.net_tabular_data) cooling_fan_powers = [] - for s_i in range(self.net_neep_data.number_of_cooling_speeds): - i = self.inverse_neep_cooling_speed_map[s_i + 1] + for s_i in range(self.net_tabular_data.number_of_cooling_speeds): + i = self.inverse_tabular_cooling_speed_map[s_i + 1] cooling_fan_powers.append(self.system.rated_cooling_fan_power[i]) heating_fan_powers = [] - for s_i in range(self.net_neep_data.number_of_heating_speeds): - i = self.inverse_neep_heating_speed_map[s_i + 1] + for s_i in range(self.net_tabular_data.number_of_heating_speeds): + i = self.inverse_tabular_heating_speed_map[s_i + 1] heating_fan_powers.append(self.system.rated_heating_fan_power[i]) - self.gross_neep_data.make_gross(cooling_fan_powers, heating_fan_powers) + self.gross_tabular_data.make_gross(cooling_fan_powers, heating_fan_powers) - for i, speed in self.neep_cooling_speed_map.items(): - self.system.rated_net_cooling_cop[i] = self.net_neep_data.cooling_cop( - speed + for i, speed in self.tabular_cooling_speed_map.items(): + self.system.rated_net_cooling_cop[i] = ( + self.net_tabular_data.cooling_cop(speed) ) self.system.rated_gross_cooling_cop[i] = ( - self.gross_neep_data.cooling_cop(speed) + self.gross_tabular_data.cooling_cop(speed) ) - for i, speed in self.neep_heating_speed_map.items(): - self.system.rated_net_heating_cop[i] = self.net_neep_data.heating_cop( - speed + for i, speed in self.tabular_heating_speed_map.items(): + self.system.rated_net_heating_cop[i] = ( + self.net_tabular_data.heating_cop(speed) ) self.system.rated_gross_heating_cop[i] = ( - self.gross_neep_data.heating_cop(speed) + self.gross_tabular_data.heating_cop(speed) ) # setup lower speed net capacities if they aren't provided @@ -292,25 +344,25 @@ def set_c_d_heating(self, input): self.system.c_d_heating = self.set_heating_default(input, 0.15) def set_rated_net_cooling_cop(self, input): - if self.net_neep_data is not None: + if self.net_tabular_data is not None: pass else: NRELDXModel.set_rated_net_cooling_cop(self, input) def set_rated_gross_cooling_cop(self, input): - if self.net_neep_data is not None: + if self.net_tabular_data is not None: pass else: NRELDXModel.set_rated_gross_cooling_cop(self, input) def set_rated_net_heating_cop(self, input): - if self.net_neep_data is not None: + if self.net_tabular_data is not None: pass else: NRELDXModel.set_rated_net_heating_cop(self, input) def set_rated_gross_heating_cop(self, input): - if self.net_neep_data is not None: + if self.net_tabular_data is not None: pass else: NRELDXModel.set_rated_gross_heating_cop(self, input) diff --git a/resdx/rating_solver.py b/resdx/rating_solver.py new file mode 100644 index 0000000..20839b5 --- /dev/null +++ b/resdx/rating_solver.py @@ -0,0 +1,87 @@ +from typing import Union +from scipy import optimize + +from koozie import fr_u + +from .dx_unit import DXUnit, AHRIVersion, StagingType +from .defrost import Defrost + + +def make_rating_unit( + staging_type: StagingType, + seer: float, + hspf: float, + eer: Union[float, None] = None, + q95: float = fr_u(3.0, "ton_ref"), + q47: Union[float, None] = None, + qm17: float = 0.63, + t_min: Union[float, None] = None, + t_max: float = fr_u(125.0, "degF"), + t_defrost: float = fr_u(40.0, "degF"), + rating_standard: AHRIVersion = AHRIVersion.AHRI_210_240_2023, +) -> DXUnit: + + q47_: float + if q47 is None: + q47_ = q95 + else: + q47_ = q47 + + # Cooling COP 82 (B low) + cop_82_min = 3.0 # Arbitrary value if it's not needed + if staging_type != StagingType.SINGLE_STAGE: + cop_82_min = optimize.newton( + lambda cop_82_min_guess: DXUnit( + staging_type=staging_type, + rating_standard=rating_standard, + rated_net_total_cooling_capacity=q95, + rated_net_heating_capacity=q47, + rated_net_heating_capacity_17=q47_ * qm17, + input_seer=seer, + input_eer=eer, + input_hspf=hspf, + rated_net_total_cooling_cop_82_min=cop_82_min_guess, + cooling_off_temperature=t_max, + heating_off_temperature=t_min, + defrost=Defrost(high_temperature=t_defrost), + ).seer() + - seer, + seer / 3.0, + ) + + # Heating COP 47 (H1 Full) + cop_47 = optimize.newton( + lambda cop_47_guess: DXUnit( + staging_type=staging_type, + rating_standard=rating_standard, + rated_net_total_cooling_capacity=q95, + rated_net_heating_capacity=q47, + rated_net_heating_capacity_17=q47_ * qm17, + input_seer=seer, + input_eer=eer, + input_hspf=hspf, + rated_net_heating_cop=cop_47_guess, + rated_net_total_cooling_cop_82_min=cop_82_min, + cooling_off_temperature=t_max, + heating_off_temperature=t_min, + defrost=Defrost(high_temperature=t_defrost), + ).hspf() + - hspf, + hspf / 2.0, + ) + + return DXUnit( + staging_type=staging_type, + rating_standard=rating_standard, + rated_net_total_cooling_capacity=q95, + rated_net_heating_capacity=q47, + rated_net_heating_capacity_17=q47_ * qm17, + rated_net_heating_cop=cop_47, + rated_net_total_cooling_cop_82_min=cop_82_min, + input_seer=seer, + input_eer=eer, + input_hspf=hspf, + cooling_off_temperature=t_max, + heating_off_temperature=t_min, + defrost=Defrost(high_temperature=t_defrost), + ) diff --git a/test/test_resdx.py b/test/test_resdx.py index 99b0eff..2a05e21 100644 --- a/test/test_resdx.py +++ b/test/test_resdx.py @@ -4,6 +4,7 @@ from scipy import optimize from resdx.dx_unit import AHRIVersion +from resdx.rating_solver import make_rating_unit import numpy as np @@ -18,40 +19,20 @@ # Single speed gross COP values used for regression testing -COP_C = 4.277 -COP_H = 3.462 +COP_C = 3.312 +COP_H = 3.121 # Tests def test_1_speed_regression(): # Single speed, SEER 13, HSPF 8 - seer_1 = 13.0 - hspf_1 = 8.0 - cop_1_c, solution_1_c = optimize.newton( - lambda x: DXUnit( - rated_gross_cooling_cop=x, - input_seer=seer_1, - rating_standard=AHRIVersion.AHRI_210_240_2017, - ).seer() - - seer_1, - seer_1 / 3.33, - full_output=True, - ) - cop_1_h, solution_1_h = optimize.newton( - lambda x: DXUnit( - rated_gross_heating_cop=x, - input_hspf=hspf_1, - rating_standard=AHRIVersion.AHRI_210_240_2017, - ).hspf() - - hspf_1, - hspf_1 / 2.0, - full_output=True, - ) - dx_unit_1_speed = DXUnit( - rated_gross_cooling_cop=cop_1_c, - rated_gross_heating_cop=cop_1_h, - input_seer=seer_1, - input_hspf=hspf_1, + seer = 13.0 + hspf = 8.0 + + dx_unit_1_speed = make_rating_unit( + StagingType.SINGLE_STAGE, + seer, + hspf, rating_standard=AHRIVersion.AHRI_210_240_2017, ) @@ -66,44 +47,24 @@ def test_1_speed_regression(): assert dx_unit_1_speed.net_total_cooling_capacity() == approx( dx_unit_1_speed.rated_net_total_cooling_capacity[0], 0.01 ) - assert dx_unit_1_speed.seer() == approx(seer_1, 0.0001) - assert dx_unit_1_speed.hspf() == approx(hspf_1, 0.0001) - assert dx_unit_1_speed.rated_gross_cooling_cop[0] == approx(COP_C, 0.001) - assert dx_unit_1_speed.rated_gross_heating_cop[0] == approx(COP_H, 0.001) + assert dx_unit_1_speed.seer() == approx(seer, 0.0001) + assert dx_unit_1_speed.hspf() == approx(hspf, 0.0001) + assert dx_unit_1_speed.rated_net_cooling_cop[0] == approx(COP_C, 0.001) + assert dx_unit_1_speed.rated_net_heating_cop[0] == approx(COP_H, 0.001) def test_1_speed_refrigerant_charge_regression(): # Single speed, SEER 13, HSPF 8 - seer_1 = 13.0 - hspf_1 = 8.0 - cop_1_c, solution_1_c = optimize.newton( - lambda x: DXUnit( - rated_gross_cooling_cop=x, - input_seer=seer_1, - rating_standard=AHRIVersion.AHRI_210_240_2017, - ).seer() - - seer_1, - seer_1 / 3.33, - full_output=True, - ) - cop_1_h, solution_1_h = optimize.newton( - lambda x: DXUnit( - rated_gross_heating_cop=x, - input_hspf=hspf_1, - rating_standard=AHRIVersion.AHRI_210_240_2017, - ).hspf() - - hspf_1, - hspf_1 / 2.0, - full_output=True, - ) - dx_unit_1_speed = DXUnit( - rated_gross_cooling_cop=cop_1_c, - rated_gross_heating_cop=cop_1_h, - input_seer=seer_1, - input_hspf=hspf_1, + seer = 13.0 + hspf = 8.0 + + dx_unit_1_speed = make_rating_unit( + StagingType.SINGLE_STAGE, + seer, + hspf, rating_standard=AHRIVersion.AHRI_210_240_2017, - refrigerant_charge_deviation=-0.25, ) + dx_unit_1_speed.refrigerant_charge_deviation = -0.25 dx_unit_1_speed.print_cooling_info() dx_unit_1_speed.print_heating_info() @@ -113,38 +74,44 @@ def test_1_speed_refrigerant_charge_regression(): def test_1_speed_2023_regression(): # Single speed, SEER 13, HSPF 8 - seer_1 = 13.0 - hspf_1 = 8.0 - dx_unit_1_speed = DXUnit( - rated_gross_cooling_cop=COP_C, - rated_gross_heating_cop=COP_H, - input_seer=seer_1, - input_hspf=hspf_1, + seer = 13.0 + hspf = 8.0 + + dx_unit_1_speed = make_rating_unit( + StagingType.SINGLE_STAGE, + seer, + hspf, + rating_standard=AHRIVersion.AHRI_210_240_2017, ) + dx_unit_1_speed.set_rating_standard(AHRIVersion.AHRI_210_240_2023) + dx_unit_1_speed.print_cooling_info() dx_unit_1_speed.print_heating_info() assert dx_unit_1_speed.seer() == approx(12.999, 0.001) # SEER2 - assert dx_unit_1_speed.hspf() == approx(7.197, 0.001) # HSPF2 + assert dx_unit_1_speed.hspf() == approx(7.172, 0.001) # HSPF2 def test_1_speed_rating_version(): # Single speed, SEER 13, HSPF 8 seer_1 = 13.0 hspf_1 = 8.0 + eer_1 = seer_1 / 1.2 dx_unit_2017 = DXUnit( - rated_gross_cooling_cop=COP_C, - rated_gross_heating_cop=COP_H, + rated_net_cooling_cop=COP_C, + rated_net_heating_cop=COP_H, rating_standard=AHRIVersion.AHRI_210_240_2017, input_seer=seer_1, input_hspf=hspf_1, + input_eer=eer_1, ) dx_unit_2023 = DXUnit( - rated_gross_cooling_cop=COP_C, - rated_gross_heating_cop=COP_H, + rated_net_cooling_cop=COP_C, + rated_net_heating_cop=COP_H, rating_standard=AHRIVersion.AHRI_210_240_2023, input_seer=seer_1, input_hspf=hspf_1, + input_eer=eer_1, ) assert dx_unit_2017.rated_cooling_airflow[0] == approx( @@ -158,35 +125,11 @@ def test_2_speed_regression(): # Two speed, SEER 17, HSPF 10 seer_2 = 17.0 hspf_2 = 10.0 - cop_2_c, solution_2_c = optimize.newton( - lambda x: DXUnit( - staging_type=resdx.StagingType.TWO_STAGE, - rated_gross_cooling_cop=x, - input_seer=seer_2, - rating_standard=AHRIVersion.AHRI_210_240_2017, - ).seer() - - seer_2, - seer_2 / 3.33, - full_output=True, - ) - cop_2_h, solution_2_h = optimize.newton( - lambda x: DXUnit( - staging_type=resdx.StagingType.TWO_STAGE, - rated_gross_heating_cop=x, - input_hspf=hspf_2, - rating_standard=AHRIVersion.AHRI_210_240_2017, - ).hspf() - - hspf_2, - hspf_2 / 2.0, - full_output=True, - ) - dx_unit_2_speed = DXUnit( - staging_type=resdx.StagingType.TWO_STAGE, - rated_gross_cooling_cop=cop_2_c, - rated_gross_heating_cop=cop_2_h, - input_seer=seer_2, - input_hspf=hspf_2, + dx_unit_2_speed = make_rating_unit( + resdx.StagingType.TWO_STAGE, + seer_2, + hspf_2, rating_standard=AHRIVersion.AHRI_210_240_2017, ) @@ -198,11 +141,11 @@ def test_2_speed_regression(): dx_unit_2_speed.rated_gross_total_cooling_capacity[0], 0.01 ) assert dx_unit_2_speed.seer() == approx(seer_2, 0.01) - assert dx_unit_2_speed.rated_gross_cooling_cop[0] == approx(4.343, 0.001) - assert dx_unit_2_speed.rated_gross_cooling_cop[1] == approx(4.772, 0.001) + assert dx_unit_2_speed.rated_net_cooling_cop[0] == approx(3.980, 0.001) + assert dx_unit_2_speed.rated_net_cooling_cop[1] == approx(4.284, 0.001) assert dx_unit_2_speed.hspf() == approx(hspf_2, 0.01) - assert dx_unit_2_speed.rated_gross_heating_cop[0] == approx(3.61, 0.001) - assert dx_unit_2_speed.rated_gross_heating_cop[1] == approx(4.146, 0.001) + assert dx_unit_2_speed.rated_net_heating_cop[0] == approx(3.424, 0.001) + assert dx_unit_2_speed.rated_net_heating_cop[1] == approx(4.029, 0.001) def test_neep_statistical_vchp_regression(): @@ -263,7 +206,7 @@ def test_neep_vchp_regression(): ] vchp_unit = resdx.DXUnit( - neep_data=resdx.models.neep_data.make_neep_model_data( + tabular_data=resdx.models.tabular_data.make_neep_model_data( cooling_capacities, cooling_powers, heating_capacities, heating_powers ), rating_standard=AHRIVersion.AHRI_210_240_2017, @@ -297,19 +240,12 @@ def test_plot(): # Single speed, SEER 13, HSPF 8 seer_1 = 13.0 hspf_1 = 8.0 - cop_1_c, solution_1_c = optimize.newton( + eer_1 = seer_1 / 1.2 + cop_1_h, _ = optimize.newton( lambda x: DXUnit( - rated_gross_cooling_cop=x, + rated_net_heating_cop=x, input_seer=seer_1, - rating_standard=AHRIVersion.AHRI_210_240_2017, - ).seer() - - seer_1, - seer_1 / 3.33, - full_output=True, - ) - cop_1_h, solution_1_h = optimize.newton( - lambda x: DXUnit( - rated_gross_heating_cop=x, + input_eer=eer_1, input_hspf=hspf_1, rating_standard=AHRIVersion.AHRI_210_240_2017, ).hspf() @@ -318,9 +254,9 @@ def test_plot(): full_output=True, ) dx_unit_1_speed = DXUnit( - rated_gross_cooling_cop=cop_1_c, - rated_gross_heating_cop=cop_1_h, + rated_net_heating_cop=cop_1_h, input_seer=seer_1, + input_eer=eer_1, input_hspf=hspf_1, rating_standard=AHRIVersion.AHRI_210_240_2017, )