From 587b49bb5ec561cec6c4d540b8041f7d77f4ae08 Mon Sep 17 00:00:00 2001 From: "Jeremy L." <5149279+lymereJ@users.noreply.github.com> Date: Mon, 23 Sep 2024 13:36:57 -0700 Subject: [PATCH] Misc. changes for code consistency (#117) * Changes for consistency. * Additional changes for consistency and add unit test for nearest neigh. * Fix typo and add asserts. --- copper/chiller.py | 12 +- copper/curves.py | 16 +- .../data/unitarydirectexpansion_curves.json | 70 ++++----- copper/generator.py | 139 ++++++++++-------- copper/unitarydirectexpansion.py | 81 ++++++---- tests/test_curves.py | 2 +- tests/test_logging.py | 5 +- tests/test_unitarydirectexpansion.py | 59 ++++++++ 8 files changed, 243 insertions(+), 141 deletions(-) diff --git a/copper/chiller.py b/copper/chiller.py index 0149d20..79d2149 100644 --- a/copper/chiller.py +++ b/copper/chiller.py @@ -159,8 +159,8 @@ def get_ref_cond_flow_rate(self): # Retrieve curves curves = self.get_chiller_curves() - cap_f_t = curves["cap_f_t"] - eir_f_t = curves["eir_f_t"] + cap_f_t = curves["cap-f-t"] + eir_f_t = curves["eir-f-t"] eir_f_plr = curves["eir_f_plr"] cap_f_lwt_lct_rated = cap_f_t.evaluate(self.ref_lwt, self.ref_lct) @@ -255,8 +255,8 @@ def calc_rated_eff(self, eff_type, unit="kW/ton", output_report=False, alt=False # Retrieve curves curves = self.get_chiller_curves() - cap_f_t = curves["cap_f_t"] - eir_f_t = curves["eir_f_t"] + cap_f_t = curves["cap-f-t"] + eir_f_t = curves["eir-f-t"] eir_f_plr = curves["eir_f_plr"] try: @@ -413,9 +413,9 @@ def get_chiller_curves(self): curves = {} for curve in self.set_of_curves: if curve.out_var == "cap-f-t": - curves["cap_f_t"] = curve + curves["cap-f-t"] = curve elif curve.out_var == "eir-f-t": - curves["eir_f_t"] = curve + curves["eir-f-t"] = curve else: curves["eir_f_plr"] = curve diff --git a/copper/curves.py b/copper/curves.py index f507889..41907bf 100644 --- a/copper/curves.py +++ b/copper/curves.py @@ -24,12 +24,12 @@ def __init__(self, eqp, sets): self.sets_of_curves = sets def get_aggregated_set_of_curves( - self, method="weighted-average", N=None, ranges={}, misc_attr={} + self, method="weighted_average", N=None, ranges={}, misc_attr={} ): """Determine sets of curves based on aggregation. - :param str method: Type of aggregation, currently supported: 'average', 'median', 'weighted-average', and 'NN-weighted-average' as in nearest neighbor weighted average. - :param int N: Number of neighbor used to the aggregation, only used when the method is 'NN-weighted-average'. + :param str method: Type of aggregation, currently supported: 'average', 'median', 'weighted_average', and 'NN_weighted_average' as in nearest neighbor weighted average. + :param int N: Number of neighbor used to the aggregation, only used when the method is 'NN_weighted_average'. :param dict ranges: Dictionary that defines the ranges of values for each independent variable used to calculate aggregated dependent variable values. :param dict misc_attr: Dictionary that provides values for the aggregated set of curves. :return: Aggregated set of curves @@ -142,7 +142,7 @@ def get_aggregated_set_of_curves( y_s = [list(map(lambda x: sum(x) / len(x), zip(*vals)))] elif method == "median": y_s = [list(map(lambda x: statistics.median(x), zip(*vals)))] - elif method == "weighted-average": + elif method == "weighted_average": df, _ = self.nearest_neighbor_sort(target_attr=misc_attr) sorted_vals = list( map(vals.__getitem__, df.index.values) @@ -152,7 +152,7 @@ def get_aggregated_set_of_curves( map(lambda x: np.dot(df["score"].values, x), zip(*sorted_vals)) ) ] - elif method == "NN-weighted-average": + elif method == "NN_weighted_average": # first make sure that the user has specified to pick N values try: assert N is not None @@ -242,7 +242,7 @@ def nearest_neighbor_sort(self, target_attr=None, vars=[], N=None): :param dict target_attr: Target attributes we want to match :param list vars: The variables we want to use to compute our l2 score. note COP will be added - :param int N: Indicates the number of nearest neighbors to consider. N=None for weighted-average + :param int N: Indicates the number of nearest neighbors to consider. N=None for weighted_average :param pandas.DataFrame df: Pandas dataframe with selected chiller names and the associated weightings :return: Index of set_of_curve that should be the closest fit :rtype: int @@ -319,7 +319,7 @@ def normalize_vars( :param dict target_attr: Reference targets with respect to which l2 score needs to computed :param list vars: List of strings for variables we want to normalize :param list weights: Weights associated with each variable in vars - :param int N: Number of nearest neighbors. It should be none unless method is 'NN-weighted-average' + :param int N: Number of nearest neighbors. It should be none unless method is 'NN_weighted_average' :return: Dataframe with added columns with normalized variables, dict with added normalized values of var in vars, index of the best curve :rtype: list @@ -467,7 +467,7 @@ def get_data_for_plotting(self, curve, norm): return [x, y] - def plot(self, out_var=[], axes=[], norm=True, color="Black", alpha=0.3): + def plot(self, out_var=[], axes=[], norm=False, color="Black", alpha=0.3): """Plot a set of curves. :param list out_var: List of the output variables to plot, e.g. `eir-f-t`, `eir-f-plr`, `cap-f-t`. diff --git a/copper/data/unitarydirectexpansion_curves.json b/copper/data/unitarydirectexpansion_curves.json index 02f8c6d..2c5797c 100644 --- a/copper/data/unitarydirectexpansion_curves.json +++ b/copper/data/unitarydirectexpansion_curves.json @@ -18,7 +18,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 120, "max_outdoor_fan_power": 360, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -116,7 +116,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 65, "max_outdoor_fan_power": 750, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -214,7 +214,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 65, "max_outdoor_fan_power": 750, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -312,7 +312,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 65, "max_outdoor_fan_power": 750, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -410,7 +410,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 65, "max_outdoor_fan_power": 450, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -508,7 +508,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 249, "max_outdoor_fan_power": 249, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -606,7 +606,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 249, "max_outdoor_fan_power": 249, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -704,7 +704,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 249, "max_outdoor_fan_power": 249, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -802,7 +802,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 372, "max_outdoor_fan_power": 372, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -900,7 +900,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 372, "max_outdoor_fan_power": 372, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -998,7 +998,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -1096,7 +1096,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -1194,7 +1194,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -1292,7 +1292,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -1390,7 +1390,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -1488,7 +1488,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -1586,7 +1586,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -1684,7 +1684,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -1782,7 +1782,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -1880,7 +1880,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -1978,7 +1978,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -2076,7 +2076,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -2174,7 +2174,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -2272,7 +2272,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -2370,7 +2370,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -2468,7 +2468,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -2566,7 +2566,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -2664,7 +2664,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -2762,7 +2762,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -2860,7 +2860,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -2958,7 +2958,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -3056,7 +3056,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -3154,7 +3154,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -3252,7 +3252,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", @@ -3350,7 +3350,7 @@ "part_eff_ref_std_alt": null, "min_outdoor_fan_power": 746, "max_outdoor_fan_power": 746, - "fan_power_unit": "W", + "indoor_fan_power_unit": "W", "set_of_curves": { "eir-f-t": { "out_var": "eir-f-t", diff --git a/copper/generator.py b/copper/generator.py index 6021b79..78e4155 100644 --- a/copper/generator.py +++ b/copper/generator.py @@ -85,7 +85,7 @@ def generate_set_of_curves(self, verbose=False): self.base_curve = seed_curves.get_aggregated_set_of_curves( ranges=ranges, misc_attr=misc_attr, - method="NN-weighted-average", + method="NN_weighted_average", N=self.num_nearest_neighbors, ) self.base_curves = [self.base_curve] @@ -94,16 +94,22 @@ def generate_set_of_curves(self, verbose=False): ) elif self.method == "weighted_average": self.base_curve = seed_curves.get_aggregated_set_of_curves( - ranges=ranges, misc_attr=misc_attr, method="weighted-average" + ranges=ranges, misc_attr=misc_attr, method="weighted_average" ) self.base_curves = [self.base_curve] self.df, _ = seed_curves.nearest_neighbor_sort( target_attr=misc_attr ) else: - self.base_curves = None - self.df = None - + logging.error( + f"{self.method} is not a valid aggregation method. Choices are `best_match`, `nearest_neighbor`, and `weighted_average`." + ) + raise ValueError("Generator failed.") + if len(self.base_curves) == 0: + logging.error( + "The base set of curves needed by the generator could not be generated. Please check input and library entries." + ) + raise ValueError("Generator failed.") self.set_of_base_curves = self.base_curves[0] self.set_of_base_curves.eqp = self.equipment self.set_of_base_curves.eqp.set_of_curves = self.set_of_base_curves.curves @@ -166,26 +172,20 @@ def run_ga(self, curves, verbose=False): ) else: full_rating_alt = "n/a" + part_eff = round( + self.equipment.calc_rated_eff( + eff_type="part", unit=self.equipment.part_eff_unit + ), + 4, + ) + full_eff = round( + self.equipment.calc_rated_eff( + eff_type="full", unit=self.equipment.full_eff_unit + ), + 4, + ) logging.info( - "GEN: {}, IPLV: {}, {}: {} IPLV-alt: {}, {}-alt: {}".format( - gen, - round( - self.equipment.calc_rated_eff( - eff_type="part", unit=self.equipment.part_eff_unit - ), - 4, - ), - self.equipment.part_eff_unit.upper(), - round( - self.equipment.calc_rated_eff( - eff_type="full", unit=self.equipment.full_eff_unit - ), - 4, - ), - self.equipment.part_eff_unit.upper(), - part_rating_alt, - full_rating_alt, - ) + f"GEN: {gen}, part load efficiency: {part_eff} {self.equipment.full_eff_unit.upper()}, full load efficiency: {full_eff} {self.equipment.full_eff_unit.upper()}" ) max_gen = gen @@ -197,25 +197,20 @@ def run_ga(self, curves, verbose=False): ) gen = 0 restart += 1 - + part_eff = round( + self.equipment.calc_rated_eff( + eff_type="part", unit=self.equipment.part_eff_unit + ), + 4, + ) + full_eff = round( + self.equipment.calc_rated_eff( + eff_type="full", unit=self.equipment.full_eff_unit + ), + 4, + ) logging.info( - "GEN: {}, IPLV: {}, kW/ton: {}".format( - gen, - round( - self.equipment.calc_rated_eff( - eff_type="part", - unit=self.equipment.part_eff_unit, - ), - 2, - ), - round( - self.equipment.calc_rated_eff( - eff_type="full", - unit=self.equipment.full_eff_unit, - ), - 2, - ), - ) + f"GEN: {gen}, part load efficiency: {part_eff} {self.equipment.full_eff_unit.upper()}, full load efficiency: {full_eff} {self.equipment.full_eff_unit.upper()}" ) else: logging.critical( @@ -269,21 +264,14 @@ def is_target_met(self): elif self.equipment.type == "UnitaryDirectExpansion": if self.equipment.set_of_curves != "": part_rating = self.equipment.calc_rated_eff( - eff_type="ieer", unit=self.equipment.part_eff_unit + eff_type="part", unit=self.equipment.part_eff_unit ) part_rating_alt = 0 full_rating_alt = 0 - full_rating = self.full_eff + full_rating = self.equipment.calc_rated_eff( + eff_type="full", unit=self.equipment.part_eff_unit + ) cap_rating = 0 - # full_rating = self.equipment.calc_rated_eff( - # eff_type="full", unit=self.equipment.full_eff_unit - # ) - # cap_rating = 0 - # if "cap-f-t" in self.vars: - # for c in self.equipment.set_of_curves: - # # set_of_curves - # if "cap" in c.out_var: - # cap_rating += abs(1 - c.get_out_reference(self.equipment)) else: return False else: @@ -401,11 +389,28 @@ def individual(self, curves): """ new_curves = copy.deepcopy(curves[0]) - for curve in new_curves.curves: - if len(self.vars) == 0 or curve.out_var in self.vars: + for i, curve in enumerate(new_curves.curves): + # Modify degradation coefficient instead of curve coefficients + # for equipment using part load fraction degradation curve + # such as unitary DX equipment + if curve.out_var == "plf-f-plr" and "plf-f-plr" in self.vars: + # Randomly choose another degradation coefficient (between 0 and 0.5) + self.equipment.degradation_coefficient = ( + random.randrange(25, 100, 1) / 500.0 + ) + setattr( + curve, + "coeff1", + 1 - self.equipment.degradation_coefficient, + ) + setattr( + curve, + "coeff2", + self.equipment.degradation_coefficient, + ) + elif len(self.vars) == 0 or curve.out_var in self.vars: for idx in range(1, 11): try: - # TODO: screening criteria setattr( curve, "coeff{}".format(idx), @@ -639,8 +644,26 @@ def perform_mutation(self, individual): """ new_individual = copy.deepcopy(individual) - for curve in new_individual.curves: - if len(self.vars) == 0 or curve.out_var in self.vars: + for i, curve in enumerate(new_individual.curves): + # Modify degradation coefficient instead of curve coefficients + # for equipment using part load fraction degradation curve + # such as unitary DX equipment + if curve.out_var == "plf-f-plr" and "plf-f-plr" in self.vars: + # Randomly choose another degradation coefficient (between 0.05 and 0.5) + self.equipment.degradation_coefficient = ( + random.randrange(25, 100, 1) / 500.0 + ) + setattr( + curve, + "coeff1", + 1 - self.equipment.degradation_coefficient, + ) + setattr( + curve, + "coeff2", + self.equipment.degradation_coefficient, + ) + elif len(self.vars) == 0 or curve.out_var in self.vars: idx = random.randint(1, curve.nb_coeffs()) setattr( curve, diff --git a/copper/unitarydirectexpansion.py b/copper/unitarydirectexpansion.py index 294d216..08be1ad 100644 --- a/copper/unitarydirectexpansion.py +++ b/copper/unitarydirectexpansion.py @@ -52,7 +52,7 @@ def __init__( }, }, indoor_fan_speeds=1, - fan_power_unit="kW", + indoor_fan_power_unit="kW", ): global log_fan self.type = "UnitaryDirectExpansion" @@ -68,7 +68,7 @@ def __init__( else: if indoor_fan_power == None: # This is 400 cfm/ton and 0.365 W/cfm. Equation 11.1 from AHRI 210/240 (2024). - fan_power_unit = "kW" + indoor_fan_power_unit = "kW" indoor_fan_power = Units( value=Units(value=ref_net_cap, unit=ref_cap_unit).conversion( new_unit="ton" @@ -76,16 +76,18 @@ def __init__( * 400 * 0.365, unit="W", - ).conversion(new_unit=fan_power_unit) + ).conversion(new_unit=indoor_fan_power_unit) if not log_fan: - logging.info(f"Default fan power used: {indoor_fan_power} kW") + logging.info( + f"Default fan power is based on 400 cfm/ton and 0.365 kW/cfm" + ) log_fan = True ref_gross_cap = Units( value=Units(value=ref_net_cap, unit=ref_cap_unit).conversion( - new_unit=fan_power_unit + new_unit=indoor_fan_power_unit ) + indoor_fan_power, - unit=fan_power_unit, + unit=indoor_fan_power_unit, ).conversion(ref_cap_unit) else: if ref_net_cap != None: @@ -93,7 +95,7 @@ def __init__( raise ValueError("Input must be one and only one capacity input") if indoor_fan_power == None: # This is 400 cfm/ton and 0.365 W/cfm. Equation 11.1 from AHRI 210/240 (2024). - fan_power_unit = "kW" + indoor_fan_power_unit = "kW" indoor_fan_power = Units( value=( 400 @@ -110,16 +112,16 @@ def __init__( * Units(value=1.0, unit="W").conversion(new_unit=ref_cap_unit) ), unit="W", - ).conversion(new_unit=fan_power_unit) + ).conversion(new_unit=indoor_fan_power_unit) if not log_fan: logging.info(f"Default fan power used: {indoor_fan_power} kW") log_fan = True ref_net_cap = Units( value=Units(value=ref_gross_cap, unit=ref_cap_unit).conversion( - new_unit=fan_power_unit + new_unit=indoor_fan_power_unit ) - indoor_fan_power, - unit=fan_power_unit, + unit=indoor_fan_power_unit, ).conversion(ref_cap_unit) self.ref_cap_unit = ref_cap_unit if self.ref_cap_unit != "kW": @@ -153,7 +155,7 @@ def __init__( self.indoor_fan_speeds_mapping = indoor_fan_speeds_mapping self.indoor_fan_speeds = indoor_fan_speeds self.indoor_fan_power = indoor_fan_power - self.fan_power_unit = fan_power_unit + self.indoor_fan_power_unit = indoor_fan_power_unit # Define rated temperatures # air entering drybulb, air entering wetbulb, entering condenser temperature, leaving condenser temperature @@ -190,11 +192,23 @@ def __init__( # Cycling degradation self.degradation_coefficient = degradation_coefficient - self.add_cycling_degredation_curve() + self.add_cycling_degradation_curve() + + def add_cycling_degradation_curve(self, overwrite=False, return_curve=False): + """Determine and assign a part load fraction as a function of part load ratio curve to a unitary DX equipment. - def add_cycling_degredation_curve(self): - """Determine and assign a part load fraction as a function of part load ratio curve to a unitary DX equipment.""" - if not "plf_f_plr" in self.get_dx_curves().keys(): + :param str overwrite: Flag to overwrite the existing degradation curve. Default is False. + :param str assign_curve: Add curve to equipment's. Default is True. + """ + # Remove exisiting curve if it exists + if overwrite: + for curve in self.set_of_curves: + if curve.out_var == "plf-f-plr": + self.set_of_curves.remove(curve) + break + + # Add new curve + if not "plf-f-plr" in self.get_dx_curves().keys() or overwrite: plf_f_plr = Curve(eqp=self, c_type="linear") plf_f_plr.out_var = "plf-f-plr" plf_f_plr.type = "linear" @@ -204,7 +218,10 @@ def add_cycling_degredation_curve(self): plf_f_plr.x_max = 1 plf_f_plr.out_min = 0 plf_f_plr.out_max = 1 - self.set_of_curves.append(plf_f_plr) + if return_curve: + return plf_f_plr + else: + self.set_of_curves.append(plf_f_plr) def calc_fan_power(self, capacity_ratio): """Calculate unitary DX equipment fan power. @@ -267,16 +284,16 @@ def calc_rated_eff( # Retrieve curves curves = self.get_dx_curves() - cap_f_f = curves["cap_f_ff"] - cap_f_t = curves["cap_f_t"] - eir_f_t = curves["eir_f_t"] - eir_f_f = curves["eir_f_ff"] - if not "plf_f_plr" in curves.keys(): - self.add_cycling_degredation_curve() + cap_f_f = curves["cap-f-ff"] + cap_f_t = curves["cap-f-t"] + eir_f_t = curves["eir-f-t"] + eir_f_f = curves["eir-f-ff"] + if not "plf-f-plr" in curves.keys(): + self.add_cycling_degradation_curve() curves = self.get_dx_curves() - plf_f_plr = curves["plf_f_plr"] + plf_f_plr = curves["plf-f-plr"] - # Calculate capacity and efficiency degredation as a function of flow fraction + # Calculate capacity and efficiency degradation as a function of flow fraction tot_cap_flow_mod_fac = cap_f_f.evaluate(1, 1) eir_flow_mod_fac = eir_f_f.evaluate(1, 1) @@ -329,8 +346,8 @@ def calc_rated_eff( ], outdoor_unit_inlet_air_dry_bulb_temp_reduced, ) - load_factor_gross = ( - reduced_plr[red_cap_num] / tot_cap_temp_mod_fac + load_factor_gross = min( + 1.0, (reduced_plr[red_cap_num] / tot_cap_temp_mod_fac) ) # Load percentage * Rated gross capacity / Available gross capacity indoor_fan_power = self.calc_fan_power(load_factor_gross) / 1000 net_cooling_cap_reduced = ( @@ -361,7 +378,7 @@ def calc_rated_eff( else 1.0 ) - # Cycling degredation + # Cycling degradation degradation_coeff = 1 / plf_f_plr.evaluate(load_factor, 1) # Power @@ -428,15 +445,15 @@ def get_dx_curves(self): curves = {} for curve in self.set_of_curves: if curve.out_var == "cap-f-t": - curves["cap_f_t"] = curve + curves["cap-f-t"] = curve elif curve.out_var == "cap-f-ff": - curves["cap_f_ff"] = curve + curves["cap-f-ff"] = curve elif curve.out_var == "eir-f-t": - curves["eir_f_t"] = curve + curves["eir-f-t"] = curve elif curve.out_var == "eir-f-ff": - curves["eir_f_ff"] = curve + curves["eir-f-ff"] = curve elif curve.out_var == "plf-f-plr": - curves["plf_f_plr"] = curve + curves["plf-f-plr"] = curve return curves def get_curves_from_lib(self, lib, filters): diff --git a/tests/test_curves.py b/tests/test_curves.py index d989d0e..3efc5f3 100644 --- a/tests/test_curves.py +++ b/tests/test_curves.py @@ -328,7 +328,7 @@ def test_flow_calcs_after_agg(self): curves = cp.SetsofCurves(sets=sets, eqp=chlr) base_curve = curves.get_aggregated_set_of_curves( - ranges=ranges, misc_attr=misc_attr, method="weighted-average", N=10 + ranges=ranges, misc_attr=misc_attr, method="weighted_average", N=10 ) base_curve.eqp = chlr diff --git a/tests/test_logging.py b/tests/test_logging.py index 75976f9..0fa0e4b 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -36,7 +36,10 @@ def test_logging(self): == "Target not met after 1 generations; Restarting the generator." ) self.assertTrue(captured[0][0].levelname == "WARNING") - self.assertTrue("GEN: 0, IPLV: 5.56, kW/ton: 5.2" in captured[0][1].msg) + self.assertTrue( + "GEN: 0, part load efficiency: 5.5583 COP, full load efficiency: 5.2 COP" + in captured[0][1].msg + ) self.assertTrue(captured[0][1].levelname == "INFO") self.assertTrue( captured[0][2].msg diff --git a/tests/test_unitarydirectexpansion.py b/tests/test_unitarydirectexpansion.py index e3843e5..614f702 100644 --- a/tests/test_unitarydirectexpansion.py +++ b/tests/test_unitarydirectexpansion.py @@ -286,3 +286,62 @@ def test_get_ranges(self): ranges = self.dx_unit_dft.get_ranges() assert isinstance(ranges, dict) assert len(ranges) == 5 + + def test_degradation(self): + self.dx_unit_dft.degradation_coefficient = 0 + self.dx_unit_dft.add_cycling_degradation_curve(overwrite=True) + assert len(self.dx_unit_dft.set_of_curves) == 5 + assert self.dx_unit_dft.get_dx_curves()["plf-f-plr"].coeff1 == 1.0 + + def test_NN_wght_avg(self): + # Define equipment + dx = cp.UnitaryDirectExpansion( + compressor_type="scroll", + condenser_type="air", + compressor_speed="constant", + ref_cap_unit="ton", + ref_gross_cap=8, + full_eff=11.55, + full_eff_unit="eer", + part_eff=14.8, + part_eff_ref_std="ahri_340/360", + model="simplified_bf", + sim_engine="energyplus", + indoor_fan_speeds=2, + indoor_fan_speeds_mapping={ + "1": { + "fan_flow_fraction": 0.66, + "fan_power_fraction": 0.4, + "capacity_fraction": 0.5, + }, + "2": { + "fan_flow_fraction": 1.0, + "fan_power_fraction": 1.0, + "capacity_fraction": 1.0, + }, + }, + indoor_fan_power=cp.Units(value=8, unit="ton").conversion(new_unit="W") + * 0.05 + / 1000, + indoor_fan_power_unit="kW", + ) + + # Generate the curves + set_of_curves = dx.generate_set_of_curves( + method="nearest_neighbor", + tol=0.005, + num_nearest_neighbors=5, + verbose=True, + vars=["eir-f-t", "plf_f_plr"], + random_seed=1, + ) + + # Check that all curves have been generated + assert len(set_of_curves) == 5 + + # Check normalization + assert round(set_of_curves[0].evaluate(19.44, 35), 2) == 1.0 + assert round(set_of_curves[1].evaluate(19.44, 35), 2) == 1.0 + assert round(set_of_curves[2].evaluate(1.0, 0), 2) == 1.0 + assert round(set_of_curves[3].evaluate(1.0, 0), 2) == 1.0 + assert round(set_of_curves[4].evaluate(1.0, 0), 2) == 1.0