OpenSimula 0.3.2__tar.gz → 0.4.1__tar.gz

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.

Potentially problematic release.


This version of OpenSimula might be problematic. Click here for more details.

Files changed (60) hide show
  1. {opensimula-0.3.2 → opensimula-0.4.1}/LICENSE +1 -1
  2. {opensimula-0.3.2 → opensimula-0.4.1}/OpenSimula.egg-info/PKG-INFO +1 -1
  3. {opensimula-0.3.2 → opensimula-0.4.1}/OpenSimula.egg-info/SOURCES.txt +2 -0
  4. {opensimula-0.3.2 → opensimula-0.4.1}/PKG-INFO +1 -1
  5. {opensimula-0.3.2 → opensimula-0.4.1}/pyproject.toml +1 -1
  6. opensimula-0.4.1/src/OpenSimula/Iterative_process.py +43 -0
  7. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/Project.py +5 -0
  8. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/__init__.py +2 -1
  9. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/File_met.py +8 -4
  10. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/HVAC_DX_equipment.py +46 -6
  11. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/HVAC_DX_system.py +133 -34
  12. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Space.py +16 -25
  13. {opensimula-0.3.2 → opensimula-0.4.1}/test/test_HVAC_DX_Equipment.py +5 -5
  14. {opensimula-0.3.2 → opensimula-0.4.1}/test/test_HVAC_DX_system.py +1 -2
  15. opensimula-0.4.1/test/test_Iterative_process.py +13 -0
  16. {opensimula-0.3.2 → opensimula-0.4.1}/OpenSimula.egg-info/dependency_links.txt +0 -0
  17. {opensimula-0.3.2 → opensimula-0.4.1}/OpenSimula.egg-info/requires.txt +0 -0
  18. {opensimula-0.3.2 → opensimula-0.4.1}/OpenSimula.egg-info/top_level.txt +0 -0
  19. {opensimula-0.3.2 → opensimula-0.4.1}/README.md +0 -0
  20. {opensimula-0.3.2 → opensimula-0.4.1}/docs/cinder/__init__.py +0 -0
  21. {opensimula-0.3.2 → opensimula-0.4.1}/mkdocs/cinder/__init__.py +0 -0
  22. {opensimula-0.3.2 → opensimula-0.4.1}/setup.cfg +0 -0
  23. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/Child.py +0 -0
  24. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/Component.py +0 -0
  25. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/Parameter_container.py +0 -0
  26. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/Parameters.py +0 -0
  27. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/Simulation.py +0 -0
  28. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/Variable.py +0 -0
  29. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Building.py +0 -0
  30. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Calculator.py +0 -0
  31. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Construction.py +0 -0
  32. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Day_schedule.py +0 -0
  33. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Exterior_surface.py +0 -0
  34. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/File_data.py +0 -0
  35. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Frame.py +0 -0
  36. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Glazing.py +0 -0
  37. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/HVAC_perfect_system.py +0 -0
  38. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Interior_surface.py +0 -0
  39. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Material.py +0 -0
  40. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Opening.py +0 -0
  41. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Opening_type.py +0 -0
  42. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Real_surface.py +0 -0
  43. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Shadow_surface.py +0 -0
  44. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Space_type.py +0 -0
  45. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Surface.py +0 -0
  46. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Test_component.py +0 -0
  47. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Underground_surface.py +0 -0
  48. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Virtual_surface.py +0 -0
  49. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Week_schedule.py +0 -0
  50. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/Year_schedule.py +0 -0
  51. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/__init__.py +0 -0
  52. {opensimula-0.3.2 → opensimula-0.4.1}/src/OpenSimula/components/utils/sun_shadows.py +0 -0
  53. {opensimula-0.3.2 → opensimula-0.4.1}/test/test_Calculator.py +0 -0
  54. {opensimula-0.3.2 → opensimula-0.4.1}/test/test_Case_600FF.py +0 -0
  55. {opensimula-0.3.2 → opensimula-0.4.1}/test/test_Case_610.py +0 -0
  56. {opensimula-0.3.2 → opensimula-0.4.1}/test/test_Construction.py +0 -0
  57. {opensimula-0.3.2 → opensimula-0.4.1}/test/test_File_data.py +0 -0
  58. {opensimula-0.3.2 → opensimula-0.4.1}/test/test_File_met.py +0 -0
  59. {opensimula-0.3.2 → opensimula-0.4.1}/test/test_Project.py +0 -0
  60. {opensimula-0.3.2 → opensimula-0.4.1}/test/test_Schedules.py +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 Juan F. Coronel
3
+ Copyright (c) 2025 Juan F. Coronel
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: OpenSimula
3
- Version: 0.3.2
3
+ Version: 0.4.1
4
4
  Summary: Component-based time simulation environment in Python
5
5
  Author-email: "Juan F. Coronel" <jfc@us.es>
6
6
  Project-URL: Homepage, http://opensimula.org
@@ -10,6 +10,7 @@ docs/cinder/__init__.py
10
10
  mkdocs/cinder/__init__.py
11
11
  src/OpenSimula/Child.py
12
12
  src/OpenSimula/Component.py
13
+ src/OpenSimula/Iterative_process.py
13
14
  src/OpenSimula/Parameter_container.py
14
15
  src/OpenSimula/Parameters.py
15
16
  src/OpenSimula/Project.py
@@ -52,5 +53,6 @@ test/test_File_data.py
52
53
  test/test_File_met.py
53
54
  test/test_HVAC_DX_Equipment.py
54
55
  test/test_HVAC_DX_system.py
56
+ test/test_Iterative_process.py
55
57
  test/test_Project.py
56
58
  test/test_Schedules.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: OpenSimula
