Skip to content

Commit

Permalink
Multi speed fan (#115)
Browse files Browse the repository at this point in the history
* multiple speed

* update 1

* update2

* added tests

* solve conflict

* fix bugs

* fix bugs

* fix bugs

* fix bugs

* Fix sphinx config.

* fix minor problems

* format

---------

Co-authored-by: Wan, Hanlong <hanlong.wan@pnnl.gov>
Co-authored-by: Lerond, Jeremy <jeremy.lerond@pnnl.gov>
  • Loading branch information
3 people authored Oct 14, 2024
1 parent 61df0f8 commit a89a11a
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 48 deletions.
15 changes: 14 additions & 1 deletion copper/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ def __init__(self, path=chiller_lib, rating_std="", export=False):
and not "degradation_coefficient" in p
and not "indoor_fan_speeds_mapping" in p
and not "indoor_fan_speeds" in p
and not "indoor_fan_curve_coef" in p
and not "indoor_fan_curve" in p
and not "indoor_fan_power_unit" in p
):
obj_args[p] = vals[p]
elif (
Expand Down Expand Up @@ -117,6 +120,9 @@ def load_obj(self, data):
and not "degradation_coefficient" in p
and not "indoor_fan_speeds_mapping" in p
and not "indoor_fan_speeds" in p
and not "indoor_fan_curve_coef" in p
and not "indoor_fan_curve" in p
and not "indoor_fan_power_unit" in p
):
obj_args[p] = data[p]

Expand Down Expand Up @@ -191,6 +197,8 @@ def find_set_of_curves_from_lib(self, filters=[], part_eff_flag=False):
"part_eff_ref_std",
"indoor_fan_speeds_mapping",
"indoor_fan_speeds",
"indoor_fan_curve_coef",
"indoor_fan_power_unit",
]

