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/library.py b/copper/library.py index 32683cf..cbfb957 100644 --- a/copper/library.py +++ b/copper/library.py @@ -383,32 +383,42 @@ def get_best_match(self, eqp, matches): if eqp.type == "chiller": cap = val["ref_cap"] cap_unit = val["ref_cap_unit"] + ref_cap = eqp.ref_cap eff = val["full_eff"] eff_unit = matches[name]["full_eff_unit"] - if cap is not None: - # Capacity conversion - if cap_unit != eqp.ref_cap_unit: - c_unit = Units(cap, cap_unit) - cap = c_unit.conversion(eqp.ref_cap_unit) - cap_unit = eqp.ref_cap_unit - - # Efficiency conversion - if eff_unit != eqp.full_eff_unit: - c_unit = Units(eff, eff_unit) - eff = c_unit.conversion(eqp.full_eff_unit) - eff_unit = eqp.full_eff_unit - - # Compute difference - c_diff = abs((eqp.ref_cap - cap) / eqp.ref_cap) + abs( - (eqp.full_eff - eff) / eqp.full_eff - ) + elif eqp.type == "UnitaryDirectExpansion": + cap = val["ref_net_cap"] + cap_unit = val["ref_cap_unit"] + ref_cap = eqp.ref_net_cap + eff = val["full_eff"] + eff_unit = matches[name]["full_eff_unit"] + else: + raise ValueError("Equipment type not supported.") + + if cap is not None: + # Capacity conversion + if cap_unit != eqp.ref_cap_unit: + c_unit = Units(cap, cap_unit) + cap = c_unit.conversion(eqp.ref_cap_unit) + cap_unit = eqp.ref_cap_unit + + # Efficiency conversion + if eff_unit != eqp.full_eff_unit: + c_unit = Units(eff, eff_unit) + eff = c_unit.conversion(eqp.full_eff_unit) + eff_unit = eqp.full_eff_unit + + # Compute difference + c_diff = abs((ref_cap - cap) / ref_cap) + abs( + (eqp.full_eff - eff) / eqp.full_eff + ) - if c_diff < diff: - # Update lowest numeric difference - diff = c_diff + if c_diff < diff: + # Update lowest numeric difference + diff = c_diff - # Update best match - best_match = name + # Update best match + best_match = name return best_match diff --git a/copper/unitarydirectexpansion.py b/copper/unitarydirectexpansion.py index dab9465..dd7b588 100644 --- a/copper/unitarydirectexpansion.py +++ b/copper/unitarydirectexpansion.py @@ -76,7 +76,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" @@ -84,16 +84,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: @@ -101,7 +103,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 @@ -118,16 +120,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": @@ -199,7 +201,23 @@ def __init__( # Cycling degradation self.degradation_coefficient = degradation_coefficient - if not "plf_f_plr" in self.get_dx_curves().keys(): + 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. + + :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" @@ -209,7 +227,10 @@ def __init__( 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) # default fan curve self.default_fan_curve = Curve( @@ -221,6 +242,13 @@ def __init__( self.default_fan_curve.coeff4 = self.indoor_fan_curve_coef["4"] def calc_fan_power(self, capacity_ratio): + """Calculate unitary DX equipment fan power. + + :param float capacity_ratio: Ratio of actual capacity to net rated capacity + :return: Unitary DX Equipment fan power in Watts + :rtype: float + + """ # Full flow/power flow_fraction = ( capacity_ratio # we assume flow_fraction = 1*capacity_ratio as default @@ -287,13 +315,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"] - plf_f_plr = curves["plf_f_plr"] - - # Calculate capacity and efficiency degredation as a function of flow fraction + 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"] + + # 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) @@ -346,8 +377,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 = ( @@ -378,7 +409,7 @@ def calc_rated_eff( else 1.0 ) - # Cycling degredation + # Cycling degradation degradation_coeff = 1 / plf_f_plr.evaluate(load_factor, 1) # Power @@ -421,7 +452,9 @@ def ieer_to_eer(self, ieer): """ - ref_net_cap = self.ref_net_cap + ref_net_cap = Units(value=self.ref_net_cap, unit=self.ref_cap_unit).conversion( + new_unit="btu/h" + ) eer = ( 9.886 @@ -443,17 +476,30 @@ 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): + """Function to get the sort from the library based on chiller filters. + + :param copper.library.Library lib: Chiller library object + :param list filters: List of tuples containing the relevant filter keys and values + :return: List of set of curves object corresponding to seed curves + :rtype: list + + """ + sets = lib.find_set_of_curves_from_lib(filters=filters, part_eff_flag=True) + assert sets is not None + return sets + def get_rated_temperatures(self, alt=False): """Get unitary DX equipment rated temperatures. @@ -529,16 +575,7 @@ def get_seed_curves(self, lib=None, filters=None, csets=None): :rtype: SetsofCurves """ - assert self.compressor_type in [ - "centrifugal", - "any", - "positive_displacement", - "reciprocating", - "scroll", - "screw", - "scroll/screw", - ] - assert self.compressor_speed in ["constant", "variable", "any"] + assert self.compressor_type in ["scroll"] if lib is None or filters is None or csets is None: lib, filters = self.get_lib_and_filters() @@ -551,18 +588,15 @@ def get_seed_curves(self, lib=None, filters=None, csets=None): self.misc_attr = { "model": self.model, - "ref_net_cap": self.net_cap, + "ref_net_cap": self.ref_net_cap, "ref_gross_cap": self.ref_gross_cap, "full_eff": full_eff_cop, "part_eff": part_eff_cop, - "ref_eff_unit": "", + "ref_eff_unit": self.full_eff_unit, "compressor_type": self.compressor_type, "condenser_type": self.condenser_type, "compressor_speed": self.compressor_speed, "sim_engine": self.sim_engine, - "min_plr": self.min_plr, - "min_unloading": self.min_unloading, - "max_plr": 1, "name": "Aggregated set of curves", "source": "Copper", } diff --git a/copper/units.py b/copper/units.py index c056985..e2ac1e6 100644 --- a/copper/units.py +++ b/copper/units.py @@ -20,6 +20,7 @@ def conversion(self, new_unit): """ ton_to_kbtu = 12 kbtu_to_kw = 3.412141633 + # Efficiencies if new_unit == "kW/ton": if self.unit == "cop": return ton_to_kbtu / (self.value * kbtu_to_kw) @@ -64,6 +65,7 @@ def conversion(self, new_unit): return self.value else: return self.value + # Capacities elif new_unit == "ton": if self.unit == "kW": return self.value * (kbtu_to_kw / ton_to_kbtu) @@ -71,6 +73,10 @@ def conversion(self, new_unit): return self.value * (kbtu_to_kw / (ton_to_kbtu * 1000)) if self.unit == "ton": return self.value + if self.unit == "kbtu/h": + return self.value / ton_to_kbtu + if self.unit == "btu/h": + return self.value / ton_to_kbtu / 1000 elif new_unit == "kW": if self.unit == "ton": return self.value / (kbtu_to_kw / ton_to_kbtu) @@ -78,6 +84,10 @@ def conversion(self, new_unit): return self.value / 1000 if self.unit == "kW": return self.value + if self.unit == "kbtu/h": + return self.value * kbtu_to_kw + if self.unit == "btu/h": + return self.value * kbtu_to_kw / 1000 elif new_unit == "W": if self.unit == "ton": return self.value / (kbtu_to_kw / (ton_to_kbtu * 1000)) @@ -85,6 +95,33 @@ def conversion(self, new_unit): return self.value * 1000 if self.unit == "W": return self.value + if self.unit == "kbtu/h": + return self.value * kbtu_to_kw * 1000 + if self.unit == "btu/h": + return self.value * kbtu_to_kw + elif new_unit == "kbtu/h": + if self.unit == "ton": + return self.value * ton_to_kbtu + if self.unit == "kW": + return self.value / kbtu_to_kw + if self.unit == "W": + return self.value / (kbtu_to_kw * 1000) + if self.unit == "kbtu/h": + return self.value + if self.unit == "btu/h": + return self.value / 1000 + elif new_unit == "btu/h": + if self.unit == "ton": + return self.value * ton_to_kbtu * 1000 + if self.unit == "kW": + return self.value * 1000 / kbtu_to_kw + if self.unit == "W": + return self.value * 1000 / (kbtu_to_kw * 1000) + if self.unit == "kbtu/h": + return self.value + if self.unit == "btu/h": + return self.value + # Temperatures elif new_unit == "degC": if self.unit == "degF": return (self.value - 32) * 5 / 9 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 fe696df..6ac2e83 100644 --- a/tests/test_unitarydirectexpansion.py +++ b/tests/test_unitarydirectexpansion.py @@ -227,6 +227,34 @@ def test_generation(self): # Check that all curves have been generated assert len(set_of_curves) == 5 + def test_generation_best_match(self): + # Define equipment characteristics + 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", + ) + # Generate the curves + set_of_curves = dx.generate_set_of_curves( + tol=0.05, + verbose=True, + method="best_match", + num_nearest_neighbors=5, + random_seed=1, + vars=["eir-f-t"], + ) + + # Check that all curves have been generated + assert len(set_of_curves) == 5 + def test_lib_default_props_dx(self): assert ( self.lib.find_set_of_curves_from_lib()[0].eqp.__dict__["part_eff_ref_std"] @@ -280,3 +308,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