3
- Version: 0.3.2
3
+ Version: 0.4.1
4
4
  Summary: Component-based time simulation environment in Python
5
5
  Author-email: "Juan F. Coronel" <jfc@us.es>
6
6
  Project-URL: Homepage, http://opensimula.org
@@ -4,7 +4,7 @@ requires = ["setuptools>=61.2", "wheel"]
4
4
 
5
5
  [project]
6
6
  name = "OpenSimula"
7
- version = "0.3.2"
7
+ version = "0.4.1"
8
8
  authors = [
9
9
  { name="Juan F. Coronel", email="jfc@us.es" },
10
10
  ]
@@ -0,0 +1,43 @@
1
+ import math
2
+
3
+ class Iterative_process:
4
+ def __init__(self, x_0, tol = 1e-5, lambda_min=0.1, lambda_max=1.0, n_ini_relax=2,rel_vel = 0.9):
5
+ """
6
+ Iterative process using the fixed-point method with adaptive relaxation coefficient
7
+
8
+ Parameters:
9
+ x_0: Initial value
10
+ tol: convergence tolerance
11
+ lambda_min, lambda_max: relaxation coefficient range
12
+ n_ini_relax: Number of iterations in which adaptive relaxation is initiated, must be greater than 1
13
+ """
14
+ self.x = [x_0]
15
+ self.tol = tol
16
+ self.lambda_min = lambda_min
17
+ self.lambda_max = lambda_max
18
+ self.lambda_i = lambda_max
19
+ self.n_ini_relax = n_ini_relax
20
+ self.rel_vel = rel_vel
21
+
22
+
23
+ def converged(self):
24
+ if len(self.x) == 1:
25
+ return False
26
+ else:
27
+ if abs(self.x[-1]-self.x[-2]) < self.tol:
28
+ return True
29
+ else:
30
+ return False
31
+
32
+ def x_next(self, proposed_x):
33
+ x_next = (1 - self.lambda_i) * self.x[-1] + self.lambda_i * proposed_x
34
+
35
+ # Ajuste dinámico de lambda (reduce si hay oscilaciones)
36
+ if len(self.x)>self.n_ini_relax:
37
+ #if abs(x_next - self.x[-1]) > abs(self.x[-1]- self.x[-2]):
38
+ self.lambda_i = max(self.lambda_min, self.lambda_i * self.rel_vel) # Reducimos lambda
39
+ #else:
40
+ #self.lambda_i = min(self.lambda_max, self.lambda_i * 1/self.rel_vel) # Aumentamos lambda
41
+
42
+ self.x.append(x_next)
43
+ return x_next
@@ -360,6 +360,7 @@ class Project(Parameter_container):
360
360
  self._sim_df = pd.DataFrame({"dates":self.dates()})
361
361
  self._sim_df["n_iterations"] = 0
362
362
  self._sim_df["last_component"] = "None"
363
+ self._sim_df["converged"] = True
363
364
 
364
365
  self._sim_.print(f"Simulating {self.parameter('name').value}: ...")
365
366
 
@@ -383,11 +384,15 @@ class Project(Parameter_container):
383
384
  converge = True
384
385
  n_iter += 1
385
386
  self._sim_df.at[i, "n_iterations"] = n_iter
387
+ self._sim_df.at[i, "converged"] = converge
386
388
  n_iter_acum += n_iter
387
389
  self._post_iteration_(i, date, daylight_saving, converge)
388
390
  date = date + dt.timedelta(0, delta_t)
389
391
 
390
392
  self._sim_.print("Simulation completed.")
393
+ n_not_converged = len(self._sim_df["converged"]) - self._sim_df["converged"].sum()
394
+ if n_not_converged > 0:
395
+ self._sim_.print(f"Warning: {n_not_converged} time steps did not converge.")
391
396
  self._post_simulation_()
392
397
 
393
398
  def _pre_simulation_(self, n_time_steps, delta_t):
@@ -4,4 +4,5 @@ OpenSimula is a Python Package for perform time simulations
4
4
 
