OpenSimula 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opensimula/Child.py +19 -0
- opensimula/Component.py +214 -0
- opensimula/Iterative_process.py +47 -0
- opensimula/Message.py +19 -0
- opensimula/Parameter_container.py +80 -0
- opensimula/Parameters.py +755 -0
- opensimula/Project.py +674 -0
- opensimula/Simulation.py +212 -0
- opensimula/Variable.py +37 -0
- opensimula/__init__.py +8 -0
- opensimula/components/Building.py +511 -0
- opensimula/components/Building_surface.py +318 -0
- opensimula/components/Calculator.py +51 -0
- opensimula/components/Construction.py +309 -0
- opensimula/components/Day_schedule.py +64 -0
- opensimula/components/File_data.py +117 -0
- opensimula/components/File_met.py +1035 -0
- opensimula/components/Frame.py +35 -0
- opensimula/components/Glazing.py +122 -0
- opensimula/components/HVAC_DX_equipment.py +210 -0
- opensimula/components/HVAC_DX_system.py +254 -0
- opensimula/components/HVAC_MZW_system.py +554 -0
- opensimula/components/HVAC_SZW_system.py +338 -0
- opensimula/components/HVAC_coil_equipment.py +206 -0
- opensimula/components/HVAC_fan_equipment.py +44 -0
- opensimula/components/HVAC_perfect_system.py +137 -0
- opensimula/components/Material.py +14 -0
- opensimula/components/Opening.py +409 -0
- opensimula/components/Opening_type.py +90 -0
- opensimula/components/Solar_surface.py +114 -0
- opensimula/components/Space.py +388 -0
- opensimula/components/Space_type.py +81 -0
- opensimula/components/Surface.py +129 -0
- opensimula/components/Test_component.py +76 -0
- opensimula/components/Week_schedule.py +30 -0
- opensimula/components/Year_schedule.py +65 -0
- opensimula/components/__init__.py +40 -0
- opensimula/visual_3D/Environment_3D.py +257 -0
- opensimula/visual_3D/Polygon_3D.py +262 -0
- opensimula-0.5.1.dist-info/METADATA +49 -0
- opensimula-0.5.1.dist-info/RECORD +44 -0
- opensimula-0.5.1.dist-info/WHEEL +5 -0
- opensimula-0.5.1.dist-info/licenses/LICENSE +21 -0
- opensimula-0.5.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from opensimula.Component import Component
|
|
2
|
+
from opensimula.Parameters import Parameter_float, Parameter_float_list, Parameter_math_exp_list
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Frame(Component):
|
|
6
|
+
def __init__(self, name, project):
|
|
7
|
+
Component.__init__(self, name, project)
|
|
8
|
+
self.parameter("type").value = "Frame"
|
|
9
|
+
self.parameter(
|
|
10
|
+
"description").value = "Opening frame"
|
|
11
|
+
|
|
12
|
+
self.add_parameter(Parameter_float_list(
|
|
13
|
+
"solar_alpha", [0.85, 0.85], "frac", min=0, max=1))
|
|
14
|
+
self.add_parameter(Parameter_float_list(
|
|
15
|
+
"lw_epsilon", [0.9, 0.9], "frac", min=0, max=1))
|
|
16
|
+
self.add_parameter(Parameter_float(
|
|
17
|
+
"thermal_resistance", 0.2, "m²K/W", min=0))
|
|
18
|
+
|
|
19
|
+
def radiant_property(self, prop, radiation_type, side, theta=0):
|
|
20
|
+
if (radiation_type == "solar_diffuse" or radiation_type == "solar_direct"):
|
|
21
|
+
if (prop == "rho"):
|
|
22
|
+
return 1-self.parameter("solar_alpha").value[side]
|
|
23
|
+
elif (prop == "tau"):
|
|
24
|
+
return 0
|
|
25
|
+
elif (prop == "alpha"):
|
|
26
|
+
return self.parameter("solar_alpha").value[side]
|
|
27
|
+
elif (prop == "alpha_other_side"):
|
|
28
|
+
return 0
|
|
29
|
+
elif (radiation_type == "long_wave"):
|
|
30
|
+
if (prop == "rho"):
|
|
31
|
+
return 1-self.parameter("lw_epsilon").value[side]
|
|
32
|
+
elif (prop == "tau"):
|
|
33
|
+
return 0
|
|
34
|
+
elif (prop == "alpha"):
|
|
35
|
+
return self.parameter("lw_epsilon").value[side]
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from scipy.integrate import quad
|
|
3
|
+
from opensimula.Component import Component
|
|
4
|
+
from opensimula.Parameters import Parameter_float, Parameter_float_list, Parameter_math_exp, Parameter_math_exp_list
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Glazing(Component):
|
|
8
|
+
def __init__(self, name, project):
|
|
9
|
+
Component.__init__(self, name, project)
|
|
10
|
+
self.parameter("type").value = "Glazing"
|
|
11
|
+
self.parameter("description").value = "Glazing material."
|
|
12
|
+
self.add_parameter(Parameter_float(
|
|
13
|
+
"solar_tau", 0.849, "frac", min=0, max=1))
|
|
14
|
+
self.add_parameter(Parameter_float_list(
|
|
15
|
+
"solar_rho", [0.077, 0.077], "frac", min=0, max=1))
|
|
16
|
+
self.add_parameter(Parameter_float_list(
|
|
17
|
+
"g", [0.867093, 0.867093], "frac", min=0, max=1))
|
|
18
|
+
self.add_parameter(Parameter_float_list(
|
|
19
|
+
"lw_epsilon", [0.837, 0.837], "frac", min=0, max=1))
|
|
20
|
+
self.add_parameter(Parameter_float(
|
|
21
|
+
"U", 5.686, "W/m²K", min=0))
|
|
22
|
+
self.add_parameter(Parameter_math_exp(
|
|
23
|
+
"f_tau_nor", "1.3186 * cos_theta**3 - 3.5251 * cos_theta**2 + 3.2065 * cos_theta", "frac"))
|
|
24
|
+
f_rho = "1.8562 * cos_theta**3 - 4.4739 * cos_theta**2 + 3.6177 * cos_theta"
|
|
25
|
+
self.add_parameter(Parameter_math_exp_list(
|
|
26
|
+
"f_1_minus_rho_nor", [f_rho, f_rho], "frac"))
|
|
27
|
+
|
|
28
|
+
def pre_simulation(self, n_time_steps, delta_t):
|
|
29
|
+
super().pre_simulation(n_time_steps, delta_t)
|
|
30
|
+
self._calc_diffuse_properties()
|
|
31
|
+
self._calc_alpha_fractions()
|
|
32
|
+
|
|
33
|
+
def _calc_diffuse_properties(self):
|
|
34
|
+
def tau_integrand(theta):
|
|
35
|
+
tau_n = self.parameter("solar_tau").value
|
|
36
|
+
# variables dictonary
|
|
37
|
+
var_dic = {"cos_theta": math.cos(theta)}
|
|
38
|
+
f_tau = self.parameter("f_tau_nor").evaluate(var_dic)
|
|
39
|
+
tau = tau_n*f_tau
|
|
40
|
+
return 2*tau * math.sin(theta) * math.cos(theta)
|
|
41
|
+
|
|
42
|
+
def rho_0_integrand(theta):
|
|
43
|
+
rho_n = self.parameter("solar_rho").value[0]
|
|
44
|
+
# variables dictonary
|
|
45
|
+
var_dic = {"cos_theta": math.cos(theta)}
|
|
46
|
+
f_rho = self.parameter("f_1_minus_rho_nor").evaluate(0, var_dic)
|
|
47
|
+
rho = 1-(1-rho_n)*f_rho
|
|
48
|
+
return 2*rho * math.sin(theta) * math.cos(theta)
|
|
49
|
+
|
|
50
|
+
def rho_1_integrand(theta):
|
|
51
|
+
rho_n = self.parameter("solar_rho").value[1]
|
|
52
|
+
# variables dictonary
|
|
53
|
+
var_dic = {"cos_theta": math.cos(theta)}
|
|
54
|
+
f_rho = self.parameter("f_1_minus_rho_nor").evaluate(1, var_dic)
|
|
55
|
+
rho = 1-(1-rho_n)*f_rho
|
|
56
|
+
return 2*rho * math.sin(theta) * math.cos(theta)
|
|
57
|
+
|
|
58
|
+
tau = quad(tau_integrand, 0, math.pi/2)[0]
|
|
59
|
+
rho_0 = quad(rho_0_integrand, 0, math.pi/2)[0]
|
|
60
|
+
rho_1 = quad(rho_1_integrand, 0, math.pi/2)[0]
|
|
61
|
+
self.tau_solar_diffuse = tau
|
|
62
|
+
self.rho_solar_diffuse = [rho_0, rho_1]
|
|
63
|
+
self.alpha_solar_diffuse = [1-rho_0-tau, 1-rho_1-tau]
|
|
64
|
+
|
|
65
|
+
def _calc_alpha_fractions(self):
|
|
66
|
+
h_CR0 = 25
|
|
67
|
+
h_CR1 = 3.6 + 4.1/0.837 * \
|
|
68
|
+
self.parameter("lw_epsilon").value[1] # UNE-EN 410:2011
|
|
69
|
+
tau = self.parameter("solar_tau").value
|
|
70
|
+
U = self.parameter("U").value
|
|
71
|
+
if (U > 5): # simple glazing calculations are inestable
|
|
72
|
+
self.alpha_own_side_fraction = [0.5, 0.5]
|
|
73
|
+
else:
|
|
74
|
+
alpha_1 = 1 - tau - self.parameter("solar_rho").value[0]
|
|
75
|
+
g_1 = self.parameter("g").value[0]
|
|
76
|
+
alpha_11 = (g_1+alpha_1*U/h_CR1-alpha_1-tau)/(U/h_CR0+U/h_CR1-1)
|
|
77
|
+
alpha_2 = 1 - tau - self.parameter("solar_rho").value[1]
|
|
78
|
+
g_2 = self.parameter("g").value[1]
|
|
79
|
+
alpha_22 = (g_2+alpha_2*U/h_CR1-alpha_2-tau)/(U/h_CR0+U/h_CR1-1)
|
|
80
|
+
self.alpha_own_side_fraction = [alpha_11/alpha_1, alpha_22/alpha_2]
|
|
81
|
+
|
|
82
|
+
def thermal_resistance(self):
|
|
83
|
+
return (1/self.parameter("U").value - 1/25 - 1/(3.6 + 4.1/0.837 *
|
|
84
|
+
self.parameter("lw_epsilon").value[1])) # UNE-EN 673:2011
|
|
85
|
+
|
|
86
|
+
def radiant_property(self, prop, radiation_type, side, theta=0):
|
|
87
|
+
if (radiation_type == "solar_diffuse"):
|
|
88
|
+
if (prop == "rho"):
|
|
89
|
+
return self.rho_solar_diffuse[side]
|
|
90
|
+
elif (prop == "tau"):
|
|
91
|
+
return self.tau_solar_diffuse
|
|
92
|
+
elif (prop == "alpha"):
|
|
93
|
+
return self.alpha_solar_diffuse[side]*self.alpha_own_side_fraction[side]
|
|
94
|
+
elif (prop == "alpha_other_side"):
|
|
95
|
+
return self.alpha_solar_diffuse[side]*(1-self.alpha_own_side_fraction[side])
|
|
96
|
+
elif (radiation_type == "solar_direct"):
|
|
97
|
+
# variables dictonary
|
|
98
|
+
var_dic = {"cos_theta": math.cos(theta)}
|
|
99
|
+
if (prop == "rho"):
|
|
100
|
+
rho_n = self.parameter("solar_rho").value[side]
|
|
101
|
+
f_rho = self.parameter(
|
|
102
|
+
"f_1_minus_rho_nor").evaluate(side, var_dic)
|
|
103
|
+
return 1-(1-rho_n)*f_rho
|
|
104
|
+
elif (prop == "tau"):
|
|
105
|
+
return self.parameter("solar_tau").value * self.parameter("f_tau_nor").evaluate(var_dic)
|
|
106
|
+
elif (prop == "alpha"):
|
|
107
|
+
alpha = 1 - self.radiant_property("tau", radiation_type, side, theta) - \
|
|
108
|
+
self.radiant_property("rho", radiation_type, side, theta)
|
|
109
|
+
return alpha * self.alpha_own_side_fraction[side]
|
|
110
|
+
elif (prop == "alpha_other_side"):
|
|
111
|
+
alpha = 1 - self.radiant_property("tau", radiation_type, side, theta) - \
|
|
112
|
+
self.radiant_property("rho", radiation_type, side, theta)
|
|
113
|
+
return alpha * (1-self.alpha_own_side_fraction[side])
|
|
114
|
+
elif (radiation_type == "long_wave"):
|
|
115
|
+
if (prop == "rho"):
|
|
116
|
+
return 1-self.parameter("lw_epsilon").value[side]
|
|
117
|
+
elif (prop == "tau"):
|
|
118
|
+
return 0
|
|
119
|
+
elif (prop == "alpha"):
|
|
120
|
+
return self.parameter("lw_epsilon").value[side]
|
|
121
|
+
else:
|
|
122
|
+
pass
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
from opensimula.Message import Message
|
|
2
|
+
from opensimula.Parameters import Parameter_float, Parameter_float_list, Parameter_math_exp, Parameter_options, Parameter_boolean
|
|
3
|
+
from opensimula.Component import Component
|
|
4
|
+
from scipy.optimize import fsolve
|
|
5
|
+
|
|
6
|
+
class HVAC_DX_equipment(Component):
|
|
7
|
+
def __init__(self, name, project):
|
|
8
|
+
Component.__init__(self, name, project)
|
|
9
|
+
self.parameter("type").value = "HVAC_DX_equipment"
|
|
10
|
+
self.parameter("description").value = "HVAC Direct Expansion equipment manufacturer information"
|
|
11
|
+
self.add_parameter(Parameter_float("nominal_air_flow", 1, "m³/s", min=0))
|
|
12
|
+
self.add_parameter(Parameter_float("nominal_total_cooling_capacity", 0, "W", min=0))
|
|
13
|
+
self.add_parameter(Parameter_float("nominal_sensible_cooling_capacity", 0, "W", min=0))
|
|
14
|
+
self.add_parameter(Parameter_float("nominal_cooling_power", 0, "W", min=0))
|
|
15
|
+
self.add_parameter(Parameter_float("indoor_fan_power", 0, "W", min=0))
|
|
16
|
+
self.add_parameter(Parameter_float_list("nominal_cooling_conditions", [27, 19, 35], "ºC"))
|
|
17
|
+
self.add_parameter(Parameter_math_exp("total_cooling_capacity_expression", "1", "frac"))
|
|
18
|
+
self.add_parameter(Parameter_math_exp("sensible_cooling_capacity_expression", "1", "frac"))
|
|
19
|
+
self.add_parameter(Parameter_math_exp("cooling_power_expression", "1", "frac"))
|
|
20
|
+
self.add_parameter(Parameter_math_exp("EER_expression", "1", "frac"))
|
|
21
|
+
self.add_parameter(Parameter_float("nominal_heating_capacity", 0, "W", min=0))
|
|
22
|
+
self.add_parameter(Parameter_float("nominal_heating_power", 0, "W", min=0))
|
|
23
|
+
self.add_parameter(Parameter_float_list("nominal_heating_conditions", [20, 7, 6], "ºC"))
|
|
24
|
+
self.add_parameter(Parameter_math_exp("heating_capacity_expression", "1", "frac"))
|
|
25
|
+
self.add_parameter(Parameter_math_exp("heating_power_expression", "1", "frac"))
|
|
26
|
+
self.add_parameter(Parameter_math_exp("COP_expression", "1", "frac"))
|
|
27
|
+
self.add_parameter(Parameter_options("dry_coil_model", "SENSIBLE", ["TOTAL", "SENSIBLE"]))
|
|
28
|
+
self.add_parameter(Parameter_options("indoor_fan_operation", "CONTINUOUS", ["CONTINUOUS", "CYCLING"]))
|
|
29
|
+
self.add_parameter(Parameter_boolean("power_dry_coil_correction", True))
|
|
30
|
+
self.add_parameter(Parameter_float_list("expression_max_values", [60,30,60,30,1.5,1], "-"))
|
|
31
|
+
self.add_parameter(Parameter_float_list("expression_min_values", [0,0,-30,-30,0,0], "-"))
|
|
32
|
+
|
|
33
|
+
def check(self):
|
|
34
|
+
errors = super().check()
|
|
35
|
+
# Test Cooling and Heating conditions 3 values
|
|
36
|
+
if len(self.parameter("nominal_cooling_conditions").value)!= 3:
|
|
37
|
+
msg = f"Error: {self.parameter('name').value}, nominal_cooling_conditions size must be 3"
|
|
38
|
+
errors.append(Message(msg, "ERROR"))
|
|
39
|
+
if len(self.parameter("nominal_heating_conditions").value)!= 3:
|
|
40
|
+
msg = f"Error: {self.parameter('name').value}, nominal_heating_conditions size must be 3"
|
|
41
|
+
errors.append(Message(msg, "ERROR"))
|
|
42
|
+
return errors
|
|
43
|
+
|
|
44
|
+
def get_heating_load(self,T_idb,T_iwb,T_odb,T_owb,F_air,Q_required):
|
|
45
|
+
"""
|
|
46
|
+
Q_required: Required heating load
|
|
47
|
+
Returns (Q_eq,Q_coil,f_load).
|
|
48
|
+
Q_eq: Net heat given by the equipment
|
|
49
|
+
Q_coil: Gross heat given by the coil
|
|
50
|
+
f_load: Fraction of load (0-1)
|
|
51
|
+
"""
|
|
52
|
+
capacity = self.parameter("nominal_heating_capacity").value
|
|
53
|
+
if capacity > 0:
|
|
54
|
+
# variables dictonary
|
|
55
|
+
var_dic = self._var_state_dic([T_idb, T_iwb,T_odb,T_owb,F_air,1]) #Full load
|
|
56
|
+
# Capacity
|
|
57
|
+
cap_coil = capacity * self.parameter("heating_capacity_expression").evaluate(var_dic)
|
|
58
|
+
cap_eq = cap_coil + self.get_fan_heat(1)
|
|
59
|
+
if self.get_fan_heat(0) == 0: # Q_required is equipment load
|
|
60
|
+
if Q_required > cap_eq:
|
|
61
|
+
return (cap_eq,cap_coil,1)
|
|
62
|
+
else:
|
|
63
|
+
f_load = Q_required/cap_eq
|
|
64
|
+
return (Q_required, Q_required-self.get_fan_heat(f_load), f_load)
|
|
65
|
+
else: # Q_required is coil load
|
|
66
|
+
if Q_required > cap_coil:
|
|
67
|
+
return (cap_eq,cap_coil,1)
|
|
68
|
+
else:
|
|
69
|
+
f_load = Q_required/cap_coil
|
|
70
|
+
return (Q_required+self.get_fan_heat(f_load), Q_required, f_load)
|
|
71
|
+
else:
|
|
72
|
+
return (self.get_fan_heat(0),0, 0)
|
|
73
|
+
|
|
74
|
+
def get_heating_power(self,T_idb, T_iwb,T_odb,T_owb,F_air,Q_required):
|
|
75
|
+
"""
|
|
76
|
+
Returns (power,indoor_fan_power,F_COP).
|
|
77
|
+
"""
|
|
78
|
+
cap_nom = self.parameter("nominal_heating_capacity").value
|
|
79
|
+
if cap_nom > 0:
|
|
80
|
+
Q_eq, Q_coil, F_load = self.get_heating_load(T_idb, T_iwb,T_odb,T_owb,F_air,Q_required)
|
|
81
|
+
# variables dictonary
|
|
82
|
+
var_dic = self._var_state_dic([T_idb, T_iwb,T_odb,T_owb,F_air,F_load])
|
|
83
|
+
# Compressor
|
|
84
|
+
power_full = self.parameter("nominal_heating_power").value
|
|
85
|
+
power_full = power_full * self.parameter("heating_power_expression").evaluate(var_dic)
|
|
86
|
+
capacity = Q_coil/F_load
|
|
87
|
+
COP_full = capacity/power_full
|
|
88
|
+
F_COP = self.parameter("COP_expression").evaluate(var_dic)
|
|
89
|
+
COP = COP_full * F_COP
|
|
90
|
+
if self.parameter("indoor_fan_operation").value == "CONTINUOUS":
|
|
91
|
+
fan_power = self.parameter("indoor_fan_power").value
|
|
92
|
+
elif self.parameter("indoor_fan_operation").value == "CYCLING":
|
|
93
|
+
fan_power = self.parameter("indoor_fan_power").value*F_load/F_COP
|
|
94
|
+
return (capacity*F_load/COP + fan_power, fan_power, F_COP)
|
|
95
|
+
else:
|
|
96
|
+
fan_power = self.get_fan_power(0)
|
|
97
|
+
return (fan_power,fan_power,0)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_cooling_load(self,T_idb,T_iwb,T_odb,T_owb,F_air,Q_required):
|
|
101
|
+
"""
|
|
102
|
+
Q_required: Required cooling load (negative for cooling)
|
|
103
|
+
Returns (Q_eq,Q_coil,Q_lat ,f_load).
|
|
104
|
+
Q_eq: Sensible net heat given by the equipment
|
|
105
|
+
Q_coil: Sensible gross heat given by the coil
|
|
106
|
+
Q_lat: Latent heat given by the coil
|
|
107
|
+
f_load: Fraction of load (0-1)
|
|
108
|
+
"""
|
|
109
|
+
nom_total_capacity = self.parameter("nominal_total_cooling_capacity").value
|
|
110
|
+
if nom_total_capacity > 0:
|
|
111
|
+
Q_required = -Q_required # Positive
|
|
112
|
+
# variables dictonary
|
|
113
|
+
var_dic = self._var_state_dic([T_idb, T_iwb,T_odb,T_owb,F_air,1]) #Full load
|
|
114
|
+
# Total
|
|
115
|
+
f = self.parameter("total_cooling_capacity_expression").evaluate(var_dic)
|
|
116
|
+
total_capacity = nom_total_capacity * f
|
|
117
|
+
# Sensible
|
|
118
|
+
sensible_capacity = self.parameter("nominal_sensible_cooling_capacity").value
|
|
119
|
+
f = self.parameter("sensible_cooling_capacity_expression").evaluate(var_dic)
|
|
120
|
+
sensible_capacity = sensible_capacity * f
|
|
121
|
+
if (sensible_capacity > total_capacity):
|
|
122
|
+
if self.parameter("dry_coil_model").value == "SENSIBLE":
|
|
123
|
+
total_capacity = sensible_capacity
|
|
124
|
+
elif self.parameter("dry_coil_model").value == "TOTAL":
|
|
125
|
+
sensible_capacity = total_capacity
|
|
126
|
+
cap_eq = sensible_capacity - self.get_fan_heat(1)
|
|
127
|
+
if self.get_fan_heat(0) == 0: # Q_required is equipment load
|
|
128
|
+
if Q_required > cap_eq:
|
|
129
|
+
return (cap_eq,sensible_capacity,total_capacity-sensible_capacity,1)
|
|
130
|
+
else:
|
|
131
|
+
F_load = Q_required/cap_eq
|
|
132
|
+
return (Q_required, Q_required+self.get_fan_heat(F_load),(total_capacity-sensible_capacity)*F_load , F_load)
|
|
133
|
+
else: # Q_requiered is coil load
|
|
134
|
+
if Q_required > sensible_capacity:
|
|
135
|
+
return (cap_eq,sensible_capacity,total_capacity-sensible_capacity,1)
|
|
136
|
+
else:
|
|
137
|
+
F_load = Q_required/sensible_capacity
|
|
138
|
+
return (Q_required-self.get_fan_heat(F_load), Q_required,(total_capacity-sensible_capacity)*F_load , F_load)
|
|
139
|
+
else:
|
|
140
|
+
return (-self.get_fan_heat(0), 0, 0, 0)
|
|
141
|
+
|
|
142
|
+
def get_cooling_power(self,T_idb,T_iwb,T_odb,T_owb,F_air,Q_required):
|
|
143
|
+
"""
|
|
144
|
+
Returns (power,indoor_fan_power,F_EER).
|
|
145
|
+
"""
|
|
146
|
+
Q_eq, Q_sen, Q_lat, F_load = self.get_cooling_load(T_idb,T_iwb,T_odb,T_owb,F_air,Q_required)
|
|
147
|
+
if Q_sen > 0:
|
|
148
|
+
# variables dictonary
|
|
149
|
+
var_dic = self._var_state_dic([T_idb, T_iwb,T_odb,T_owb,F_air,F_load])
|
|
150
|
+
capacity_tot = (Q_sen+Q_lat)/F_load
|
|
151
|
+
capacity_sen = Q_sen/F_load
|
|
152
|
+
power_full = self._get_correct_cooling_power(capacity_tot,capacity_sen,var_dic)
|
|
153
|
+
EER_full = capacity_tot/power_full
|
|
154
|
+
F_EER = self.parameter("EER_expression").evaluate(var_dic)
|
|
155
|
+
EER = EER_full * F_EER
|
|
156
|
+
if self.parameter("indoor_fan_operation").value == "CONTINUOUS":
|
|
157
|
+
fan_power = self.parameter("indoor_fan_power").value
|
|
158
|
+
elif self.parameter("indoor_fan_operation").value == "CYCLING":
|
|
159
|
+
fan_power = self.parameter("indoor_fan_power").value*F_load/F_EER
|
|
160
|
+
return (capacity_tot*F_load/EER + fan_power, fan_power, F_EER)
|
|
161
|
+
else:
|
|
162
|
+
fan_power = self.get_fan_power(0)
|
|
163
|
+
return (fan_power,fan_power,0)
|
|
164
|
+
|
|
165
|
+
def _get_correct_cooling_power(self,total_capacity, sensible_capacity, var_dic):
|
|
166
|
+
power = self.parameter("nominal_cooling_power").value
|
|
167
|
+
if (sensible_capacity == total_capacity and self.parameter("power_dry_coil_correction").value):
|
|
168
|
+
T_iwb_min = self._get_min_T_iwb(var_dic)
|
|
169
|
+
var_dic["T_iwb"] = T_iwb_min
|
|
170
|
+
f = self.parameter("cooling_power_expression").evaluate(var_dic)
|
|
171
|
+
return (power * f)
|
|
172
|
+
|
|
173
|
+
def _get_min_T_iwb(self,var_dic):
|
|
174
|
+
total_capacity = self.parameter("nominal_total_cooling_capacity").value
|
|
175
|
+
sensible_capacity = self.parameter("nominal_sensible_cooling_capacity").value
|
|
176
|
+
def func(T_iwb):
|
|
177
|
+
var_dic["T_iwb"] = T_iwb
|
|
178
|
+
return (sensible_capacity*self.parameter("sensible_cooling_capacity_expression").evaluate(var_dic)-
|
|
179
|
+
total_capacity*self.parameter("total_cooling_capacity_expression").evaluate(var_dic))
|
|
180
|
+
root = fsolve(func, var_dic["T_iwb"],xtol=1e-3)
|
|
181
|
+
return root[0]
|
|
182
|
+
|
|
183
|
+
def get_fan_power(self, f_load):
|
|
184
|
+
if self.parameter("indoor_fan_operation").value == "CONTINUOUS":
|
|
185
|
+
return self.parameter("indoor_fan_power").value
|
|
186
|
+
elif self.parameter("indoor_fan_operation").value == "CYCLING":
|
|
187
|
+
return self.parameter("indoor_fan_power").value * f_load
|
|
188
|
+
|
|
189
|
+
def get_fan_heat(self, f_load):
|
|
190
|
+
return self.get_fan_power(f_load)
|
|
191
|
+
|
|
192
|
+
def _var_state_dic(self, values):
|
|
193
|
+
max = self.parameter("expression_max_values").value
|
|
194
|
+
min = self.parameter("expression_min_values").value
|
|
195
|
+
for i in range(len(values)):
|
|
196
|
+
if (values[i] > max[i]):
|
|
197
|
+
values[i] = max[i]
|
|
198
|
+
elif (values[i] < min[i]):
|
|
199
|
+
values[i] = min[i]
|
|
200
|
+
return {"T_idb":values[0],
|
|
201
|
+
"T_iwb":values[1],
|
|
202
|
+
"T_odb":values[2],
|
|
203
|
+
"T_owb":values[3],
|
|
204
|
+
"F_air":values[4],
|
|
205
|
+
"F_load":values[5]}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
from opensimula.Message import Message
|
|
2
|
+
from opensimula.Parameters import Parameter_component, Parameter_float, Parameter_variable_list, Parameter_math_exp, Parameter_options
|
|
3
|
+
from opensimula.Component import Component
|
|
4
|
+
from opensimula.Variable import Variable
|
|
5
|
+
import psychrolib as sicro
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HVAC_DX_system(Component):
|
|
9
|
+
def __init__(self, name, project):
|
|
10
|
+
Component.__init__(self, name, project)
|
|
11
|
+
self.parameter("type").value = "HVAC_DX_system"
|
|
12
|
+
self.parameter("description").value = "HVAC Direct Expansion system for time simulation"
|
|
13
|
+
self.add_parameter(Parameter_component("equipment", "not_defined", ["HVAC_DX_equipment"]))
|
|
14
|
+
self.add_parameter(Parameter_component("spaces", "not_defined", ["Space"])) # Space, TODO: Add Air_distribution, Energy_load
|
|
15
|
+
self.add_parameter(Parameter_float("air_flow", 1, "m³/s", min=0))
|
|
16
|
+
self.add_parameter(Parameter_math_exp("outdoor_air_fraction", "0", "frac"))
|
|
17
|
+
self.add_parameter(Parameter_variable_list("input_variables", []))
|
|
18
|
+
self.add_parameter(Parameter_math_exp("heating_setpoint", "20", "°C"))
|
|
19
|
+
self.add_parameter(Parameter_math_exp("cooling_setpoint", "25", "°C"))
|
|
20
|
+
self.add_parameter(Parameter_math_exp("system_on_off", "1", "on/off"))
|
|
21
|
+
self.add_parameter(Parameter_options("economizer", "NO", ["NO", "TEMPERATURE","TEMPERATURE_NOT_INTEGRATED","ENTHALPY","ENTHALPY_LIMITED"]))
|
|
22
|
+
self.add_parameter(Parameter_float("economizer_DT", 0, "ºC", min=0))
|
|
23
|
+
self.add_parameter(Parameter_float("economizer_enthalpy_limit", 0, "kJ/kg", min=0))
|
|
24
|
+
|
|
25
|
+
# Variables
|
|
26
|
+
self.add_variable(Variable("state", unit="flag")) # 0: 0ff, 1: Heating, 2: Heating max cap, -1:Cooling, -2:Cooling max cap, 3: Venting
|
|
27
|
+
self.add_variable(Variable("T_OA", unit="°C"))
|
|
28
|
+
self.add_variable(Variable("T_OAwb", unit="°C"))
|
|
29
|
+
self.add_variable(Variable("T_MA", unit="°C"))
|
|
30
|
+
self.add_variable(Variable("T_MAwb", unit="°C"))
|
|
31
|
+
self.add_variable(Variable("F_air", unit="frac"))
|
|
32
|
+
self.add_variable(Variable("F_load", unit="frac"))
|
|
33
|
+
self.add_variable(Variable("outdoor_air_fraction", unit="frac"))
|
|
34
|
+
self.add_variable(Variable("m_air_flow", unit="kg/s"))
|
|
35
|
+
self.add_variable(Variable("T_SA", unit="°C"))
|
|
36
|
+
self.add_variable(Variable("w_SA", unit="g/kg"))
|
|
37
|
+
self.add_variable(Variable("Q_total", unit="W"))
|
|
38
|
+
self.add_variable(Variable("Q_sensible", unit="W"))
|
|
39
|
+
self.add_variable(Variable("Q_latent", unit="W"))
|
|
40
|
+
self.add_variable(Variable("power", unit="W"))
|
|
41
|
+
self.add_variable(Variable("indoor_fan_power", unit="W"))
|
|
42
|
+
self.add_variable(Variable("heating_setpoint", unit="°C"))
|
|
43
|
+
self.add_variable(Variable("cooling_setpoint", unit="°C"))
|
|
44
|
+
self.add_variable(Variable("EER", unit="frac"))
|
|
45
|
+
self.add_variable(Variable("COP", unit="frac"))
|
|
46
|
+
self.add_variable(Variable("efficiency_degradation", unit="frac"))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def check(self):
|
|
50
|
+
errors = super().check()
|
|
51
|
+
# Test equipment defined
|
|
52
|
+
if self.parameter("equipment").value == "not_defined":
|
|
53
|
+
msg = f"{self.parameter('name').value}, must define its equipment."
|
|
54
|
+
errors.append(Message(msg, "ERROR"))
|
|
55
|
+
# Test space defined
|
|
56
|
+
if self.parameter("spaces").value == "not_defined":
|
|
57
|
+
msg = f"{self.parameter('name').value}, must define its space."
|
|
58
|
+
errors.append(Message(msg, "ERROR"))
|
|
59
|
+
# Test file_met defined
|
|
60
|
+
if self.project().parameter("simulation_file_met").value == "not_defined":
|
|
61
|
+
msg = f"{self.parameter('name').value}, file_met must be defined in the project 'simulation_file_met'."
|
|
62
|
+
errors.append(Message(msg, "ERROR"))
|
|
63
|
+
return errors
|
|
64
|
+
|
|
65
|
+
def pre_simulation(self, n_time_steps, delta_t):
|
|
66
|
+
super().pre_simulation(n_time_steps, delta_t)
|
|
67
|
+
self._equipment = self.parameter("equipment").component
|
|
68
|
+
self._space = self.parameter("spaces").component
|
|
69
|
+
self._file_met = self.project().parameter("simulation_file_met").component
|
|
70
|
+
sicro.SetUnitSystem(sicro.SI)
|
|
71
|
+
self.props = self._sim_.props
|
|
72
|
+
self._air_flow = self.parameter("air_flow").value
|
|
73
|
+
self._f_air = self._air_flow / self._equipment.parameter("nominal_air_flow").value
|
|
74
|
+
self._f_load = 0
|
|
75
|
+
self._no_load_heat = self._equipment.get_fan_heat(0)
|
|
76
|
+
self._rho_i = self.props["RHO_A"]
|
|
77
|
+
self._economizer = self.parameter("economizer").value != "NO"
|
|
78
|
+
self._economizer_DT = self.parameter("economizer_DT").value
|
|
79
|
+
|
|
80
|
+
def pre_iteration(self, time_index, date, daylight_saving):
|
|
81
|
+
super().pre_iteration(time_index, date, daylight_saving)
|
|
82
|
+
# Outdoor air
|
|
83
|
+
self._T_odb = self._file_met.variable("temperature").values[time_index]
|
|
84
|
+
self._T_owb = self._file_met.variable("wet_bulb_temp").values[time_index]
|
|
85
|
+
self._w_o = self._file_met.variable("abs_humidity").values[time_index]
|
|
86
|
+
self.variable("T_OA").values[time_index] = self._T_odb
|
|
87
|
+
self.variable("T_OAwb").values[time_index] = self._T_owb
|
|
88
|
+
self._outdoor_rho = 1/sicro.GetMoistAirVolume(self._T_odb,self._w_o/1000,self.props["ATM_PRESSURE"])
|
|
89
|
+
# variables dictonary
|
|
90
|
+
var_dic = self.get_parameter_variable_dictionary(time_index)
|
|
91
|
+
|
|
92
|
+
# outdoor air fraction
|
|
93
|
+
self._outdoor_air_fraction = self.parameter("outdoor_air_fraction").evaluate(var_dic)
|
|
94
|
+
self._f_oa = self._outdoor_air_fraction
|
|
95
|
+
|
|
96
|
+
# setpoints
|
|
97
|
+
self._T_heat_sp = self.parameter("heating_setpoint").evaluate(var_dic)
|
|
98
|
+
self.variable("heating_setpoint").values[time_index] = self._T_heat_sp
|
|
99
|
+
self._T_cool_sp = self.parameter("cooling_setpoint").evaluate(var_dic)
|
|
100
|
+
self.variable("cooling_setpoint").values[time_index] = self._T_cool_sp
|
|
101
|
+
# on/off
|
|
102
|
+
self._on_off = self.parameter("system_on_off").evaluate(var_dic)
|
|
103
|
+
if self._on_off == 0:
|
|
104
|
+
self._state = 0
|
|
105
|
+
self.variable("state").values[time_index] = 0
|
|
106
|
+
self._on_off = False
|
|
107
|
+
else:
|
|
108
|
+
self._on_off = True
|
|
109
|
+
|
|
110
|
+
def iteration(self, time_index, date, daylight_saving, n_iter):
|
|
111
|
+
super().iteration(time_index, date, daylight_saving, n_iter)
|
|
112
|
+
space_air = {"M_a": 0, "T_a": 0, "w_a":0, "Q_s":0, "M_w":0 }
|
|
113
|
+
if self._on_off:
|
|
114
|
+
self._T_space = self._space.variable("temperature").values[time_index]
|
|
115
|
+
self._w_space = self._space.variable("abs_humidity").values[time_index]
|
|
116
|
+
# Calculate Q_required
|
|
117
|
+
self._calculate_required_Q()
|
|
118
|
+
if (self._economizer):
|
|
119
|
+
self._simulate_economizer() # Calculation of new f_oa
|
|
120
|
+
self._calculate_required_Q()
|
|
121
|
+
space_air = self._simulate_system()
|
|
122
|
+
self._space.set_control_system(space_air)
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
def _calculate_required_Q(self):
|
|
126
|
+
K_t,F_t = self._space.get_thermal_equation(False)
|
|
127
|
+
m_cp_oa = self._air_flow * self._f_oa * self._rho_i * self.props["C_PA"]
|
|
128
|
+
K_ts = K_t + m_cp_oa
|
|
129
|
+
F_ts = F_t + m_cp_oa * self._T_odb + self._no_load_heat
|
|
130
|
+
T_flo = F_ts/K_ts
|
|
131
|
+
if T_flo > self._T_cool_sp:
|
|
132
|
+
self._Q_required = K_ts * self._T_cool_sp - F_ts
|
|
133
|
+
elif T_flo < self._T_heat_sp:
|
|
134
|
+
self._Q_required = K_ts * self._T_heat_sp - F_ts
|
|
135
|
+
else:
|
|
136
|
+
self._Q_required = 0
|
|
137
|
+
if abs(self._Q_required) < 0.0001: # Problems with convergence
|
|
138
|
+
self._Q_required = 0
|
|
139
|
+
|
|
140
|
+
def _simulate_economizer(self):
|
|
141
|
+
if (self.parameter("economizer").value == "TEMPERATURE" or self.parameter("economizer").value == "TEMPERATURE_NOT_INTEGRATED"):
|
|
142
|
+
on_economizer = self._T_odb < self._T_space-self._economizer_DT
|
|
143
|
+
elif (self.parameter("economizer").value == "ENTHALPY"):
|
|
144
|
+
h_odb = sicro.GetMoistAirEnthalpy(self._T_odb,self._w_o/1000)
|
|
145
|
+
h_space = sicro.GetMoistAirEnthalpy(self._T_space,self._w_space/1000)
|
|
146
|
+
on_economizer = h_odb < h_space
|
|
147
|
+
elif (self.parameter("economizer").value == "ENTHALPY_LIMITED"):
|
|
148
|
+
h_odb = sicro.GetMoistAirEnthalpy(self._T_odb,self._w_o/1000)
|
|
149
|
+
on_economizer = h_odb < self.parameter("economizer_enthalpy_limit").value * 1000
|
|
150
|
+
|
|
151
|
+
if (on_economizer):
|
|
152
|
+
if (self._Q_required < 0):
|
|
153
|
+
mrhocp = self._air_flow * self.props["C_PA"]* self._outdoor_rho
|
|
154
|
+
Q_rest_ae = mrhocp * (1-self._f_oa) * (self._T_odb - self._T_space)
|
|
155
|
+
if Q_rest_ae < self._Q_required:
|
|
156
|
+
self._f_oa += self._Q_required/(mrhocp * (self._T_odb-self._T_space))
|
|
157
|
+
self._Q_required = 0
|
|
158
|
+
else:
|
|
159
|
+
if (self.parameter("economizer").value == "TEMPERATURE_NOT_INTEGRATED"):
|
|
160
|
+
self._f_oa = self._outdoor_air_fraction
|
|
161
|
+
else:
|
|
162
|
+
self._f_oa = 1
|
|
163
|
+
elif (self._Q_required > 0): # Heating
|
|
164
|
+
self._f_oa = self._outdoor_air_fraction
|
|
165
|
+
else:
|
|
166
|
+
self._f_oa = self._outdoor_air_fraction
|
|
167
|
+
|
|
168
|
+
def _simulate_system(self):
|
|
169
|
+
# Venting
|
|
170
|
+
state = 3
|
|
171
|
+
f_load = 0
|
|
172
|
+
Q_coil = 0
|
|
173
|
+
Q_eq = self._equipment.get_fan_heat(0)
|
|
174
|
+
M_w = 0
|
|
175
|
+
|
|
176
|
+
self._T_idb, self._w_i, self._T_iwb = self._mix_air(self._f_oa, self._T_odb, self._w_o, self._T_space, self._w_space)
|
|
177
|
+
self._rho_i = 1/sicro.GetMoistAirVolume(self._T_idb,self._w_i/1000,self.props["ATM_PRESSURE"])
|
|
178
|
+
|
|
179
|
+
if self._Q_required > 0: # Heating
|
|
180
|
+
Q_eq,Q_coil,f_load = self._equipment.get_heating_load(self._T_idb, self._T_iwb, self._T_odb, self._T_owb,self._f_air,self._Q_required)
|
|
181
|
+
if f_load == 1:
|
|
182
|
+
state = 1
|
|
183
|
+
else:
|
|
184
|
+
state = 2
|
|
185
|
+
elif self._Q_required < 0: # Cooling
|
|
186
|
+
Q_eq, Q_coil, Q_lat,f_load = self._equipment.get_cooling_load(self._T_idb, self._T_iwb, self._T_odb,self._T_owb,self._f_air,self._Q_required)
|
|
187
|
+
if f_load == 1:
|
|
188
|
+
state = -2
|
|
189
|
+
else:
|
|
190
|
+
state = -1
|
|
191
|
+
M_w = -(Q_lat) / self.props["LAMBDA"]
|
|
192
|
+
Q_coil = -Q_coil
|
|
193
|
+
Q_eq = -Q_eq
|
|
194
|
+
|
|
195
|
+
self._state = state
|
|
196
|
+
self._Q_coil = Q_coil
|
|
197
|
+
self._Q_eq = Q_eq
|
|
198
|
+
self._M_w = M_w
|
|
199
|
+
self._f_load = f_load
|
|
200
|
+
|
|
201
|
+
air_flow = {"M_a": self._air_flow * self._f_oa * self._rho_i,
|
|
202
|
+
"T_a": self._T_odb,
|
|
203
|
+
"w_a":self._w_o,
|
|
204
|
+
"Q_s": Q_eq,
|
|
205
|
+
"M_w":self._M_w }
|
|
206
|
+
return air_flow
|
|
207
|
+
|
|
208
|
+
def _mix_air(self, f, T1, w1, T2, w2):
|
|
209
|
+
T = f * T1 + (1-f)*T2
|
|
210
|
+
w = f * w1 + (1-f)*w2
|
|
211
|
+
if (T > 100):
|
|
212
|
+
T_wb = 50 # Inventado
|
|
213
|
+
else:
|
|
214
|
+
T_wb = sicro.GetTWetBulbFromHumRatio(T,w/1000,self.props["ATM_PRESSURE"])
|
|
215
|
+
return (T,w,T_wb)
|
|
216
|
+
|
|
217
|
+
def post_iteration(self, time_index, date, daylight_saving, converged):
|
|
218
|
+
super().post_iteration(time_index, date, daylight_saving, converged)
|
|
219
|
+
self.variable("state").values[time_index] = self._state
|
|
220
|
+
if self._state != 0 : # on
|
|
221
|
+
self.variable("T_MA").values[time_index] = self._T_idb
|
|
222
|
+
self.variable("T_MAwb").values[time_index] = self._T_iwb
|
|
223
|
+
m_supply = self._air_flow * self._rho_i
|
|
224
|
+
self.variable("m_air_flow").values[time_index] = m_supply
|
|
225
|
+
self.variable("outdoor_air_fraction").values[time_index] = self._f_oa
|
|
226
|
+
self.variable("T_SA").values[time_index] = self._Q_eq/(m_supply*self.props["C_PA"]) + self._T_idb
|
|
227
|
+
self.variable("w_SA").values[time_index] = self._M_w/(self._air_flow * self._rho_i) +self._w_i
|
|
228
|
+
self.variable("F_air").values[time_index] = self._f_air
|
|
229
|
+
self.variable("F_load").values[time_index] = self._f_load
|
|
230
|
+
if self._state == 1 or self._state == 2: # Heating
|
|
231
|
+
power,fan_power, F_COP = self._equipment.get_heating_power(self._T_idb,self._T_iwb,self._T_odb,self._T_owb,self._f_air,self._Q_required)
|
|
232
|
+
Q_sys = self._Q_coil
|
|
233
|
+
self.variable("Q_sensible").values[time_index] = Q_sys
|
|
234
|
+
self.variable("Q_total").values[time_index] = Q_sys
|
|
235
|
+
self.variable("power").values[time_index] = power
|
|
236
|
+
self.variable("indoor_fan_power").values[time_index] = fan_power
|
|
237
|
+
if power>0:
|
|
238
|
+
self.variable("COP").values[time_index] = Q_sys/power
|
|
239
|
+
self.variable("efficiency_degradation").values[time_index] = F_COP
|
|
240
|
+
elif self._state == -1 or self._state == -2: #Cooling
|
|
241
|
+
power,fan_power,F_EER = self._equipment.get_cooling_power(self._T_idb,self._T_iwb,self._T_odb,self._T_owb,self._f_air,self._Q_required)
|
|
242
|
+
Q_s = -self._Q_coil
|
|
243
|
+
Q_l = -self._M_w * self.props["LAMBDA"]
|
|
244
|
+
self.variable("Q_sensible").values[time_index] = Q_s
|
|
245
|
+
self.variable("Q_latent").values[time_index] = Q_l
|
|
246
|
+
self.variable("Q_total").values[time_index] = Q_s + Q_l
|
|
247
|
+
self.variable("power").values[time_index] = power
|
|
248
|
+
self.variable("indoor_fan_power").values[time_index] = fan_power
|
|
249
|
+
if power>0:
|
|
250
|
+
self.variable("EER").values[time_index] = (Q_s+Q_l)/power
|
|
251
|
+
self.variable("efficiency_degradation").values[time_index] = F_EER
|
|
252
|
+
else: # venting
|
|
253
|
+
self.variable("power").values[time_index] = self._equipment.get_fan_power(0)
|
|
254
|
+
self.variable("indoor_fan_power").values[time_index] = self._equipment.get_fan_power(0)
|