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,338 @@
|
|
|
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
|
+
class HVAC_SZW_system(Component): # HVAC Single Zone Water system
|
|
8
|
+
def __init__(self, name, project):
|
|
9
|
+
Component.__init__(self, name, project)
|
|
10
|
+
self.parameter("type").value = "HVAC_SZW_system"
|
|
11
|
+
self.parameter("description").value = "HVAC Single Zone Water system"
|
|
12
|
+
self.add_parameter(Parameter_component("spaces", "not_defined", ["Space"])) # Space
|
|
13
|
+
self.add_parameter(Parameter_component("cooling_coil", "not_defined", ["HVAC_coil_equipment"]))
|
|
14
|
+
self.add_parameter(Parameter_component("heating_coil", "not_defined", ["HVAC_coil_equipment"]))
|
|
15
|
+
self.add_parameter(Parameter_component("supply_fan", "not_defined", ["HVAC_fan_equipment"]))
|
|
16
|
+
self.add_parameter(Parameter_component("return_fan", "not_defined", ["HVAC_fan_equipment"]))
|
|
17
|
+
self.add_parameter(Parameter_float("air_flow", 1, "m³/s", min=0))
|
|
18
|
+
self.add_parameter(Parameter_float("return_air_flow", 1, "m³/s", min=0)) # Not used when return fan is not defined
|
|
19
|
+
self.add_parameter(Parameter_math_exp("outdoor_air_fraction", "0", "frac"))
|
|
20
|
+
self.add_parameter(Parameter_variable_list("input_variables", []))
|
|
21
|
+
self.add_parameter(Parameter_math_exp("heating_setpoint", "20", "°C"))
|
|
22
|
+
self.add_parameter(Parameter_math_exp("cooling_setpoint", "25", "°C"))
|
|
23
|
+
self.add_parameter(Parameter_math_exp("system_on_off", "1", "on/off"))
|
|
24
|
+
self.add_parameter(Parameter_options("fan_operation", "CONTINUOUS", ["CONTINUOUS", "CYCLING"]))
|
|
25
|
+
self.add_parameter(Parameter_options("water_source", "UNKNOWN", ["UNKNOWN", "WATER_LOOP"]))
|
|
26
|
+
self.add_parameter(Parameter_float("cooling_water_flow", 1, "m³/s", min=0))
|
|
27
|
+
self.add_parameter(Parameter_float("heating_water_flow", 1, "m³/s", min=0))
|
|
28
|
+
self.add_parameter(Parameter_float("inlet_cooling_water_temp", 7, "ºC"))
|
|
29
|
+
self.add_parameter(Parameter_float("inlet_heating_water_temp", 50, "ºC"))
|
|
30
|
+
self.add_parameter(Parameter_options("water_flow_control", "ON_OFF", ["ON_OFF", "PROPORTIONAL"]))
|
|
31
|
+
self.add_parameter(Parameter_options("economizer", "NO", ["NO", "TEMPERATURE","TEMPERATURE_NOT_INTEGRATED","ENTHALPY","ENTHALPY_LIMITED"]))
|
|
32
|
+
self.add_parameter(Parameter_float("economizer_DT", 0, "ºC", min=0))
|
|
33
|
+
self.add_parameter(Parameter_float("economizer_enthalpy_limit", 0, "kJ/kg", min=0))
|
|
34
|
+
|
|
35
|
+
# Variables
|
|
36
|
+
self.add_variable(Variable("state", unit="flag")) # 0: 0ff, 1: Heating, 2: Heating max cap, -1:Cooling, -2:Cooling max cap, 3: Venting
|
|
37
|
+
self.add_variable(Variable("T_OA", unit="°C"))
|
|
38
|
+
self.add_variable(Variable("T_OAwb", unit="°C"))
|
|
39
|
+
self.add_variable(Variable("T_MA", unit="°C"))
|
|
40
|
+
self.add_variable(Variable("T_MAwb", unit="°C"))
|
|
41
|
+
self.add_variable(Variable("F_load", unit="frac"))
|
|
42
|
+
self.add_variable(Variable("outdoor_air_fraction", unit="frac"))
|
|
43
|
+
self.add_variable(Variable("m_air_flow", unit="kg/s"))
|
|
44
|
+
self.add_variable(Variable("T_SA", unit="°C"))
|
|
45
|
+
self.add_variable(Variable("w_SA", unit="g/kg"))
|
|
46
|
+
self.add_variable(Variable("T_CA", unit="°C"))
|
|
47
|
+
self.add_variable(Variable("w_CA", unit="g/kg"))
|
|
48
|
+
self.add_variable(Variable("Q_sensible", unit="W"))
|
|
49
|
+
self.add_variable(Variable("Q_latent", unit="W"))
|
|
50
|
+
self.add_variable(Variable("Q_total", unit="W"))
|
|
51
|
+
self.add_variable(Variable("supply_fan_power", unit="W"))
|
|
52
|
+
self.add_variable(Variable("return_fan_power", unit="W"))
|
|
53
|
+
self.add_variable(Variable("heating_setpoint", unit="°C"))
|
|
54
|
+
self.add_variable(Variable("cooling_setpoint", unit="°C"))
|
|
55
|
+
self.add_variable(Variable("epsilon", unit="frac"))
|
|
56
|
+
self.add_variable(Variable("epsilon_adp", unit="frac"))
|
|
57
|
+
self.add_variable(Variable("T_iw", unit="°C"))
|
|
58
|
+
self.add_variable(Variable("T_ow", unit="°C"))
|
|
59
|
+
self.add_variable(Variable("T_ADP", unit="°C"))
|
|
60
|
+
self.add_variable(Variable("T_RA", unit="°C")) # Return air temperature
|
|
61
|
+
def check(self):
|
|
62
|
+
errors = super().check()
|
|
63
|
+
# Test space defined
|
|
64
|
+
if self.parameter("spaces").value == "not_defined":
|
|
65
|
+
msg = f"{self.parameter('name').value}, must define its space."
|
|
66
|
+
errors.append(Message(msg, "ERROR"))
|
|
67
|
+
# Test coil defined
|
|
68
|
+
if self.parameter("cooling_coil").value == "not_defined" and self.parameter("heating_coil").value == "not_defined":
|
|
69
|
+
msg = f"{self.parameter('name').value}, must define one coil equipment."
|
|
70
|
+
errors.append(Message(msg, "ERROR"))
|
|
71
|
+
# Test supply fan defined
|
|
72
|
+
if self.parameter("supply_fan").value == "not_defined":
|
|
73
|
+
msg = f"{self.parameter('name').value}, must define its supply fan equipment."
|
|
74
|
+
errors.append(Message(msg, "ERROR"))
|
|
75
|
+
# Test file_met defined
|
|
76
|
+
if self.project().parameter("simulation_file_met").value == "not_defined":
|
|
77
|
+
msg = f"{self.parameter('name').value}, file_met must be defined in the project 'simulation_file_met'."
|
|
78
|
+
errors.append(Message(msg, "ERROR"))
|
|
79
|
+
return errors
|
|
80
|
+
|
|
81
|
+
def pre_simulation(self, n_time_steps, delta_t):
|
|
82
|
+
super().pre_simulation(n_time_steps, delta_t)
|
|
83
|
+
# Sicro
|
|
84
|
+
sicro.SetUnitSystem(sicro.SI)
|
|
85
|
+
self.props = self._sim_.props
|
|
86
|
+
self.file_met = self.project().parameter("simulation_file_met").component
|
|
87
|
+
|
|
88
|
+
# Parameters
|
|
89
|
+
self.space = self.parameter("spaces").component
|
|
90
|
+
self.c_coil = self.parameter("cooling_coil").component
|
|
91
|
+
self.h_coil = self.parameter("heating_coil").component
|
|
92
|
+
self.supply_fan = self.parameter("supply_fan").component
|
|
93
|
+
self.return_fan = self.parameter("return_fan").component
|
|
94
|
+
self.air_flow = self.parameter("air_flow").value
|
|
95
|
+
self.return_air_flow = self.parameter("return_air_flow").value
|
|
96
|
+
|
|
97
|
+
# Water flows and temperatures
|
|
98
|
+
self.cooling_water_flow = self.parameter("cooling_water_flow").value
|
|
99
|
+
self.heating_water_flow = self.parameter("heating_water_flow").value
|
|
100
|
+
if self.c_coil:
|
|
101
|
+
self.cooling_F_water = self.cooling_water_flow/self.c_coil.parameter("nominal_cooling_water_flow").value
|
|
102
|
+
if self.h_coil:
|
|
103
|
+
self.heating_F_water = self.heating_water_flow/self.h_coil.parameter("nominal_heating_water_flow").value
|
|
104
|
+
self.cooling_water_temp = self.parameter("inlet_cooling_water_temp").value
|
|
105
|
+
self.heating_water_temp = self.parameter("inlet_heating_water_temp").value
|
|
106
|
+
self.rho_MA = self.props["RHO_A"]
|
|
107
|
+
# Fan operation
|
|
108
|
+
self.fan_operation = self.parameter("fan_operation").value
|
|
109
|
+
# adp model
|
|
110
|
+
self.water_flow_control = self.parameter("water_flow_control").value
|
|
111
|
+
|
|
112
|
+
def pre_iteration(self, time_index, date, daylight_saving):
|
|
113
|
+
super().pre_iteration(time_index, date, daylight_saving)
|
|
114
|
+
# Outdoor air
|
|
115
|
+
self.T_OA = self.file_met.variable("temperature").values[time_index]
|
|
116
|
+
self.T_OAwb = self.file_met.variable("wet_bulb_temp").values[time_index]
|
|
117
|
+
self.w_OA = self.file_met.variable("abs_humidity").values[time_index]
|
|
118
|
+
self.variable("T_OA").values[time_index] = self.T_OA
|
|
119
|
+
self.variable("T_OAwb").values[time_index] = self.T_OAwb
|
|
120
|
+
self.rho_OA = 1/sicro.GetMoistAirVolume(self.T_OA,self.w_OA/1000,self.props["ATM_PRESSURE"])
|
|
121
|
+
|
|
122
|
+
# variables dictonary
|
|
123
|
+
var_dic = self.get_parameter_variable_dictionary(time_index)
|
|
124
|
+
# outdoor air fraction
|
|
125
|
+
self.F_OA_min = self.parameter("outdoor_air_fraction").evaluate(var_dic)
|
|
126
|
+
self.F_OA = self.F_OA_min
|
|
127
|
+
# setpoints
|
|
128
|
+
self.T_heat_sp = self.parameter("heating_setpoint").evaluate(var_dic)
|
|
129
|
+
self.variable("heating_setpoint").values[time_index] = self.T_heat_sp
|
|
130
|
+
self.T_cool_sp = self.parameter("cooling_setpoint").evaluate(var_dic)
|
|
131
|
+
self.variable("cooling_setpoint").values[time_index] = self.T_cool_sp
|
|
132
|
+
# on/off
|
|
133
|
+
self.on_off = self.parameter("system_on_off").evaluate(var_dic)
|
|
134
|
+
if self.on_off == 0:
|
|
135
|
+
self.state = 0
|
|
136
|
+
self.variable("state").values[time_index] = 0
|
|
137
|
+
self.on_off = False
|
|
138
|
+
else:
|
|
139
|
+
self.on_off = True
|
|
140
|
+
# Starting with the system venting
|
|
141
|
+
self.F_load = 0
|
|
142
|
+
self.epsilon = 0
|
|
143
|
+
self.epsilon_adp = 0
|
|
144
|
+
|
|
145
|
+
def iteration(self, time_index, date, daylight_saving, n_iter):
|
|
146
|
+
super().iteration(time_index, date, daylight_saving, n_iter)
|
|
147
|
+
if self.on_off:
|
|
148
|
+
self._calculate_return_air(time_index)
|
|
149
|
+
self._calculate_required_Q()
|
|
150
|
+
if (self.parameter("economizer").value != "NO"):
|
|
151
|
+
self._simulate_economizer() # Calculation of new F_OA
|
|
152
|
+
self._calculate_required_Q()
|
|
153
|
+
self._calculate_mixed_air()
|
|
154
|
+
self._simulate_system()
|
|
155
|
+
air_flow = {"M_a": self.air_flow * self.F_OA * self.rho_MA,
|
|
156
|
+
"T_a": self.T_OA,
|
|
157
|
+
"w_a": self.w_OA,
|
|
158
|
+
"Q_s": self.Q_eq,
|
|
159
|
+
"M_w": self.M_w }
|
|
160
|
+
self.space.set_control_system(air_flow)
|
|
161
|
+
return True
|
|
162
|
+
|
|
163
|
+
def _calculate_return_air(self, time_index):
|
|
164
|
+
self.T_ZA = self.space.variable("temperature").values[time_index]
|
|
165
|
+
self.w_ZA = self.space.variable("abs_humidity").values[time_index]
|
|
166
|
+
self.w_RA = self.w_ZA
|
|
167
|
+
if self.return_fan is None: # No return fan
|
|
168
|
+
self.T_RA =self.T_ZA
|
|
169
|
+
self.Q_return_fan_required = 0
|
|
170
|
+
else:
|
|
171
|
+
Q_return_fan = self._get_fan_power("return",self.F_load)
|
|
172
|
+
self.Q_return_fan_required = Q_return_fan * self.air_flow * (1-self.F_OA)/ self.return_air_flow
|
|
173
|
+
mrhocp = self.return_air_flow * self.rho_MA * self.props["C_PA"]
|
|
174
|
+
if mrhocp > 0:
|
|
175
|
+
self.T_RA = self.T_ZA + Q_return_fan/mrhocp
|
|
176
|
+
|
|
177
|
+
def _calculate_required_Q(self):
|
|
178
|
+
K_t,F_t = self.space.get_thermal_equation(False)
|
|
179
|
+
Q_supply_fan = self._get_fan_power("supply",self.F_load)
|
|
180
|
+
m_cp_oa = self.air_flow * self.F_OA * self.rho_MA * self.props["C_PA"]
|
|
181
|
+
K_ts = K_t + m_cp_oa
|
|
182
|
+
F_ts = F_t + m_cp_oa * self.T_OA + Q_supply_fan + self.Q_return_fan_required
|
|
183
|
+
T_flo = F_ts/K_ts
|
|
184
|
+
if T_flo > self.T_cool_sp:
|
|
185
|
+
self.Q_required = K_ts * self.T_cool_sp - F_ts
|
|
186
|
+
elif T_flo < self.T_heat_sp:
|
|
187
|
+
self.Q_required = K_ts * self.T_heat_sp - F_ts
|
|
188
|
+
else:
|
|
189
|
+
self.Q_required = 0
|
|
190
|
+
if abs(self.Q_required) < 0.0001: # Problems with convergence
|
|
191
|
+
self.Q_required = 0
|
|
192
|
+
|
|
193
|
+
def _get_fan_power(self, fan, F_load):
|
|
194
|
+
if fan == "supply":
|
|
195
|
+
if self.fan_operation == "CONTINUOUS":
|
|
196
|
+
return self.supply_fan.get_power(self.air_flow)
|
|
197
|
+
elif self.fan_operation == "CYCLING":
|
|
198
|
+
return self.supply_fan.get_power(self.air_flow)*F_load
|
|
199
|
+
elif fan == "return":
|
|
200
|
+
if self.return_fan is None: # No return fan
|
|
201
|
+
return 0
|
|
202
|
+
else:
|
|
203
|
+
if self.fan_operation == "CONTINUOUS":
|
|
204
|
+
return self.return_fan.get_power(self.return_air_flow)
|
|
205
|
+
elif self.fan_operation == "CYCLING":
|
|
206
|
+
return self.return_fan.get_power(self.return_air_flow)*F_load
|
|
207
|
+
|
|
208
|
+
def _calculate_mixed_air(self):
|
|
209
|
+
# Mixed air
|
|
210
|
+
self.T_MA, self.w_MA, self.T_MAwb = self._mix_air(self.F_OA, self.T_OA, self.w_OA, self.T_RA, self.w_RA)
|
|
211
|
+
self.rho_MA = 1/sicro.GetMoistAirVolume(self.T_MA,self.w_MA/1000,self.props["ATM_PRESSURE"])
|
|
212
|
+
|
|
213
|
+
def _mix_air(self, f, T1, w1, T2, w2):
|
|
214
|
+
T = f * T1 + (1-f)*T2
|
|
215
|
+
w = f * w1 + (1-f)*w2
|
|
216
|
+
if (T > 100):
|
|
217
|
+
T_wb = 50 # Inventado
|
|
218
|
+
else:
|
|
219
|
+
T_wb = sicro.GetTWetBulbFromHumRatio(T,w/1000,self.props["ATM_PRESSURE"])
|
|
220
|
+
return (T,w,T_wb)
|
|
221
|
+
|
|
222
|
+
def _simulate_economizer(self):
|
|
223
|
+
if (self.parameter("economizer").value == "TEMPERATURE" or self.parameter("economizer").value == "TEMPERATURE_NOT_INTEGRATED"):
|
|
224
|
+
on_economizer = self.T_OA < self.T_RA - self.parameter("economizer_DT").value
|
|
225
|
+
elif (self.parameter("economizer").value == "ENTHALPY"):
|
|
226
|
+
h_OA = sicro.GetMoistAirEnthalpy(self.T_OA,self.w_OA/1000)
|
|
227
|
+
h_RA = sicro.GetMoistAirEnthalpy(self.T_RA,self.w_RA/1000)
|
|
228
|
+
on_economizer = h_OA < h_RA
|
|
229
|
+
elif (self.parameter("economizer").value == "ENTHALPY_LIMITED"):
|
|
230
|
+
h_OA = sicro.GetMoistAirEnthalpy(self.T_OA,self.w_OA/1000)
|
|
231
|
+
on_economizer = h_OA < self.parameter("economizer_enthalpy_limit").value * 1000
|
|
232
|
+
|
|
233
|
+
if (on_economizer):
|
|
234
|
+
if (self.Q_required < 0):
|
|
235
|
+
mrhocp = self.air_flow * self.props["C_PA"]* self.rho_OA
|
|
236
|
+
Q_rest_oa = mrhocp * (1-self.F_OA) * (self.T_OA - self.T_ZA)
|
|
237
|
+
if Q_rest_oa < self.Q_required:
|
|
238
|
+
self.F_OA += self.Q_required/(mrhocp * (self.T_OA-self.T_ZA))
|
|
239
|
+
self.Q_required = 0
|
|
240
|
+
else:
|
|
241
|
+
if (self.parameter("economizer").value == "TEMPERATURE_NOT_INTEGRATED"):
|
|
242
|
+
self.F_OA = self.F_OA_min
|
|
243
|
+
else:
|
|
244
|
+
self.F_OA = 1
|
|
245
|
+
elif (self.Q_required > 0): # Heating
|
|
246
|
+
self.F_OA = self.F_OA_min
|
|
247
|
+
else:
|
|
248
|
+
self.F_OA = self.F_OA_min
|
|
249
|
+
|
|
250
|
+
def _simulate_system(self):
|
|
251
|
+
if self.h_coil is not None and self.Q_required > 0: # Heating
|
|
252
|
+
self._simulate_heating()
|
|
253
|
+
elif self.c_coil is not None and self.Q_required < 0: # Cooling
|
|
254
|
+
self._simulate_cooling()
|
|
255
|
+
else: # Venting
|
|
256
|
+
self.state = 3
|
|
257
|
+
self.F_load = 0
|
|
258
|
+
self.Q_coil = 0
|
|
259
|
+
self.Q_eq = self._get_fan_power("supply", 0) + self.Q_return_fan_required
|
|
260
|
+
self.M_w = 0
|
|
261
|
+
|
|
262
|
+
def _simulate_heating(self):
|
|
263
|
+
capacity, self.epsilon = self.h_coil.get_heating_capacity(self.T_MA, self.T_MAwb, self.heating_water_temp,self.air_flow,self.heating_water_flow)
|
|
264
|
+
self.M_w = 0 # No latent load in heating
|
|
265
|
+
# Q_required is only the coil capacity
|
|
266
|
+
if capacity < self.Q_required: # Coil capacity is not enough
|
|
267
|
+
self.F_load = 1
|
|
268
|
+
self.Q_coil = capacity
|
|
269
|
+
self.Q_eq = capacity + self._get_fan_power("supply",1) + self.Q_return_fan_required
|
|
270
|
+
self.state = 2
|
|
271
|
+
else:
|
|
272
|
+
self.Q_coil = self.Q_required
|
|
273
|
+
self.F_load = self.Q_coil / capacity
|
|
274
|
+
self.Q_eq = self.Q_required + self._get_fan_power("supply", self.F_load) + self.Q_return_fan_required
|
|
275
|
+
self.state = 1
|
|
276
|
+
|
|
277
|
+
def _simulate_cooling(self):
|
|
278
|
+
capacity_sen, capacity_lat, self.T_ADP, self.epsilon, self.epsilon_adp = self.c_coil.get_cooling_capacity(self.T_MA, self.T_MAwb,self.cooling_water_temp,self.air_flow,self.cooling_water_flow)
|
|
279
|
+
Q_required = -self.Q_required
|
|
280
|
+
# Q_required is only the coil capacity
|
|
281
|
+
if capacity_sen < Q_required: # Coil capacity is not enough
|
|
282
|
+
self.state = -2
|
|
283
|
+
self.F_load = 1
|
|
284
|
+
self.Q_coil = - capacity_sen
|
|
285
|
+
self.Q_eq = self._get_fan_power("supply",1) + self.Q_return_fan_required - capacity_sen
|
|
286
|
+
self.M_w = - (capacity_lat) / self.props["LAMBDA"]
|
|
287
|
+
else:
|
|
288
|
+
self.state = -1
|
|
289
|
+
self.Q_coil = - Q_required
|
|
290
|
+
self.F_load = Q_required / capacity_sen
|
|
291
|
+
self.Q_eq = - Q_required + self._get_fan_power("supply", self.F_load) +self.Q_return_fan_required
|
|
292
|
+
if self.water_flow_control == "ON_OFF":
|
|
293
|
+
self.M_w = - (capacity_lat*self.F_load) / self.props["LAMBDA"]
|
|
294
|
+
elif self.water_flow_control == "PROPORTIONAL":
|
|
295
|
+
mrhocp = self.air_flow * self.rho_MA * self.props["C_PA"]
|
|
296
|
+
T_CA = self.T_MA - (Q_required) / mrhocp
|
|
297
|
+
Q_lat, self.T_ADP,self.epsilon_adp = self.c_coil.get_latent_cooling_load(self.T_MA, self.T_MAwb, self.cooling_water_temp, self.air_flow, self.cooling_water_flow, T_CA)
|
|
298
|
+
self.M_w = - Q_lat / self.props["LAMBDA"]
|
|
299
|
+
|
|
300
|
+
def post_iteration(self, time_index, date, daylight_saving, converged):
|
|
301
|
+
super().post_iteration(time_index, date, daylight_saving, converged)
|
|
302
|
+
self.variable("state").values[time_index] = self.state
|
|
303
|
+
if self.state != 0 : # on
|
|
304
|
+
self.variable("T_MA").values[time_index] = self.T_MA
|
|
305
|
+
self.variable("T_MAwb").values[time_index] = self.T_MAwb
|
|
306
|
+
m_supply = self.air_flow * self.rho_MA
|
|
307
|
+
self.variable("m_air_flow").values[time_index] = m_supply
|
|
308
|
+
self.variable("outdoor_air_fraction").values[time_index] = self.F_OA
|
|
309
|
+
# Density of air at the fan inlet
|
|
310
|
+
T_CA = self.Q_coil/(m_supply*self.props["C_PA"]) + self.T_MA
|
|
311
|
+
w_CA = self.M_w/m_supply +self.w_MA
|
|
312
|
+
self.variable("T_CA").values[time_index] = T_CA
|
|
313
|
+
self.variable("w_CA").values[time_index] = w_CA
|
|
314
|
+
self.variable("T_SA").values[time_index] = self._get_fan_power("supply",self.F_load)/(m_supply*self.props["C_PA"]) + T_CA
|
|
315
|
+
self.variable("w_SA").values[time_index] = w_CA
|
|
316
|
+
self.variable("T_RA").values[time_index] =self.T_RA
|
|
317
|
+
self.variable("F_load").values[time_index] = self.F_load
|
|
318
|
+
self.variable("supply_fan_power").values[time_index] = self._get_fan_power("supply", self.F_load)
|
|
319
|
+
self.variable("return_fan_power").values[time_index] = self._get_fan_power("return", self.F_load)
|
|
320
|
+
if self.state == 1 or self.state == 2: # Heating
|
|
321
|
+
Q_sys = self.Q_coil
|
|
322
|
+
self.variable("Q_sensible").values[time_index] = Q_sys
|
|
323
|
+
self.variable("Q_total").values[time_index] = Q_sys
|
|
324
|
+
self.variable("T_iw").values[time_index] = self.heating_water_temp
|
|
325
|
+
self.variable("T_ow").values[time_index] = self.heating_water_temp - Q_sys/(self.heating_water_flow * self.props["RHOCP_W"](self.heating_water_temp))
|
|
326
|
+
self.variable("T_ADP").values[time_index] = 0
|
|
327
|
+
self.variable("epsilon").values[time_index] = self.epsilon
|
|
328
|
+
elif self.state == -1 or self.state == -2: #Cooling
|
|
329
|
+
Q_s = -self.Q_coil
|
|
330
|
+
Q_l = -self.M_w * self.props["LAMBDA"]
|
|
331
|
+
self.variable("Q_sensible").values[time_index] = Q_s
|
|
332
|
+
self.variable("Q_latent").values[time_index] = Q_l
|
|
333
|
+
self.variable("Q_total").values[time_index] = Q_s
|
|
334
|
+
self.variable("T_iw").values[time_index] = self.cooling_water_temp
|
|
335
|
+
self.variable("T_ow").values[time_index] = self.cooling_water_temp + (Q_s+Q_l)/(self.cooling_water_flow * self.props["RHOCP_W"](self.cooling_water_temp))
|
|
336
|
+
self.variable("T_ADP").values[time_index] = self.T_ADP
|
|
337
|
+
self.variable("epsilon").values[time_index] = self.epsilon
|
|
338
|
+
self.variable("epsilon_adp").values[time_index] = self.epsilon_adp
|
|
@@ -0,0 +1,206 @@
|
|
|
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
|
+
import psychrolib as sicro
|
|
6
|
+
|
|
7
|
+
class HVAC_coil_equipment(Component):
|
|
8
|
+
def __init__(self, name, project):
|
|
9
|
+
Component.__init__(self, name, project)
|
|
10
|
+
self.parameter("type").value = "HVAC_coil_equipment"
|
|
11
|
+
self.parameter("description").value = "HVAC coil equipment manufacturer information"
|
|
12
|
+
self.add_parameter(Parameter_float("nominal_air_flow", 1, "m³/s", min=0))
|
|
13
|
+
self.add_parameter(Parameter_float("nominal_cooling_water_flow", 1, "m³/s", min=0))
|
|
14
|
+
self.add_parameter(Parameter_float("nominal_total_cooling_capacity", 0, "W", min=0))
|
|
15
|
+
self.add_parameter(Parameter_float("nominal_sensible_cooling_capacity", 0, "W", min=0))
|
|
16
|
+
self.add_parameter(Parameter_float_list("nominal_cooling_conditions", [27, 19, 7], "ºC"))
|
|
17
|
+
self.add_parameter(Parameter_float("nominal_heating_water_flow", 1, "m³/s", min=0))
|
|
18
|
+
self.add_parameter(Parameter_float("nominal_heating_capacity", 0, "W", min=0))
|
|
19
|
+
self.add_parameter(Parameter_float_list("nominal_heating_conditions", [20, 15, 50], "ºC"))
|
|
20
|
+
self.add_parameter(Parameter_math_exp("heating_epsilon_expression", "1", "frac"))
|
|
21
|
+
self.add_parameter(Parameter_math_exp("cooling_epsilon_expression", "1", "frac"))
|
|
22
|
+
self.add_parameter(Parameter_math_exp("cooling_adp_epsilon_expression", "1", "frac"))
|
|
23
|
+
self.add_parameter(Parameter_float_list("expression_max_values", [60,30,99,2,2], "-"))
|
|
24
|
+
self.add_parameter(Parameter_float_list("expression_min_values", [-30,-30,0,0,0], "-"))
|
|
25
|
+
|
|
26
|
+
def check(self):
|
|
27
|
+
errors = super().check()
|
|
28
|
+
# Test Cooling and Heating conditions: 3, 3 values (Entering air and water)
|
|
29
|
+
if len(self.parameter("nominal_cooling_conditions").value)!= 3:
|
|
30
|
+
msg = f"{self.parameter('name').value}, nominal_cooling_conditions size must be 3"
|
|
31
|
+
errors.append(Message(msg, "ERROR"))
|
|
32
|
+
if len(self.parameter("nominal_heating_conditions").value)!= 3:
|
|
33
|
+
msg = f"{self.parameter('name').value}, nominal_heating_conditions size must be 3"
|
|
34
|
+
errors.append(Message(msg, "ERROR"))
|
|
35
|
+
return errors
|
|
36
|
+
|
|
37
|
+
def pre_simulation(self, n_time_steps, delta_t):
|
|
38
|
+
super().pre_simulation(n_time_steps, delta_t)
|
|
39
|
+
# Sicro
|
|
40
|
+
sicro.SetUnitSystem(sicro.SI)
|
|
41
|
+
self.props = self._sim_.props
|
|
42
|
+
|
|
43
|
+
# Parameters
|
|
44
|
+
self._nominal_air_flow = self.parameter("nominal_air_flow").value
|
|
45
|
+
# Heating
|
|
46
|
+
self._nominal_heating_capacity = self.parameter("nominal_heating_capacity").value
|
|
47
|
+
self._nominal_heating_water_flow = self.parameter("nominal_heating_water_flow").value
|
|
48
|
+
self._T_idb_HN = self.parameter("nominal_heating_conditions").value[0]
|
|
49
|
+
self._T_iwb_HN = self.parameter("nominal_heating_conditions").value[1]
|
|
50
|
+
self._T_iw_HN = self.parameter("nominal_heating_conditions").value[2]
|
|
51
|
+
# Cooling
|
|
52
|
+
self._nominal_total_cooling_capacity = self.parameter("nominal_total_cooling_capacity").value
|
|
53
|
+
self._nominal_sensible_cooling_capacity = self.parameter("nominal_sensible_cooling_capacity").value
|
|
54
|
+
self._nominal_cooling_water_flow = self.parameter("nominal_cooling_water_flow").value
|
|
55
|
+
self._T_idb_CN = self.parameter("nominal_cooling_conditions").value[0]
|
|
56
|
+
self._T_iwb_CN = self.parameter("nominal_cooling_conditions").value[1]
|
|
57
|
+
self._T_iw_CN = self.parameter("nominal_cooling_conditions").value[2]
|
|
58
|
+
|
|
59
|
+
self.calculate_nominal_effectiveness()
|
|
60
|
+
|
|
61
|
+
def calculate_nominal_effectiveness(self):
|
|
62
|
+
# Heating
|
|
63
|
+
if self._nominal_heating_capacity > 0:
|
|
64
|
+
w = sicro.GetHumRatioFromTWetBulb(self._T_iw_HN,self._T_iwb_HN,101325)
|
|
65
|
+
rho = 1/sicro.GetMoistAirVolume(self._T_idb_HN,w,101325)
|
|
66
|
+
C_min = min( self._nominal_air_flow * rho * self.props["C_PA"], self._nominal_heating_water_flow * self.props["RHOCP_W"](self._T_iw_HN))
|
|
67
|
+
Q_max = C_min * (self._T_iw_HN - self._T_idb_HN)
|
|
68
|
+
self._nominal_heating_epsilon = self._nominal_heating_capacity/Q_max
|
|
69
|
+
if self._nominal_heating_epsilon > 1:
|
|
70
|
+
self._sim_.message(Message(f"{self.parameter('name').value} : Nominal heating effectiveness > 1","ERROR"))
|
|
71
|
+
else:
|
|
72
|
+
self._nominal_heating_epsilon = 0
|
|
73
|
+
|
|
74
|
+
# Cooling
|
|
75
|
+
if self._nominal_total_cooling_capacity > 0:
|
|
76
|
+
T_ri =sicro.GetTDewPointFromTWetBulb(self._T_idb_CN,self._T_iwb_CN,101325)
|
|
77
|
+
if T_ri < self._T_iw_CN:
|
|
78
|
+
self._sim_.message(Message(f"{self.parameter('name').value} : Nominal dry coil, inlet water temperature must be less than inlet dew point temperature","ERROR"))
|
|
79
|
+
w_i = sicro.GetHumRatioFromTWetBulb(self._T_idb_CN,self._T_iwb_CN,101325)
|
|
80
|
+
rho = 1/sicro.GetMoistAirVolume(self._T_idb_CN,w_i,101325)
|
|
81
|
+
h_ia = sicro.GetMoistAirEnthalpy(self._T_idb_CN,w_i)
|
|
82
|
+
h_iw = sicro.GetMoistAirEnthalpy(self._T_iw_CN,sicro.GetHumRatioFromRelHum(self._T_iw_CN,1,101325))
|
|
83
|
+
self._nominal_enthalpy_epsilon = self._nominal_total_cooling_capacity / (self._nominal_air_flow * rho * (h_ia-h_iw))
|
|
84
|
+
if (self._nominal_enthalpy_epsilon > 1):
|
|
85
|
+
self._sim_.message(Message(f"{self.parameter('name').value} : Nominal cooling effectiveness > 1","ERROR"))
|
|
86
|
+
h_oa = h_ia - self._nominal_total_cooling_capacity/(self._nominal_air_flow * rho)
|
|
87
|
+
T_odb = self._T_idb_CN - self._nominal_sensible_cooling_capacity/(self._nominal_air_flow * rho * self.props["C_PA"])
|
|
88
|
+
w_o = sicro.GetHumRatioFromEnthalpyAndTDryBulb(h_oa,T_odb)
|
|
89
|
+
# Calculate ADP
|
|
90
|
+
self._nominal_T_adp = self.calculate_ADP(self._T_idb_CN,w_i,T_odb,w_o,101325)
|
|
91
|
+
self._nominal_cooling_adp_epsilon = (self._T_idb_CN - T_odb)/(self._T_idb_CN - self._nominal_T_adp)
|
|
92
|
+
if (self._nominal_cooling_adp_epsilon > 1):
|
|
93
|
+
self._sim_.message(Message(f"{self.parameter('name').value} : Nominal cooling ADP effectiveness > 1","ERROR"))
|
|
94
|
+
else:
|
|
95
|
+
self._nominal_enthalpy_epsilon = 0
|
|
96
|
+
self._nominal_cooling_adp_epsilon = 0
|
|
97
|
+
|
|
98
|
+
def calculate_ADP(self,T_i,w_i,T_o,w_o,p):
|
|
99
|
+
def func(x):
|
|
100
|
+
w_adp = sicro.GetHumRatioFromRelHum(x,1,p)
|
|
101
|
+
w_adp2 = w_i - (T_i -x) * (w_i - w_o)/(T_i-T_o)
|
|
102
|
+
return w_adp - w_adp2
|
|
103
|
+
solucion = fsolve(func, x0=T_o -1,xtol=1e-3)
|
|
104
|
+
return solucion[0]
|
|
105
|
+
|
|
106
|
+
def get_heating_capacity(self,T_idb,T_iwb,T_iw,air_flow,water_flow):
|
|
107
|
+
if self._nominal_heating_capacity > 0:
|
|
108
|
+
# variables dictonary
|
|
109
|
+
F_air = air_flow/ self._nominal_air_flow
|
|
110
|
+
F_water = water_flow/ self._nominal_heating_water_flow
|
|
111
|
+
var_dic = self._var_state_dic([T_idb, T_iwb,T_iw,F_air,F_water])
|
|
112
|
+
w_i = sicro.GetHumRatioFromTWetBulb(T_idb,T_iwb,self.props["ATM_PRESSURE"])
|
|
113
|
+
rho_i = 1/sicro.GetMoistAirVolume(T_idb,w_i,self.props["ATM_PRESSURE"])
|
|
114
|
+
|
|
115
|
+
# Capacity
|
|
116
|
+
epsilon = self._nominal_heating_epsilon * self.parameter("heating_epsilon_expression").evaluate(var_dic)
|
|
117
|
+
C_min = min(self._nominal_air_flow * F_air * rho_i * self.props["C_PA"], self._nominal_heating_water_flow*F_water*self.props["RHOCP_W"](T_iw))
|
|
118
|
+
capacity = epsilon * C_min * (T_iw - T_idb)
|
|
119
|
+
return capacity, epsilon
|
|
120
|
+
else:
|
|
121
|
+
return 0, 0
|
|
122
|
+
|
|
123
|
+
def get_cooling_capacity(self,T_idb,T_iwb,T_iw,air_flow,water_flow):
|
|
124
|
+
Q_sen = 0
|
|
125
|
+
Q_lat = 0
|
|
126
|
+
T_adp = 0
|
|
127
|
+
epsilon = 0
|
|
128
|
+
adp_epsilon = 0
|
|
129
|
+
if self._nominal_total_cooling_capacity > 0:
|
|
130
|
+
# variables dictonary
|
|
131
|
+
F_air = air_flow/ self._nominal_air_flow
|
|
132
|
+
F_water = water_flow/ self._nominal_cooling_water_flow
|
|
133
|
+
var_dic = self._var_state_dic([T_idb, T_iwb,T_iw,F_air, F_water])
|
|
134
|
+
# epsilon
|
|
135
|
+
epsilon = self._nominal_enthalpy_epsilon * self.parameter("cooling_epsilon_expression").evaluate(var_dic)
|
|
136
|
+
adp_epsilon = self._nominal_cooling_adp_epsilon * self.parameter("cooling_adp_epsilon_expression").evaluate(var_dic)
|
|
137
|
+
T_idp =sicro.GetTDewPointFromTWetBulb(T_idb,T_iwb,self.props["ATM_PRESSURE"])
|
|
138
|
+
w_i = sicro.GetHumRatioFromTWetBulb(T_idb,T_iwb,self.props["ATM_PRESSURE"])
|
|
139
|
+
h_i = sicro.GetMoistAirEnthalpy(T_idb,w_i)
|
|
140
|
+
rho_i = 1/sicro.GetMoistAirVolume(T_idb,w_i,self.props["ATM_PRESSURE"])
|
|
141
|
+
mrho = self._nominal_air_flow * F_air * rho_i
|
|
142
|
+
mrhocp = mrho * self.props["C_PA"]
|
|
143
|
+
if T_idp < T_iw: # Dry coil
|
|
144
|
+
Q_lat = 0
|
|
145
|
+
Q_sen = epsilon * mrhocp * (T_idb - T_iw)
|
|
146
|
+
T_adp = T_idp
|
|
147
|
+
else: # Wet coil
|
|
148
|
+
h_iw = sicro.GetMoistAirEnthalpy(T_iw,sicro.GetHumRatioFromRelHum(T_iw,1,self.props["ATM_PRESSURE"]))
|
|
149
|
+
Q_tot = epsilon * mrho * (h_i - h_iw)
|
|
150
|
+
h_o = h_i - Q_tot/mrho
|
|
151
|
+
h_adp = h_i - (h_i -h_o)/adp_epsilon
|
|
152
|
+
T_adp = self.get_T_adp_from_h_adp(h_adp,T_iw + 4)
|
|
153
|
+
Q_sen = adp_epsilon * mrhocp * (T_idb - T_adp)
|
|
154
|
+
if (Q_sen > Q_tot):
|
|
155
|
+
Q_sen = Q_tot
|
|
156
|
+
Q_lat = Q_tot - Q_sen
|
|
157
|
+
return Q_sen, Q_lat, T_adp, epsilon, adp_epsilon
|
|
158
|
+
|
|
159
|
+
def get_latent_cooling_load(self,T_idb,T_iwb,T_iw,air_flow,water_flow,T_odb):
|
|
160
|
+
# variables dictonary
|
|
161
|
+
F_air = air_flow/ self._nominal_air_flow
|
|
162
|
+
F_water = water_flow/ self._nominal_cooling_water_flow
|
|
163
|
+
var_dic = self._var_state_dic([T_idb, T_iwb,T_iw,F_air, F_water])
|
|
164
|
+
# epsilon
|
|
165
|
+
adp_epsilon = self._nominal_cooling_adp_epsilon * self.parameter("cooling_adp_epsilon_expression").evaluate(var_dic)
|
|
166
|
+
T_adp = T_idb - (T_idb - T_odb)/ adp_epsilon
|
|
167
|
+
T_idp =sicro.GetTDewPointFromTWetBulb(T_idb,T_iwb,self.props["ATM_PRESSURE"])
|
|
168
|
+
w_i = sicro.GetHumRatioFromTWetBulb(T_idb,T_iwb,self.props["ATM_PRESSURE"])
|
|
169
|
+
h_i = sicro.GetMoistAirEnthalpy(T_idb,w_i)
|
|
170
|
+
rho_i = 1/sicro.GetMoistAirVolume(T_idb,w_i,self.props["ATM_PRESSURE"])
|
|
171
|
+
mrho = self._nominal_air_flow * F_air * rho_i
|
|
172
|
+
Q_sen = mrho * self.props["C_PA"] * (T_idb - T_odb)
|
|
173
|
+
if T_adp < T_idp:
|
|
174
|
+
w_adp = sicro.GetHumRatioFromRelHum(T_adp,1,self.props["ATM_PRESSURE"])
|
|
175
|
+
h_adp = sicro.GetMoistAirEnthalpy(T_adp,w_adp)
|
|
176
|
+
cap_tot = adp_epsilon * mrho * (h_i - h_adp)
|
|
177
|
+
Q_lat = cap_tot - Q_sen
|
|
178
|
+
else:
|
|
179
|
+
Q_lat = 0
|
|
180
|
+
T_adp = T_idp
|
|
181
|
+
return Q_lat, T_adp, adp_epsilon
|
|
182
|
+
|
|
183
|
+
def get_T_adp_from_h_adp(self,h_adp,T_ini):
|
|
184
|
+
def func(x):
|
|
185
|
+
return (h_adp - sicro.GetSatAirEnthalpy(x,self.props["ATM_PRESSURE"]))
|
|
186
|
+
solucion = fsolve(func, x0=T_ini,xtol=1e-3)
|
|
187
|
+
return solucion[0]
|
|
188
|
+
|
|
189
|
+
def _var_state_dic(self, values):
|
|
190
|
+
max = self.parameter("expression_max_values").value
|
|
191
|
+
min = self.parameter("expression_min_values").value
|
|
192
|
+
for i in range(len(values)):
|
|
193
|
+
if (values[i] > max[i]):
|
|
194
|
+
values[i] = max[i]
|
|
195
|
+
elif (values[i] < min[i]):
|
|
196
|
+
values[i] = min[i]
|
|
197
|
+
return {"T_idb":values[0],
|
|
198
|
+
"T_iwb":values[1],
|
|
199
|
+
"T_iw":values[2],
|
|
200
|
+
"F_air":values[3],
|
|
201
|
+
"F_water":values[4]}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from opensimula.Parameters import Parameter_float, Parameter_math_exp
|
|
2
|
+
from opensimula.Component import Component
|
|
3
|
+
|
|
4
|
+
class HVAC_fan_equipment(Component):
|
|
5
|
+
def __init__(self, name, project):
|
|
6
|
+
Component.__init__(self, name, project)
|
|
7
|
+
self.parameter("type").value = "HVAC_fan_equipment"
|
|
8
|
+
self.parameter("description").value = "HVAC Fan equipment manufacturer information"
|
|
9
|
+
self.add_parameter(Parameter_float("nominal_air_flow", 1, "m³/s", min=0))
|
|
10
|
+
self.add_parameter(Parameter_float("nominal_pressure", 1, "Pa", min=0))
|
|
11
|
+
self.add_parameter(Parameter_float("nominal_power", 1, "W", min=0))
|
|
12
|
+
self.add_parameter(Parameter_math_exp("pressure_expression", "1", "frac"))
|
|
13
|
+
self.add_parameter(Parameter_math_exp("power_expression", "1", "frac"))
|
|
14
|
+
|
|
15
|
+
def check(self):
|
|
16
|
+
errors = super().check()
|
|
17
|
+
return errors
|
|
18
|
+
|
|
19
|
+
def pre_simulation(self, n_time_steps, delta_t):
|
|
20
|
+
# Parameters
|
|
21
|
+
self._nominal_air_flow = self.parameter("nominal_air_flow").value
|
|
22
|
+
self._nominal_pressure = self.parameter("nominal_pressure").value
|
|
23
|
+
self._nominal_power = self.parameter("nominal_power").value
|
|
24
|
+
|
|
25
|
+
def get_pressure(self,air_flow):
|
|
26
|
+
# variables dictonary
|
|
27
|
+
var_dic = {"F_air":air_flow/self._nominal_air_flow}
|
|
28
|
+
# Pressure
|
|
29
|
+
pressure = self._nominal_pressure * self.parameter("pressure_expression").evaluate(var_dic)
|
|
30
|
+
return pressure
|
|
31
|
+
|
|
32
|
+
def get_power(self,air_flow):
|
|
33
|
+
# variables dictonary
|
|
34
|
+
var_dic = {"F_air":air_flow/self._nominal_air_flow}
|
|
35
|
+
# Power
|
|
36
|
+
power = self._nominal_power * self.parameter("power_expression").evaluate(var_dic)
|
|
37
|
+
return power
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|