5
5
  """
6
6
  from OpenSimula.Simulation import Simulation
7
- VERSION = "0.3.2"
7
+ from OpenSimula.Iterative_process import Iterative_process
8
+ VERSION = "0.4.1"
@@ -239,17 +239,21 @@ class File_met(Component):
239
239
  variable).values[time_i] = array[i] * (1 - f) + array[j] * f
240
240
 
241
241
  def _get_solar_interpolation_tuple_(self, datetime, solar_hour):
242
- day = datetime.timetuple().tm_yday # Día del año
242
+ day = datetime.timetuple().tm_yday # Day of the year
243
+ if (solar_hour - datetime.hour > 10): # solar hour from previous day
244
+ day = day - 1
245
+ elif (solar_hour - datetime.hour < -10): # solar hour next day
246
+ day = day + 1
243
247
  # El primer valor es a las 00:30
244
248
  index = solar_hour + (day-1)*24
245
249
  if index < 0:
246
- index = index + 8760
250
+ index = 0
247
251
  elif index >= 8760:
248
- index = index - 8760
252
+ index = 8760
249
253
  i = math.floor(index)
250
254
  j = i + 1
251
255
  if j >= 8760:
252
- j = 0
256
+ j = 8760
253
257
  f = index - i
254
258
  return (i, j, f)
255
259
 
@@ -11,8 +11,7 @@ class HVAC_DX_equipment(Component):
11
11
  self.add_parameter(Parameter_float("nominal_total_cooling_capacity", 0, "W", min=0))
12
12
  self.add_parameter(Parameter_float("nominal_sensible_cooling_capacity", 0, "W", min=0))
13
13
  self.add_parameter(Parameter_float("nominal_cooling_power", 0, "W", min=0))
14
- self.add_parameter(Parameter_float("no_load_power", 0, "W", min=0))
15
- self.add_parameter(Parameter_float("no_load_heat", 0, "W", min=0))
14
+ self.add_parameter(Parameter_float("indoor_fan_power", 0, "W", min=0))
16
15
  self.add_parameter(Parameter_float_list("nominal_cooling_conditions", [27, 19, 35], "ºC"))
17
16
  self.add_parameter(Parameter_math_exp("total_cooling_capacity_expression", "1", "frac"))
18
17
  self.add_parameter(Parameter_math_exp("sensible_cooling_capacity_expression", "1", "frac"))
@@ -25,6 +24,7 @@ class HVAC_DX_equipment(Component):
25
24
  self.add_parameter(Parameter_math_exp("heating_power_expression", "1", "frac"))
26
25
  self.add_parameter(Parameter_math_exp("COP_expression", "1", "frac"))
27
26
  self.add_parameter(Parameter_options("dry_coil_model", "SENSIBLE", ["TOTAL", "SENSIBLE"]))
27
+ self.add_parameter(Parameter_options("indoor_fan_operation", "CONTINUOUS", ["CONTINUOUS", "CICLING"]))
28
28
  self.add_parameter(Parameter_boolean("power_dry_coil_correction", True))
29
29
  self.add_parameter(Parameter_float_list("expression_max_values", [60,30,60,30,1.5,1], "-"))
30
30
  self.add_parameter(Parameter_float_list("expression_min_values", [0,0,-30,-30,0,0], "-"))
@@ -39,6 +39,11 @@ class HVAC_DX_equipment(Component):
39
39
  return errors
40
40
 
41
41
  def get_cooling_capacity(self,T_idb,T_iwb,T_odb,T_owb,F_air):
42
+ """
43
+ Returns (Q_tot,Q_sen) capacities.
44
+ If indoor_fan_operation is CONTINUOUS: It returns the values from the expressions (Gross capacity = Coil capacity)
45
+ If indoor_fan_operation is CICLING: It returns the expressions minus the indoor fan power (Net capacity = Gross capacity - indoor fan)
46
+ """
42
47
  total_capacity = self.parameter("nominal_total_cooling_capacity").value
43
48
  if total_capacity > 0:
44
49
  # variables dictonary
@@ -55,24 +60,39 @@ class HVAC_DX_equipment(Component):
55
60
  total_capacity = sensible_capacity
56
61
  elif self.parameter("dry_coil_model").value == "TOTAL":
57
62
  sensible_capacity = total_capacity
63
+ if self.parameter("indoor_fan_operation").value == "CICLING":
64
+ sensible_capacity = sensible_capacity - self.parameter("indoor_fan_power").value
65
+ total_capacity = total_capacity - self.parameter("indoor_fan_power").value
58
66
  return (total_capacity, sensible_capacity)
59
67
  else:
60
68
  return (0,0)
61
69
 
62
70
  def get_heating_capacity(self,T_idb,T_iwb,T_odb,T_owb,F_air):
71
+ """
72
+ Returns heat sensible capacity.
73
+ If indoor_fan_operation is CONTINUOUS: It returns the values from the expressions (Gross capacity = Coil capacity)
74
+ If indoor_fan_operation is CICLING: It returns the expressions plus the indoor fan power (Net capacity = Gross capacity + indoor fan) """
63
75
  capacity = self.parameter("nominal_heating_capacity").value
64
76
  if capacity > 0:
65
77
  # variables dictonary
66
78
  var_dic = self._var_state_dic([T_idb, T_iwb,T_odb,T_owb,F_air,0])
67
79
  # Capacity
68
80
  capacity = capacity * self.parameter("heating_capacity_expression").evaluate(var_dic)
81
+ if self.parameter("indoor_fan_operation").value == "CICLING":
82
+ capacity = capacity + self.parameter("indoor_fan_power").value
69
83
  return capacity
70
84
  else:
71
85
  return 0
72
86
 
73
87
  def get_cooling_state(self,T_idb,T_iwb,T_odb,T_owb,F_air,F_load):
88
+ """
89
+ Returns (Q_tot,Q_sen,Power,F_EER).
90
+ """
74
91
  total_capacity, sensible_capacity = self.get_cooling_capacity(T_idb,T_iwb,T_odb,T_owb,F_air)
75
92
  if total_capacity > 0:
93
+ if self.parameter("indoor_fan_operation").value == "CICLING": # Get gross capacities
94
+ total_capacity= total_capacity + self.parameter("indoor_fan_power").value
95
+ sensible_capacity= sensible_capacity + self.parameter("indoor_fan_power").value
76
96
  if (F_load > 0):
77
97
  # variables dictonary
78
98
  var_dic = self._var_state_dic([T_idb, T_iwb,T_odb,T_owb,F_air,F_load])
@@ -82,10 +102,16 @@ class HVAC_DX_equipment(Component):
82
102
  EER_full = total_capacity/power_full
83
103
  F_EER = self.parameter("EER_expression").evaluate(var_dic)
84
104
  EER = EER_full * F_EER
85
- power = total_capacity*F_load/EER + self.parameter("no_load_power").value
105
+ if self.parameter("indoor_fan_operation").value == "CONTINUOUS":
106
+ power = total_capacity*F_load/EER + self.parameter("indoor_fan_power").value
107
+ elif self.parameter("indoor_fan_operation").value == "CICLING":
108
+ power = total_capacity*F_load/EER + self.parameter("indoor_fan_power").value*F_load/F_EER
86
109
  return (total_capacity*F_load, sensible_capacity*F_load, power, F_EER)
87
110
  else:
88
- return ( 0 , 0 , self.parameter("no_load_power").value, 0 )
111
+ if self.parameter("indoor_fan_operation").value == "CONTINUOUS":
112
+ return ( 0 , 0 , self.parameter("indoor_fan_power").value, 0 )
113
+ elif self.parameter("indoor_fan_operation").value == "CICLING":
114
+ return ( 0 , 0 , 0, 0 )
89
115
  else:
90
116
  return (0,0,0,0)
91
117
 
@@ -110,6 +136,8 @@ class HVAC_DX_equipment(Component):
110
136
  def get_heating_state(self,T_idb, T_iwb,T_odb,T_owb,F_air,F_load):
111
137
  capacity = self.get_heating_capacity(T_idb, T_iwb, T_odb,T_owb,F_air)
112
138
  if capacity > 0:
139
+ if self.parameter("indoor_fan_operation").value == "CICLING": # Get gross capacities
140
+ capacity= capacity - self.parameter("indoor_fan_power").value
113
141
  if (F_load > 0):
114
142
  # variables dictonary
115
143
  var_dic = self._var_state_dic([T_idb, T_iwb,T_odb,T_owb,F_air,F_load])
@@ -119,12 +147,24 @@ class HVAC_DX_equipment(Component):
119
147
  COP_full = capacity/power_full
120
148
  F_COP = self.parameter("COP_expression").evaluate(var_dic)
121
149
  COP = COP_full * F_COP
122
- power = capacity*F_load/COP + + self.parameter("no_load_power").value
150
+ if self.parameter("indoor_fan_operation").value == "CONTINUOUS":
151
+ power = capacity*F_load/COP + self.parameter("indoor_fan_power").value
152
+ elif self.parameter("indoor_fan_operation").value == "CICLING":
153
+ power = capacity*F_load/COP + self.parameter("indoor_fan_power").value*F_load/F_COP
123
154
  return (capacity*F_load, power, F_COP)
124
155
  else:
125
- return (0,self.parameter("no_load_power").value, 0)
156
+ if self.parameter("indoor_fan_operation").value == "CONTINUOUS":
157
+ return ( 0 , 0 , self.parameter("indoor_fan_power").value, 0 )
158
+ elif self.parameter("indoor_fan_operation").value == "CICLING":
159
+ return ( 0 , 0 , 0, 0 )
126
160
  else:
127
161
  return (0,0,0)
162
+
163
+ def get_no_load_power(self):
164
+ if self.parameter("indoor_fan_operation").value == "CONTINUOUS":
165
+ return self.parameter("indoor_fan_power").value
166
+ elif self.parameter("indoor_fan_operation").value == "CICLING":
167
+ return 0
128
168
 
129
169
  def _var_state_dic(self, values):
130
170
  max = self.parameter("expression_max_values").value
@@ -1,6 +1,7 @@
1
1
  from OpenSimula.Parameters import Parameter_component, Parameter_float, Parameter_variable_list, Parameter_math_exp, Parameter_options
2
2
  from OpenSimula.Component import Component
3
3
  from OpenSimula.Variable import Variable
4
+ from OpenSimula.Iterative_process import Iterative_process
4
5
  import psychrolib as sicro
5
6
 
6
7
 
@@ -21,8 +22,10 @@ class HVAC_DX_system(Component):
21
22
  self.add_parameter(Parameter_options("control_type", "PERFECT", ["PERFECT", "TEMPERATURE"]))
22
23
  self.add_parameter(Parameter_float("cooling_bandwidth", 1, "ºC", min=0))
23
24
  self.add_parameter(Parameter_float("heating_bandwidth", 1, "ºC", min=0))
24
- self.add_parameter(Parameter_float("relaxing_coefficient", 1, "frac", min=0, max=1))
25
-
25
+ self.add_parameter(Parameter_options("economizer", "NO", ["NO", "TEMPERATURE","TEMPERATURE_NOT_INTEGRATED","ENTHALPY","ENTHALPY_LIMITED"]))
26
+ self.add_parameter(Parameter_float("economizer_DT", 0, "ºC", min=0))
27
+ self.add_parameter(Parameter_float("economizer_enthalpy_limit", 0, "kJ/kg", min=0))
28
+
26
29
  # Variables
27
30
  self.add_variable(Variable("state", unit="flag")) # 0: 0ff, 1: Heating, 2: Heating max cap, -1:Cooling, -2:Cooling max cap, 3: Venting
28
31
  self.add_variable(Variable("T_odb", unit="°C"))
@@ -63,6 +66,11 @@ class HVAC_DX_system(Component):
63
66
  if self.parameter("file_met").value == "not_defined":
64
67
  errors.append(
65
68
  f"Error: {self.parameter('name').value}, file_met must be defined.")
69
+ # Test enthalpy economizer not compatible with temperature control type
70
+ if (self.parameter("economizer").value == "ENTHALPY" or self.parameter("economizer").value == "ENTHALPY_LIMITED") and self.parameter("control_type").value == "TEMPERATURE":
71
+ errors.append(
72
+ f"Error: {self.parameter('name').value}, enthalpy economizer not compatible with temperature control type. control typed changed to PERFECT.")
73
+ self.parameter("control_type").value = "PERFECT"
66
74
  return errors
67
75
 
68
76
  def pre_simulation(self, n_time_steps, delta_t):
@@ -88,10 +96,10 @@ class HVAC_DX_system(Component):
88
96
  self.input_var_variable.append(
89
97
  self.parameter("input_variables").variable[i])
90
98
  self._f_load = 0
91
- self._r_coef = self.parameter("relaxing_coefficient").value
92
- self._n_max_iter = self.project().parameter("n_max_iteration").value
93
- self._no_load_heat = self._equipment.parameter("no_load_heat").value
94
- self._no_load_power = self._equipment.parameter("no_load_power").value
99
+
100
+ self._no_load_power = self._equipment.get_no_load_power()
101
+ self._economizer = self.parameter("economizer").value != "NO"
102
+ self._economizer_DT = self.parameter("economizer_DT").value
95
103
 
96
104
  def pre_iteration(self, time_index, date, daylight_saving):
97
105
  super().pre_iteration(time_index, date, daylight_saving)
@@ -125,7 +133,7 @@ class HVAC_DX_system(Component):
125
133
  self._on_off = False
126
134
  else:
127
135
  self._on_off = True
128
- self._f_load_pre = self._f_load
136
+ #self._f_load_pre = self._f_load
129
137
 
130
138
  # Add uncontrolled ventilation to the space
131
139
  if self._on_off:
@@ -133,12 +141,12 @@ class HVAC_DX_system(Component):
133
141
  "V": self._outdoor_air_flow,
134
142
  "T":self._T_odb,
135
143
  "w": self._w_o,
136
- "Q":self._no_load_heat,
144
+ "Q":self._no_load_power,
137
145
  "M": 0}
138
146
  self._space.add_uncontrol_system(system_dic)
139
- self._M_w_pre = 0
140
- self._Q_sen_pre = 0
141
- self._f_load_pre = 0
147
+ # Iterative Process for outdoor air fraction with economizer
148
+ self.itera_Foa = Iterative_process(self._outdoor_air_flow/self._supply_air_flow,tol=0.01,n_ini_relax=3,rel_vel=0.8)
149
+
142
150
 
143
151
  def iteration(self, time_index, date, daylight_saving, n_iter):
144
152
  super().iteration(time_index, date, daylight_saving, n_iter)
@@ -146,14 +154,17 @@ class HVAC_DX_system(Component):
146
154
  if self._on_off:
147
155
  self._T_space = self._space.variable("temperature").values[time_index]
148
156
  self._w_space = self._space.variable("abs_humidity").values[time_index]
149
- # Mix air
150
- 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)
151
157
  if self.parameter("control_type").value == "PERFECT":
152
158
  control= self._perfect_control(n_iter)
153
159
  elif self.parameter("control_type").value == "TEMPERATURE":
154
160
  control = self._air_temperature_control(n_iter)
155
161
  self._space.set_control_system(control)
156
- return True
162
+
163
+ # Test convergence
164
+ if self._on_off and self.parameter("control_type").value == "TEMPERATURE" and self._economizer:
165
+ return self.itera_Foa.converged()
166
+ else:
167
+ return True
157
168
 
158
169
  def _mix_air(self, f, T1, w1, T2, w2):
159
170
  T = f * T1 + (1-f)*T2
@@ -165,12 +176,18 @@ class HVAC_DX_system(Component):
165
176
  return (T,w,T_wb)
166
177
 
167
178
  def _perfect_control(self, n_iter):
168
- Q_required = self._space.get_Q_required(self._T_cool_sp, self._T_heat_sp)
169
179
  # Venting
170
180
  state = 3
171
181
  f_load = 0
172
182
  Q_sen = 0
173
183
  M_w = 0
184
+
185
+ # Economizer
186
+ Q_required = self._economizer_perfect()
187
+
188
+ # Mix air
189
+ 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)
190
+
174
191
  if Q_required > 0: # Heating
175
192
  heat_cap = self._equipment.get_heating_capacity(self._T_idb, self._T_iwb, self._T_odb, self._T_owb,self._f_air)
176
193
  if heat_cap > 0:
@@ -197,27 +214,72 @@ class HVAC_DX_system(Component):
197
214
  Q_tot = tot_cool_cap*f_load
198
215
  M_w = (Q_tot - Q_sen) / self.DH_W
199
216
  # relaxing coef
200
- r_coef = (0.01-self._r_coef)/self._n_max_iter * n_iter + self._r_coef
201
- self._M_w = r_coef * M_w + (1-r_coef)*self._M_w_pre # Always relaxing
202
- self._Q_sen = r_coef * Q_sen + (1-r_coef)*self._Q_sen_pre
203
- self._f_load = r_coef * f_load + (1-r_coef)*self._f_load_pre
217
+ #r_coef = (0.01-self._r_coef)/self._n_max_iter * n_iter + self._r_coef
218
+ #self._M_w = r_coef * M_w + (1-r_coef)*self._M_w_pre # Always relaxing
219
+ #self._Q_sen = r_coef * Q_sen + (1-r_coef)*self._Q_sen_pre
220
+ #self._f_load = r_coef * f_load + (1-r_coef)*self._f_load_pre
221
+ #self._state = state
222
+ #self._M_w_pre = self._M_w
223
+ #self._Q_sen_pre = self._Q_sen
224
+ #self._f_load_pre = self._f_load
225
+
204
226
  self._state = state
205
- self._M_w_pre = self._M_w
206
- self._Q_sen_pre = self._Q_sen
207
- self._f_load_pre = self._f_load
227
+ self._Q_sen = Q_sen
228
+ self._M_w = M_w
229
+ self._f_load = f_load
208
230
 
209
231
  control = {"V": 0, "T": 0, "w":0, "Q":self._Q_sen, "M":self._M_w }
210
232
  return control
211
-
233
+
234
+ def _economizer_perfect(self):
235
+ Q_required = self._space.get_Q_required(self._T_cool_sp, self._T_heat_sp)
236
+ if (self._economizer):
237
+ if (self.parameter("economizer").value == "TEMPERATURE" or self.parameter("economizer").value == "TEMPERATURE_NOT_INTEGRATED"):
238
+ use_economizer = self._T_odb < self._T_space-self._economizer_DT
239
+ elif (self.parameter("economizer").value == "ENTHALPY"):
240
+ h_odb = sicro.GetMoistAirEnthalpy(self._T_odb,self._w_o/1000)
241
+ h_space = sicro.GetMoistAirEnthalpy(self._T_space,self._w_space/1000)
242
+ use_economizer = h_odb < h_space
243
+ elif (self.parameter("economizer").value == "ENTHALPY_LIMITED"):
244
+ h_odb = sicro.GetMoistAirEnthalpy(self._T_odb,self._w_o/1000)
245
+ use_economizer = h_odb < self.parameter("economizer_enthalpy_limit").value * 1000
246
+ if (Q_required < 0 and use_economizer):
247
+ Q_rest_ae = self._mrcp * (1-self._f_oa)*(self._T_odb - self._T_space)
248
+ if Q_rest_ae < Q_required:
249
+ self._f_oa += Q_required/(self._mrcp*(self._T_odb-self._T_space))
250
+ Q_required = 0
251
+ else:
252
+ if (self.parameter("economizer").value == "TEMPERATURE_NOT_INTEGRATED"):
253
+ self._f_oa = self._outdoor_air_flow/self._supply_air_flow
254
+ elif (self.parameter("economizer").value == "TEMPERATURE"):
255
+ self._f_oa = 1
256
+ else:
257
+ self._f_oa = self._outdoor_air_flow/self._supply_air_flow
258
+ system_dic = {"name": self.parameter("name").value,
259
+ "V": self._supply_air_flow*self._f_oa,
260
+ "T":self._T_odb,
261
+ "w": self._w_o,
262
+ "Q":self._no_load_power,
263
+ "M": 0}
264
+ self._space.add_uncontrol_system(system_dic)
265
+ Q_required = self._space.get_Q_required(self._T_cool_sp, self._T_heat_sp)
266
+ return Q_required
267
+
268
+
212
269
  def _air_temperature_control(self, n_iter):
213
- K_tot, F_tot = self._space._calculate_K_F_tot(False) # Space Equation
214
- T_flo = F_tot/K_tot
270
+
215
271
  # Venting
216
272
  state = 3
217
273
  f_load = 0
218
274
  Q_sen = 0
219
275
  M_w = 0
220
276
 
277
+ # Economizer
278
+ T_flo, K_tot, F_tot = self._economizer_temperature()
279
+
280
+ # Mix air
281
+ 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)
282
+
221
283
  if (T_flo >= self._T_cool_sp - self._cool_band/2):
222
284
  tot_cool_cap, sen_cool_cap = self._equipment.get_cooling_capacity(self._T_idb, self._T_iwb, self._T_odb,self._T_owb, self._f_air)
223
285
  if sen_cool_cap > 0:
@@ -225,7 +287,7 @@ class HVAC_DX_system(Component):
225
287
  if T_max_cap > self._T_cool_sp + self._cool_band/2:
226
288
  state = -2
227
289
  f_load = -1
228
- Q_sen = sen_cool_cap
290
+ Q_sen = -sen_cool_cap
229
291
  else:
230
292
  state = -1
231
293
  T_c = (F_tot+sen_cool_cap*(self._T_cool_sp - self._cool_band/2)/self._cool_band)/(K_tot + sen_cool_cap /self._cool_band)
@@ -249,18 +311,54 @@ class HVAC_DX_system(Component):
249
311
  state = 1
250
312
 
251
313
  # relaxing coef
252
- r_coef = (0.01-self._r_coef)/self._n_max_iter * n_iter + self._r_coef
253
- self._M_w = r_coef * M_w + (1-r_coef)*self._M_w_pre
254
- self._Q_sen = r_coef * Q_sen + (1-r_coef)*self._Q_sen_pre
255
- self._f_load = r_coef * f_load + (1-r_coef)*self._f_load_pre
256
- self._state = state
257
- self._M_w_pre = self._M_w
258
- self._Q_sen_pre = self._Q_sen
259
- self._f_load_pre = self._f_load
314
+ #r_coef = (0.01-self._r_coef)/self._n_max_iter * n_iter + self._r_coef
315
+ #self._M_w = r_coef * M_w + (1-r_coef)*self._M_w_pre
316
+ #self._Q_sen = r_coef * Q_sen + (1-r_coef)*self._Q_sen_pre
317
+ #self._f_load = r_coef * f_load + (1-r_coef)*self._f_load_pre
318
+ #self._state = state
319
+ #self._M_w_pre = self._M_w
320
+ #self._Q_sen_pre = self._Q_sen
321
+ #self._f_load_pre = self._f_load
260
322
 
323
+ self._state = state
324
+ self._Q_sen = Q_sen
325
+ self._M_w = M_w
326
+ self._f_load = f_load
327
+
261
328
  control = {"V": 0, "T": 0, "w":0, "Q":self._Q_sen, "M":self._M_w }
262
329
  return control
263
330
 
331
+ def _economizer_temperature(self):
332
+ K_tot, F_tot = self._space._calculate_K_F_tot(False) # Space Equation
333
+ T_flo = F_tot/K_tot
334
+ if (self._economizer):
335
+ if (self._T_odb < self._T_space - self._economizer_DT):
336
+ if (T_flo >= self._T_cool_sp - 1.5*self._cool_band and T_flo < self._T_cool_sp - 0.5*self._cool_band ):
337
+ f_oa_min = self._outdoor_air_flow/self._supply_air_flow
338
+ T_min = self._T_cool_sp - 1.5*self._cool_band
339
+ f_oa = (T_flo - T_min)/self._cool_band*(1-f_oa_min) + f_oa_min
340
+ elif (T_flo >= self._T_cool_sp - 0.5*self._cool_band):
341
+ if (self.parameter("economizer").value == "TEMPERATURE_NOT_INTEGRATED"):
342
+ f_oa = self._outdoor_air_flow/self._supply_air_flow
343
+ elif (self.parameter("economizer").value == "TEMPERATURE"):
344
+ f_oa = 1
345
+ else:
346
+ f_oa = self._outdoor_air_flow/self._supply_air_flow
347
+ else:
348
+ f_oa = self._outdoor_air_flow/self._supply_air_flow
349
+
350
+ self._f_oa = self.itera_Foa.x_next(f_oa)
351
+ system_dic = {"name": self.parameter("name").value,
352
+ "V": self._supply_air_flow*self._f_oa,
353
+ "T":self._T_odb,
354
+ "w": self._w_o,
355
+ "Q":self._no_load_power,
356
+ "M": 0}
357
+ self._space.add_uncontrol_system(system_dic)
358
+ K_tot, F_tot = self._space._calculate_K_F_tot(False) # Space Equation
359
+ T_flo = F_tot/K_tot
360
+ return T_flo, K_tot, F_tot
361
+
264
362
  def post_iteration(self, time_index, date, daylight_saving, converged):
265
363
  super().post_iteration(time_index, date, daylight_saving, converged)
266
364
  self.variable("state").values[time_index] = self._state
@@ -274,6 +372,7 @@ class HVAC_DX_system(Component):
274
372
  self.variable("F_air").values[time_index] = self._f_air
275
373
  self.variable("F_load").values[time_index] = self._f_load
276
374
  self.variable("power").values[time_index] = self._no_load_power
375
+ self.variable("outdoor_air_flow").values[time_index] = self._supply_air_flow * self._f_oa
277
376
 
278
377
  if self._state == 1 or self._state == 2: # Heating
279
378
  Q,power,F_COP = self._equipment.get_heating_state(self._T_idb,self._T_iwb,self._T_odb,self._T_owb,self._f_air,self._f_load)
@@ -1,6 +1,7 @@
1
1
  from OpenSimula.Component import Component
2
- from OpenSimula.Parameters import Parameter_component, Parameter_float, Parameter_boolean
2
+ from OpenSimula.Parameters import Parameter_component, Parameter_float
3
3
  from OpenSimula.Variable import Variable
4
+ from OpenSimula.Iterative_process import Iterative_process
4
5
  import numpy as np
5
6
  import psychrolib as sicro
6
7
  import math
@@ -18,7 +19,7 @@ class Space(Component):
18
19
  self.add_parameter(Parameter_float("volume", 1, "m³", min=0.0))
19
20
  self.add_parameter(Parameter_float("furniture_weight", 10, "kg/m²", min=0.0))
20
21
  self.add_parameter(Parameter_float("convergence_DT", 0.01, "°C", min=0.0))
21
- self.add_parameter(Parameter_float("convergence_Dw", 0.001, "g/kg", min=0.0))
22
+ self.add_parameter(Parameter_float("convergence_Dw", 0.01, "g/kg", min=0.0))
22
23
 
23
24
  # Variables
24
25
  self.add_variable(Variable("temperature", unit="°C"))
@@ -226,12 +227,17 @@ class Space(Component):
226
227
  self.control_system = {"V": 0, "T": 0, "w":0, "Q": 0, "M": 0} # V (m^3/s), T (°C), w (g/kg), Q (W), M (gr H2O)
227
228
  # Initial values
228
229
  self._estimate_T_w(time_index)
230
+ # Iterative Process
231
+ self.itera_T = Iterative_process(self._T,tol=self.parameter("convergence_DT").value,n_ini_relax=3,rel_vel=0.8)
232
+ self.itera_w = Iterative_process(self._w,tol=self.parameter("convergence_Dw").value,n_ini_relax=3,rel_vel=0.8)
233
+
229
234
  # Humidity balance
230
235
  rho = self.building().RHO
231
236
  lam = self.building().LAMBDA
232
237
  self._K_hum = rho * (self._volume / self._Dt + V_inf)
233
238
  self._F_hum = (self.variable("people_latent").values[time_index] + self.variable("other_gains_latent").values[time_index])/lam
234
239
  self._F_hum += rho * self._volume / self._Dt * self._w_pre + rho * V_inf * self._file_met.variable("abs_humidity").values[time_index]
240
+
235
241
 
236
242
  def _estimate_T_w(self, time_i):
237
243
  if time_i == 0:
@@ -247,12 +253,12 @@ class Space(Component):
247
253
 
248
254
  def iteration(self, time_index, date, daylight_saving, n_iter):
249
255
  super().iteration(time_index, date, daylight_saving, n_iter)
250
- force_iteration = False
251
256
 
252
257
  # Calculate temperature
253
258
  K_tot,F_tot = self._calculate_K_F_tot(True)
254
259
  T = F_tot/K_tot
255
- self.variable("temperature").values[time_index] = T
260
+ self.variable("temperature").values[time_index] = self.itera_T.x_next(T)
261
+
256
262
  # Calculate humidity
257
263
  K_hum, F_hum = self._calculate_K_F_hum(True)
258
264
  w = F_hum/K_hum
@@ -261,18 +267,13 @@ class Space(Component):
261
267
  if (T< 100): # Sicro limits
262
268
  max_hum = sicro.GetHumRatioFromRelHum(T, 1, self.building().ATM_PRESSURE)*1000
263
269
  if (w > max_hum):
264
- self.variable("abs_humidity").values[time_index] = max_hum
265
- force_iteration = True
266
- else:
267
- self.variable("abs_humidity").values[time_index] = w
268
- else:
269
- self.variable("abs_humidity").values[time_index] = w
270
+ w = max_hum
271
+
272
+ self.variable("abs_humidity").values[time_index] = self.itera_w.x_next(w)
273
+
270
274
  # Test convergence
271
- converged = self._converged(time_index)
272
- if force_iteration:
273
- return False
274
- else:
275
- return converged
275
+ converged = self.itera_T.converged() and self.itera_w.converged()
276
+ return converged
276
277
 
277
278
  def _calculate_solar_direct(self, time_i):
278
279
  solar_gain = 0
@@ -319,16 +320,6 @@ class Space(Component):
319
320
  F_hum += self.control_system["V"]*rho*self.control_system["w"] + self.control_system["M"]
320
321
  return (K_hum, F_hum)
321
322
 
322
- def _converged(self, time_i):
323
- converged = True
324
- if abs(self._T -self.variable("temperature").values[time_i]) > self.parameter("convergence_DT").value:
325
- converged = False
326
- if abs(self._w -self.variable("abs_humidity").values[time_i]) > self.parameter("convergence_Dw").value:
327
- converged = False
328
- self._T = self.variable("temperature").values[time_i]
329
- self._w = self.variable("abs_humidity").values[time_i]
330
- return converged
331
-
332
323
  def get_Q_required(self, T_cool_sp, T_heat_sp):
333
324
  K_tot, F_tot = self._calculate_K_F_tot(False)
334
325
  T = F_tot/K_tot
@@ -11,7 +11,7 @@ case_dict = {
11
11
  "nominal_total_cooling_capacity": 7951,
12
12
  "nominal_sensible_cooling_capacity": 6136,
13
13
  "nominal_cooling_power": 2198,
14
- "no_load_cooling_power": 230,
14
+ "indoor_fan_power": 230,
15
15
  "nominal_cooling_conditions": [26.7,19.4,35],
16
16
  "total_cooling_capacity_expression": "9.099e-04 * T_odb + 4.351e-02 * T_iwb -3.475e-05 * T_odb**2 + 1.512e-04 * T_iwb**2 -4.703e-04 * T_odb * T_iwb + 4.281e-01",
17
17
  "sensible_cooling_capacity_expression": "1.148e-03 * T_odb - 7.886e-02 * T_iwb + 1.044e-01 * T_idb - 4.117e-05 * T_odb**2 - 3.917e-03 * T_iwb**2 - 2.450e-03 * T_idb**2 + 4.042e-04 * T_odb * T_iwb - 4.762e-04 * T_odb * T_idb + 5.503e-03 * T_iwb * T_idb + 2.903e-01",
@@ -35,17 +35,17 @@ def test_points():
35
35
 
36
36
  assert nominal[0] == pytest.approx(7951,rel=1e-2)
37
37
  assert nominal[1] == pytest.approx(6136,rel=1e-2)
38
- assert nominal[2] == pytest.approx(2198,rel=1e-2)
38
+ assert nominal[2] == pytest.approx(2198+230,rel=1e-2)
39
39
 
40
40
  assert wet_coil[0] == pytest.approx(7570,rel=1e-2)
41
41
  assert wet_coil[1] == pytest.approx(6280,rel=1e-2)
42
- assert wet_coil[2] == pytest.approx(2078,rel=1e-2)
42
+ assert wet_coil[2] == pytest.approx(2078+230,rel=1e-2)
43
43
 
44
44
  assert dry_coil[0] == pytest.approx(6900,rel=1e-2)
45
45
  assert dry_coil[1] == pytest.approx(6900,rel=1e-2)
46
- assert dry_coil[2] == pytest.approx(2480,rel=1e-2)
46
+ assert dry_coil[2] == pytest.approx(2480+230,rel=1e-2)
47
47
 
48
48
  assert wet_coil_part_load[0] == pytest.approx( wet_coil[0]*.3)
49
49
  assert wet_coil_part_load[1] == pytest.approx( wet_coil[1]*.3)
50
- assert wet_coil_part_load[2] == pytest.approx( wet_coil[2]*0.3/(1 - 0.229*0.7))
50
+ assert wet_coil_part_load[2] == pytest.approx( (wet_coil[2]-230)*0.3/(1 - 0.229*0.7) +230)
51
51
 
@@ -331,8 +331,7 @@ case610_dict = {
331
331
  "nominal_total_cooling_capacity": 6900,
332
332
  "nominal_sensible_cooling_capacity": 4800,
333
333
  "nominal_cooling_power": 2400,
334
- "no_load_power": 240,
335
- "no_load_heat": 240,
334
+ "indoor_fan_power": 240,
336
335
  "total_cooling_capacity_expression": "0.88078 + 0.014248 * T_iwb + 0.00055436 * T_iwb**2 - 0.0075581 * T_odb + 3.2983E-05 * T_odb**2 - 0.00019171 * T_odb * T_iwb",
337
336
  "sensible_cooling_capacity_expression": "0.50060 - 0.046438 * T_iwb - 0.00032472 * T_iwb**2 - 0.013202 * T_odb + 7.9307E-05 * T_odb**2 + 0.069958 * T_idb - 3.4276E-05 * T_idb**2",
338
337
  "cooling_power_expression": "0.11178 + 0.028493 * T_iwb - 0.00041116 * T_iwb**2 + 0.021414 * T_odb + 0.00016113 * T_odb**2 - 0.00067910 * T_odb * T_iwb",
@@ -0,0 +1,13 @@
1
+ import OpenSimula as osm
2
+ import math
3
+ import pytest
4
+
5
+ def test_Iterative_process():
6
+ def y(x):
7
+ return math.cos(x)
8
+ x_i = 0.5
9
+ itera = osm.Iterative_process(x_i)
10
+ while not itera.converged():
11
+ x_i = itera.x_next(y(x_i))
12
+
13
+ assert x_i == pytest.approx(0.73908,abs=1e-4)
File without changes
File without changes