# Set the equipment properties
Expand All @@ -204,7 +212,12 @@ def find_set_of_curves_from_lib(self, filters=[], part_eff_flag=False):
elif p in prop_to_default:
obj_args[p] = sign_eqp_class.parameters[p].default
elif (
"part_eff" in p or "alt" in p or "degradation_coefficient" in p
"part_eff" in p
or "alt" in p
or "degradation_coefficient" in p
or "indoor_fan_curve_coef" in p
or "indoor_fan_curve" in p
or "indoor_fan_power_unit" in p
):
pass
else:
Expand Down
98 changes: 67 additions & 31 deletions copper/unitarydirectexpansion.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,23 @@ def __init__(
"1": {
"fan_flow_fraction": 0.66,
"fan_power_fraction": 0.4,
"capacity_ratio": 0.5,
"capacity_fraction": 0.5,
},
"2": {
"fan_flow_fraction": 1.0,
"fan_power_fraction": 1.0,
"capacity_ratio": 1.0,
"capacity_fraction": 1.0,
},
},
indoor_fan_curve_coef={
"type": "cubic",
"1": 0.63 * 0.0408,
"2": 0.63 * 0.088,
"3": -0.63 * 0.0729,
"4": 0.63 * 0.9437,
},
indoor_fan_speeds=1,
indoor_fan_curve=False,
indoor_fan_power_unit="kW",
):
global log_fan
Expand All @@ -69,6 +77,7 @@ def __init__(
if indoor_fan_power == None:
# This is 400 cfm/ton and 0.365 W/cfm. Equation 11.1 from AHRI 210/240 (2024).
indoor_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"
Expand All @@ -78,6 +87,9 @@ def __init__(
unit="W",
).conversion(new_unit=indoor_fan_power_unit)
if not log_fan:
logging.info(
f"Default fan power is based on 400 cfm/ton and 0.365 kW/cfm"
)
logging.info(
f"Default fan power is based on 400 cfm/ton and 0.365 kW/cfm"
)
Expand Down Expand Up @@ -157,8 +169,9 @@ 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.indoor_fan_curve_coef = indoor_fan_curve_coef
self.indoor_fan_power_unit = indoor_fan_power_unit

self.indoor_fan_curve = indoor_fan_curve
# Define rated temperatures
# air entering drybulb, air entering wetbulb, entering condenser temperature, leaving condenser temperature
aed, self.aew, ect, lct = self.get_rated_temperatures()
Expand Down Expand Up @@ -225,43 +238,65 @@ def add_cycling_degradation_curve(self, overwrite=False, return_curve=False):
else:
self.set_of_curves.append(plf_f_plr)

def calc_fan_power(self, capacity_ratio):
# default fan curve
self.default_fan_curve = Curve(
eqp=self, c_type=self.indoor_fan_curve_coef["type"]
)
self.default_fan_curve.coeff1 = self.indoor_fan_curve_coef["1"]
self.default_fan_curve.coeff2 = self.indoor_fan_curve_coef["2"]
self.default_fan_curve.coeff3 = self.indoor_fan_curve_coef["3"]
self.default_fan_curve.coeff4 = self.indoor_fan_curve_coef["4"]

def calc_fan_power(self, capacity_fraction):
"""Calculate unitary DX equipment fan power.
:param float capacity_ratio: Ratio of actual capacity to net rated capacity
:param float capacity_fraction: Ratio of actual capacity to net rated capacity
:return: Unitary DX Equipment fan power in Watts
:rtype: float
"""
# Full flow/power
if capacity_ratio == 1 or self.indoor_fan_speeds == 1:
flow_fraction = capacity_fraction # we assume flow_fraction = 1*capacity_fraction as default
if capacity_fraction == 1 or self.indoor_fan_speeds == 1:
return self.indoor_fan_power
else:
capacity_ratios = []
fan_power_fractions = []
for speed_info in self.indoor_fan_speeds_mapping.values():
capacity_ratios.append(speed_info["capacity_ratio"])
fan_power_fractions.append(speed_info["fan_power_fraction"])
# Minimum flow/power
if capacity_ratio <= capacity_ratios[0]:
return self.indoor_fan_power * fan_power_fractions[0]
elif capacity_ratio in capacity_ratios:
return (
self.indoor_fan_power
* fan_power_fractions[capacity_ratios.index(capacity_ratio)]
)
else:
# In between-speeds: determine power by linear interpolation
for i, ratio in enumerate(capacity_ratios):
if (
ratio < capacity_ratio
and capacity_ratios[i + 1] > capacity_ratio
):
a = (fan_power_fractions[i + 1] - fan_power_fractions[i]) / (
capacity_ratios[i + 1] - capacity_ratios[i]
)
b = fan_power_fractions[i] - a * capacity_ratios[i]
return self.indoor_fan_power * (a * capacity_ratio + b)
if self.indoor_fan_curve == False:
capacity_fractions = []
fan_power_fractions = []
for speed_info in self.indoor_fan_speeds_mapping.values():
capacity_fractions.append(speed_info["capacity_fraction"])
fan_power_fractions.append(speed_info["fan_power_fraction"])
# Minimum flow/power
if capacity_fraction <= capacity_fractions[0]:
return self.indoor_fan_power * fan_power_fractions[0]
elif capacity_fraction in capacity_fractions:
return (
self.indoor_fan_power
* fan_power_fractions[
capacity_fractions.index(capacity_fraction)
]
)
else:
# In between-speeds: determine power by linear interpolation
for i, ratio in enumerate(capacity_fractions):
if (
ratio < capacity_fraction
and capacity_fractions[i + 1] > capacity_fraction
):
a = (
fan_power_fractions[i + 1] - fan_power_fractions[i]
) / (capacity_fractions[i + 1] - capacity_fractions[i])
b = fan_power_fractions[i] - a * capacity_fractions[i]
return self.indoor_fan_power * (a * capacity_fraction + b)
else: # using curve
default_min_fan_power = (
self.indoor_fan_power * 0.25
) # default min fan power
power_factor = self.default_fan_curve.evaluate(x=flow_fraction, y=0)
if self.indoor_fan_power * power_factor > default_min_fan_power:
return self.indoor_fan_power * power_factor
else:
return default_min_fan_power

def calc_rated_eff(
self, eff_type="part", unit="cop", output_report=False, alt=False
Expand Down Expand Up @@ -425,6 +460,7 @@ def ieer_to_eer(self, ieer):
:rtype: float
"""

ref_net_cap = Units(value=self.ref_net_cap, unit=self.ref_cap_unit).conversion(
new_unit="btu/h"
)
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@
# -- Options for intersphinx extension ---------------------------------------

# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/': None}
intersphinx_mapping = {'python': ('https://docs.python.org/3', None)}

# -- Options for todo extension ----------------------------------------------

Expand Down
51 changes: 36 additions & 15 deletions tests/test_unitarydirectexpansion.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,17 @@ def test_multi_speed(self):
dx_unit_two_speed = self.dx_unit_dft
dx_unit_two_speed.indoor_fan_speeds = 2
assert (
dx_unit_two_speed.calc_fan_power(capacity_ratio=0.5)
dx_unit_two_speed.calc_fan_power(capacity_fraction=0.5)
/ dx_unit_two_speed.indoor_fan_power
== 0.4
)
assert (
dx_unit_two_speed.calc_fan_power(capacity_ratio=1.0)
dx_unit_two_speed.calc_fan_power(capacity_fraction=1.0)
/ dx_unit_two_speed.indoor_fan_power
== 1.0
)
assert (
dx_unit_two_speed.calc_fan_power(capacity_ratio=0.75)
dx_unit_two_speed.calc_fan_power(capacity_fraction=0.75)
/ dx_unit_two_speed.indoor_fan_power
== 0.7
)
Expand All @@ -130,56 +130,77 @@ def test_multi_speed(self):
"1": {
"fan_flow_fraction": 0.2,
"fan_power_fraction": 0.15,
"capacity_ratio": 0.2,
"capacity_fraction": 0.2,
},
"2": {
"fan_flow_fraction": 0.45,
"fan_power_fraction": 0.4,
"capacity_ratio": 0.45,
"capacity_fraction": 0.45,
},
"3": {
"fan_flow_fraction": 0.75,
"fan_power_fraction": 0.7,
"capacity_ratio": 0.75,
"capacity_fraction": 0.75,
},
"4": {
"fan_flow_fraction": 1.0,
"fan_power_fraction": 1.0,
"capacity_ratio": 1.0,
"capacity_fraction": 1.0,
},
}
assert (
dx_unit_four_speed.calc_fan_power(capacity_ratio=0.1)
dx_unit_four_speed.calc_fan_power(capacity_fraction=0.1)
/ dx_unit_four_speed.indoor_fan_power
== 0.15
)
assert (
dx_unit_four_speed.calc_fan_power(capacity_ratio=1.0)
dx_unit_four_speed.calc_fan_power(capacity_fraction=1.0)
/ dx_unit_four_speed.indoor_fan_power
== 1.0
)
assert (
dx_unit_four_speed.calc_fan_power(capacity_ratio=0.75)
dx_unit_four_speed.calc_fan_power(capacity_fraction=0.75)
/ dx_unit_four_speed.indoor_fan_power
== 0.7
)
assert (
round(
dx_unit_four_speed.calc_fan_power(capacity_ratio=0.58)
dx_unit_four_speed.calc_fan_power(capacity_fraction=0.58)
/ dx_unit_four_speed.indoor_fan_power,
2,
)
== 0.53
)
assert (
round(
dx_unit_four_speed.calc_fan_power(capacity_ratio=0.70)
dx_unit_four_speed.calc_fan_power(capacity_fraction=0.70)
/ dx_unit_four_speed.indoor_fan_power,
2,
)
== 0.65
)

def test_multi_speed_with_curve(self):
# Two-speed fan unit
dx_unit_multi_speed = self.dx_unit_dft
dx_unit_multi_speed.indoor_fan_curve = True
dx_unit_multi_speed.indoor_fan_speeds = 2
assert (
dx_unit_multi_speed.calc_fan_power(capacity_fraction=0.5)
/ dx_unit_multi_speed.indoor_fan_power
== 0.25
)
assert (
dx_unit_multi_speed.calc_fan_power(capacity_fraction=1.0)
/ dx_unit_multi_speed.indoor_fan_power
== 1.0
)
assert (
dx_unit_multi_speed.calc_fan_power(capacity_fraction=0.75)
/ dx_unit_multi_speed.indoor_fan_power
< 0.7
)

def test_generation(self):
# Define equipment characteristics
dx_unit = self.dx_unit_dft
Expand Down Expand Up @@ -300,7 +321,7 @@ def test_NN_wght_avg(self):
condenser_type="air",
compressor_speed="constant",
ref_cap_unit="ton",
ref_net_cap=8,
ref_gross_cap=8,
full_eff=11.55,
full_eff_unit="eer",
part_eff=14.8,
Expand All @@ -312,12 +333,12 @@ def test_NN_wght_avg(self):
"1": {
"fan_flow_fraction": 0.66,
"fan_power_fraction": 0.4,
"capacity_ratio": 0.5,
"capacity_fraction": 0.5,
},
"2": {
"fan_flow_fraction": 1.0,
"fan_power_fraction": 1.0,
"capacity_ratio": 1.0,
"capacity_fraction": 1.0,
},
},
indoor_fan_power=cp.Units(value=8, unit="ton").conversion(new_unit="W")
Expand Down

0 comments on commit a89a11a

Please sign in to comment.