OpenSimula 0.6.0__tar.gz → 0.7.0__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.
Files changed (61) hide show
  1. {opensimula-0.6.0 → opensimula-0.7.0}/PKG-INFO +6 -7
  2. {opensimula-0.6.0 → opensimula-0.7.0}/README.md +5 -6
  3. {opensimula-0.6.0 → opensimula-0.7.0}/pyproject.toml +2 -1
  4. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/Project.py +43 -8
  5. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/Simulation.py +10 -4
  6. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/Variable.py +4 -0
  7. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/__init__.py +2 -1
  8. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Building.py +1 -1
  9. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Building_surface.py +226 -51
  10. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Construction.py +14 -0
  11. opensimula-0.6.0/src/opensimula/components/HVAC_DX_equipment.py → opensimula-0.7.0/src/opensimula/components/DX_unit.py +3 -3
  12. opensimula-0.6.0/src/opensimula/components/HVAC_fan_equipment.py → opensimula-0.7.0/src/opensimula/components/Fan.py +3 -3
  13. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/File_met.py +98 -15
  14. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/HVAC_DX_system.py +1 -1
  15. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/HVAC_MZW_system.py +5 -5
  16. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/HVAC_SZW_system.py +4 -4
  17. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/HVAC_perfect_system.py +7 -2
  18. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Material.py +7 -0
  19. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Space.py +1 -1
  20. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Surface.py +13 -1
  21. opensimula-0.6.0/src/opensimula/components/HVAC_coil_equipment.py → opensimula-0.7.0/src/opensimula/components/Water_coil.py +3 -3
  22. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/__init__.py +3 -3
  23. opensimula-0.7.0/src/opensimula/visual_3D/Environment_3D.py +467 -0
  24. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula.egg-info/PKG-INFO +6 -7
  25. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula.egg-info/SOURCES.txt +3 -3
  26. {opensimula-0.6.0 → opensimula-0.7.0}/test/test_Calculator.py +2 -2
  27. {opensimula-0.6.0 → opensimula-0.7.0}/test/test_Case_600FF.py +2 -2
  28. {opensimula-0.6.0 → opensimula-0.7.0}/test/test_Case_610.py +2 -2
  29. {opensimula-0.6.0 → opensimula-0.7.0}/test/test_Construction.py +4 -4
  30. {opensimula-0.6.0 → opensimula-0.7.0}/test/test_File_data.py +5 -5
  31. {opensimula-0.6.0 → opensimula-0.7.0}/test/test_File_met.py +39 -5
  32. {opensimula-0.6.0 → opensimula-0.7.0}/test/test_HVAC_DX_Equipment.py +4 -4
  33. {opensimula-0.6.0 → opensimula-0.7.0}/test/test_HVAC_DX_system.py +4 -4
  34. {opensimula-0.6.0 → opensimula-0.7.0}/test/test_Iterative_process.py +2 -2
  35. {opensimula-0.6.0 → opensimula-0.7.0}/test/test_Project.py +37 -7
  36. {opensimula-0.6.0 → opensimula-0.7.0}/test/test_Schedules.py +4 -4
  37. opensimula-0.6.0/src/opensimula/visual_3D/Environment_3D.py +0 -257
  38. {opensimula-0.6.0 → opensimula-0.7.0}/LICENSE +0 -0
  39. {opensimula-0.6.0 → opensimula-0.7.0}/setup.cfg +0 -0
  40. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/Child.py +0 -0
  41. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/Component.py +0 -0
  42. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/Iterative_process.py +0 -0
  43. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/Message.py +0 -0
  44. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/Parameter_container.py +0 -0
  45. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/Parameters.py +0 -0
  46. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Calculator.py +0 -0
  47. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Day_schedule.py +0 -0
  48. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/File_data.py +0 -0
  49. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Frame.py +0 -0
  50. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Glazing.py +0 -0
  51. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Opening.py +0 -0
  52. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Opening_type.py +0 -0
  53. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Solar_surface.py +0 -0
  54. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Space_type.py +0 -0
  55. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Test_component.py +0 -0
  56. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Week_schedule.py +0 -0
  57. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/components/Year_schedule.py +0 -0
  58. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula/visual_3D/Polygon_3D.py +0 -0
  59. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula.egg-info/dependency_links.txt +0 -0
  60. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula.egg-info/requires.txt +0 -0
  61. {opensimula-0.6.0 → opensimula-0.7.0}/src/opensimula.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensimula
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Component-based time simulation environment in Python
5
5
  Author-email: "Juan F. Coronel" <jfc@us.es>
6
6
  Requires-Python: >=3.12
@@ -41,9 +41,8 @@ Component-based time simulation environment in Python
41
41
  - dash
42
42
  - dash_bootstrap_components
43
43
  - dash_ag_grid
44
- - pyvista
45
- - trame
46
- - trame-vtk
47
- - trame-vuetify
48
- - ipywidgets
49
-
44
+ - nbformat
45
+ - openpyxl
46
+ - tqdm
47
+ - triangle
48
+ - vedo
@@ -17,9 +17,8 @@ Component-based time simulation environment in Python
17
17
  - dash
18
18
  - dash_bootstrap_components
19
19
  - dash_ag_grid
20
- - pyvista
21
- - trame
22
- - trame-vtk
23
- - trame-vuetify
24
- - ipywidgets
25
-
20
+ - nbformat
21
+ - openpyxl
22
+ - tqdm
23
+ - triangle
24
+ - vedo
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "opensimula"
3
- version = "0.6.0"
3
+ version = "0.7.0"
4
4
  requires-python = ">=3.12"
5
5
  authors = [
6
6
  { name="Juan F. Coronel", email="jfc@us.es" },
@@ -29,6 +29,7 @@ dev = [
29
29
  "ipykernel>=6.30.1",
30
30
  "mkdocs>=1.6.1",
31
31
  "pytest>=8.4.2",
32
+ "marimo>=0.19.0",
32
33
  ]
33
34
 
34
35
  [build-system]
@@ -158,6 +158,41 @@ class Project(Parameter_container):
158
158
  except KeyError:
159
159
  return None
160
160
 
161
+ def duplicate_component(self, component_name, new_name):
162
+ """Duplicate an existing component with a new name
163
+
164
+ Args:
165
+ component_name (string): name of the component to duplicate
166
+ new_name (string): name for the new component
167
+
168
+ Returns:
169
+ Component: the new duplicated component, None if error
170
+ """
171
+ original = self.component(component_name)
172
+ if original is None:
173
+ msg = self._get_error_header_() + f"Component '{component_name}' not found."
174
+ self._sim_.message(Message(msg, "ERROR"))
175
+ return None
176
+
177
+ # Get parameter dictionary from original component
178
+ comp_dict = {}
179
+ for key, param in original.parameter_dict().items():
180
+ comp_dict[key] = param.value
181
+
182
+ # Create new component of the same type
183
+ new_comp = self.new_component(comp_dict["type"], new_name)
184
+ if new_comp is None:
185
+ msg = self._get_error_header_() + f"Could not create component of type '{comp_dict['type']}'."
186
+ self._sim_.message(Message(msg, "ERROR"))
187
+ return None
188
+
189
+ # Copy parameters (except name and type)
190
+ for key, value in comp_dict.items():
191
+ if key != "name" and key != "type":
192
+ new_comp.parameter(key).value = value
193
+
194
+ return new_comp
195
+
161
196
  def _get_error_header_(self):
162
197
  return f'Project "{self.parameter("name").value}". '
163
198
 
@@ -641,23 +676,23 @@ class Project(Parameter_container):
641
676
  if hasattr(component, "get_polygon_3D"):
642
677
  env_3D.add_polygon_3D(component.get_polygon_3D())
643
678
 
644
- def show_3D(self):
679
+ def show_3D(self, jupyter=False):
645
680
  env_3D = Environment_3D()
646
681
  self.create_3D_environment(env_3D)
647
- env_3D.show(polygons_type="initial")
648
-
649
- def show_3D_shadows(self, date):
682
+ env_3D.show(polygons_type="initial", jupyter=jupyter)
683
+
684
+ def show_3D_shadows(self, date, jupyter=False):
650
685
  env_3D = Environment_3D()
651
686
  self.create_3D_environment(env_3D)
652
687
  file_met = self.parameter("simulation_file_met").component
653
688
  cos = file_met.sun_cosines(date)
654
689
  if len(cos) == 3:
655
690
  env_3D.calculate_shadows(cos)
656
- env_3D.show(polygons_type="Building_shadows")
691
+ env_3D.show(polygons_type="Building_shadows", jupyter=jupyter)
657
692
  else:
658
693
  self._sim_.message(Message(date.strftime("%H:%M, %d/%m/%Y") + " is night", "WARNING"))
659
694
 
660
- def show_3D_shadows_animation(self, date):
695
+ def show_3D_shadows_animation(self, date, jupyter=False):
661
696
  env_3D = Environment_3D()
662
697
  self.create_3D_environment(env_3D)
663
698
  file_met = self.parameter("simulation_file_met").component
@@ -666,9 +701,9 @@ class Project(Parameter_container):
666
701
  for hour in range(24):
667
702
  new_date = date.replace(hour=hour, minute=0, second=0)
668
703
  cos = file_met.sun_cosines(new_date)
669
- if len(cos) == 3: # Day time
704
+ if len(cos) == 3: # Day time
670
705
  texts.append(new_date.strftime("%H:%M, %d/%m/%Y"))
671
706
  cosines.append(cos)
672
- env_3D.show_animation(texts, cosines, polygons_type="Building_shadows")
707
+ env_3D.show_animation(texts, cosines, polygons_type="Building_shadows", jupyter=jupyter)
673
708
 
674
709
 
@@ -114,8 +114,11 @@ class Simulation:
114
114
  if i < len(names):
115
115
  series[names[i]] = variables[i].values
116
116
  else:
117
- series[variables[i].parent.parameter(
118
- "name").value+":"+variables[i].key] = variables[i].values
117
+ if variables[i].parent is not None:
118
+ series[variables[i].parent.parameter(
119
+ "name").value+":"+variables[i].key] = variables[i].values
120
+ else:
121
+ series[variables[i].key] = variables[i].values
119
122
  data = pd.DataFrame(series)
120
123
  if frequency is not None:
121
124
  if value == "mean":
@@ -134,8 +137,11 @@ class Simulation:
134
137
  if i < len(names):
135
138
  name = names[i]
136
139
  else:
137
- name = variables[i].parent.parameter(
138
- "name").value+":"+variables[i].key
140
+ if variables[i].parent is not None:
141
+ name = variables[i].parent.parameter(
142
+ "name").value+":"+variables[i].key
143
+ else:
144
+ name = variables[i].key
139
145
  fig = px.line(data, x='date', y=name)
140
146
  fig.for_each_trace(lambda t: t.update(name=name))
141
147
  fig.update_traces(showlegend=True)
@@ -25,6 +25,10 @@ class Variable(Child):
25
25
  def values(self):
26
26
  return self._values_
27
27
 
28
+ @values.setter
29
+ def values(self, values):
30
+ self._values_ = values
31
+
28
32
  @property
29
33
  def unit(self):
30
34
  return self._unit_
@@ -5,4 +5,5 @@ opensimula is a Python Package for perform time simulations
5
5
  """
6
6
  from opensimula.Simulation import Simulation
7
7
  from opensimula.Iterative_process import Iterative_process
8
- VERSION = "0.6.0"
8
+ from importlib.metadata import version
9
+ VERSION = version("opensimula")
@@ -168,7 +168,7 @@ class Building(Component):
168
168
  if self.spaces[j] == self.surfaces[i].get_space():
169
169
  self.KSZ_matrix[i][j] = (
170
170
  self.surfaces[i].area
171
- * self.surfaces[i].parameter("h_cv").value[0]
171
+ * self.surfaces[i].parameter("h_cv").value[self.sides[i]]
172
172
  )
173
173
  elif surface_type == "INTERIOR":
174
174
  k = self.surfaces[i].k
@@ -1,7 +1,13 @@
1
1
  import math
2
2
  from opensimula.Message import Message
3
3
  from opensimula.components.Surface import Surface
4
- from opensimula.Parameters import Parameter_component, Parameter_float_list, Parameter_options, Parameter_component_list
4
+ from opensimula.Parameters import (
5
+ Parameter_component,
6
+ Parameter_float,
7
+ Parameter_float_list,
8
+ Parameter_options,
9
+ Parameter_component_list,
10
+ )
5
11
  from opensimula.Variable import Variable
6
12
  from opensimula.visual_3D.Polygon_3D import Polygon_3D
7
13
 
@@ -12,10 +18,35 @@ class Building_surface(Surface):
12
18
  # Parameters
13
19
  self.parameter("type").value = "Building_surface"
14
20
  self.parameter("description").value = "Building surface"
15
- self.add_parameter(Parameter_component("construction", "not_defined", ["Construction"]))
16
- self.add_parameter(Parameter_component_list("spaces", ["not_defined", "not_defined"], ["Space"]))
17
- self.add_parameter(Parameter_options("surface_type", "EXTERIOR", ["EXTERIOR", "INTERIOR", "UNDERGROUND", "VIRTUAL"]))
21
+ self.add_parameter(
22
+ Parameter_component("construction", "not_defined", ["Construction"])
23
+ )
24
+ self.add_parameter(
25
+ Parameter_component_list(
26
+ "spaces", ["not_defined", "not_defined"], ["Space"]
27
+ )
28
+ )
29
+ self.add_parameter(
30
+ Parameter_options(
31
+ "surface_type",
32
+ "EXTERIOR",
33
+ ["EXTERIOR", "INTERIOR", "UNDERGROUND", "VIRTUAL"],
34
+ )
35
+ )
18
36
  self.add_parameter(Parameter_float_list("h_cv", [19.3, 2], "W/m²K", min=0))
37
+ # Only for underground surfaces
38
+ self.add_parameter(
39
+ Parameter_component("ground_material", "not_defined", ["Material"])
40
+ )
41
+ self.add_parameter(
42
+ Parameter_float("exterior_perimeter_fraction", 1, "frac", min=0, max=1)
43
+ )
44
+ self.add_parameter(
45
+ Parameter_float("exterior_perimeter_wall_thickness", 0.3, "m", min=0)
46
+ )
47
+ self.add_parameter(
48
+ Parameter_float("groundwater_depth", 0, "m", min=0)
49
+ )
19
50
 
20
51
  # Constants
21
52
  self.H_RD = 5.705 # 4*sigma*(293^3)
@@ -46,12 +77,12 @@ class Building_surface(Surface):
46
77
  def check(self):
47
78
  errors = super().check()
48
79
  # Test space defined
49
- surface_type = self.parameter("surface_type").value
50
- if surface_type == "EXTERIOR" or surface_type == "UNDERGROUND":
80
+ self._surface_type_ = self.parameter("surface_type").value
81
+ if self._surface_type_ == "EXTERIOR" or self._surface_type_ == "UNDERGROUND":
51
82
  if self.parameter("spaces").value[0] == "not_defined":
52
83
  msg = f"{self.parameter('name').value}, must define its space."
53
84
  errors.append(Message(msg, "ERROR"))
54
- elif surface_type == "INTERIOR" or surface_type == "VIRTUAL":
85
+ elif self._surface_type_ == "INTERIOR" or self._surface_type_ == "VIRTUAL":
55
86
  if (
56
87
  self.parameter("spaces").value[0] == "not_defined"
57
88
  or self.parameter("spaces").value[1] == "not_defined"
@@ -59,10 +90,20 @@ class Building_surface(Surface):
59
90
  msg = f"{self.parameter('name').value}, must define two spaces."
60
91
  errors.append(Message(msg, "ERROR"))
61
92
  # Test construction defined for non-virtual surfaces
62
- if surface_type != "VIRTUAL" and self.parameter("construction").value == "not_defined":
93
+ if (
94
+ self._surface_type_ != "VIRTUAL"
95
+ and self.parameter("construction").value == "not_defined"
96
+ ):
63
97
  msg = f"{self.parameter('name').value}, Building surfaces must define its construction."
64
98
  errors.append(Message(msg, "ERROR"))
65
- self._surface_type_ = self.parameter("surface_type").value
99
+ # Test ground material defined for underground surfaces
100
+ if (
101
+ self._surface_type_ == "UNDERGROUND"
102
+ and self.parameter("ground_material").value == "not_defined"
103
+ ):
104
+ msg = f"{self.parameter('name').value}, Underground surfaces must define its ground material."
105
+ errors.append(Message(msg, "ERROR"))
106
+ # Create openings list
66
107
  if self._surface_type_ == "EXTERIOR" or self._surface_type_ == "INTERIOR":
67
108
  self._create_openings_list_()
68
109
  return errors
@@ -70,17 +111,19 @@ class Building_surface(Surface):
70
111
  def get_building(self):
71
112
  return self.parameter("spaces").component[0].get_building()
72
113
 
73
- def get_space(self,side=0):
114
+ def get_space(self, side=0):
74
115
  return self.parameter("spaces").component[side]
75
-
116
+
76
117
  def radiant_property(self, prop, radiation_type, side, theta=0):
77
- if self.parameter("surface_type").value == "VIRTUAL":
118
+ if self._surface_type_ == "VIRTUAL":
78
119
  if prop == "tau":
79
120
  return 1
80
121
  else:
81
122
  return 0
82
123
  else:
83
- return self.parameter("construction").component.radiant_property(prop, radiation_type, side, theta)
124
+ return self.parameter("construction").component.radiant_property(
125
+ prop, radiation_type, side, theta
126
+ )
84
127
 
85
128
  # ____________ pre_simulation ____________
86
129
  def pre_simulation(self, n_time_steps, delta_t):
@@ -90,12 +133,102 @@ class Building_surface(Surface):
90
133
  self._T_ini = self.get_building().parameter("initial_temperature").value
91
134
  self._F_sky = (1 + math.sin(math.radians(self.parameter("altitude").value))) / 2
92
135
  self._surface_type_ = self.parameter("surface_type").value
136
+ self._construction_ = self.parameter("construction").component
93
137
  if self._surface_type_ == "EXTERIOR" or self._surface_type_ == "INTERIOR":
94
138
  self._create_openings_list_()
95
- if self._surface_type_ != "VIRTUAL":
96
139
  self._calculate_K_()
97
140
  if self._surface_type_ == "EXTERIOR":
98
- self._sunny_index_ = self.project().env_3D.get_sunny_index(self.parameter("name").value)
141
+ self._sunny_index_ = self.project().env_3D.get_sunny_index(
142
+ self.parameter("name").value
143
+ )
144
+ if self._surface_type_ == "UNDERGROUND":
145
+ self._create_underground_construction_()
146
+ self._construction_.pre_simulation(
147
+ n_time_steps, delta_t
148
+ ) # is not included in list of project components
149
+ self._calculate_K_()
150
+ self._calculate_ground_U_hat_()
151
+
152
+ def _create_underground_construction_(self):
153
+ # Test if underground construction already exists and delete it
154
+ und_cons_name = self.parameter("name").value + "_und_cons"
155
+ if self.project().component(und_cons_name) is not None:
156
+ self.project().del_component(self.project().component(und_cons_name))
157
+ # Test if virtual material already exists and delete it
158
+ vir_mat_name = self.parameter("name").value + "_vir_mat"
159
+ if self.project().component(vir_mat_name) is not None:
160
+ self.project().del_component(self.project().component(vir_mat_name))
161
+ # create virtual material with zero properties
162
+ vir_mat = self.project().new_component("Material", vir_mat_name)
163
+ vir_mat.set_parameters({"density": 1, "specific_heat": 1, "conductivity": 1})
164
+ # create new construction for underground surfaces
165
+ self._construction_ = self.project().duplicate_component(
166
+ self.parameter("construction").component.parameter("name").value,
167
+ und_cons_name,
168
+ )
169
+ # Equivalent thickness UNE-EN ISO 13370:2017
170
+ self._B_prime_ = (
171
+ 2
172
+ * self.area
173
+ / (self.perimeter * self.parameter("exterior_perimeter_fraction").value)
174
+ )
175
+ R_cons = self._construction_.thermal_resistance() # m²K/W
176
+ self._k_g_ = (
177
+ self.parameter("ground_material").component.parameter("conductivity").value
178
+ )
179
+ w = self.parameter("exterior_perimeter_wall_thickness").value # Thickness of perimetral walls
180
+ self._d_t_ = w + self._k_g_ * (
181
+ 1 / self.parameter("h_cv").value[0]
182
+ + 1 / self.parameter("h_cv").value[1]
183
+ + R_cons
184
+ ) # m
185
+
186
+ if self._d_t_ < self._B_prime_:
187
+ self._U_tot_ = (
188
+ (2 * self._k_g_)
189
+ / (math.pi * self._B_prime_ + self._d_t_)
190
+ * math.log((math.pi * self._B_prime_ / self._d_t_) + 1)
191
+ )
192
+ else: # Well insulated
193
+ self._U_tot_ = self._k_g_ / (self._d_t_ + 0.457 * self._B_prime_)
194
+
195
+ # Gorundwater effect
196
+ groundwater_depth = self.parameter("groundwater_depth").value
197
+ self._G_w_ = 1.0
198
+ if groundwater_depth > 0:
199
+ self._G_w_ = 1 + math.exp(-2*groundwater_depth / self._B_prime_)
200
+ self._U_tot_ *= self._G_w_
201
+
202
+ # adding 0,5m of ground material t
203
+ self._ground_thickness_ = 0.5 # fixed 50 cm.
204
+ self._construction_.add_exterior_layer(
205
+ self.parameter("ground_material").component.parameter("name").value,
206
+ self._ground_thickness_,
207
+ )
208
+ # Thermal resistanace of the virtual layer
209
+ R_vi = (
210
+ 1 / self._U_tot_
211
+ - 1 / self.parameter("h_cv").value[1]
212
+ - R_cons
213
+ - 0.5 / self._k_g_
214
+ ) # m²K/W
215
+ k_vi = 0.1 / R_vi
216
+ # Add virtual layer to the construction
217
+ vir_mat.parameter("conductivity").value = k_vi
218
+ self._construction_.add_exterior_layer(vir_mat_name, 0.1)
219
+
220
+ def _calculate_ground_U_hat_(self):
221
+ # Exterior temperature from meteorological file
222
+ # Dinamics transmitance (U_hat and U_hat_ext)
223
+ periodo_segundos = 31536000 # Un año
224
+ rho_g = self.parameter("ground_material").component.parameter("density").value
225
+ c_p_g = (
226
+ self.parameter("ground_material").component.parameter("specific_heat").value
227
+ )
228
+ d_p = math.sqrt((self._k_g_ * periodo_segundos) / (math.pi * rho_g * c_p_g))
229
+ self._U_hat_ext_ = self._G_w_ * self._k_g_ / math.sqrt(
230
+ (0.457 * self._B_prime_ + self._d_t_) ** 2 + d_p**2
231
+ )
99
232
 
100
233
  def _create_openings_list_(self):
101
234
  project_openings_list = self.project().component_list(comp_type="Opening")
@@ -105,46 +238,55 @@ class Building_surface(Surface):
105
238
  self.openings.append(opening)
106
239
 
107
240
  def _calculate_K_(self):
108
- self.a_0, self.a_1, self.a_01 = self.parameter("construction").component.get_A()
241
+ self.a_0, self.a_1, self.a_01 = self._construction_.get_A()
109
242
  self.k_01 = self.area * self.a_01
110
- surface_type = self.parameter("surface_type").value
111
- if surface_type == "EXTERIOR":
112
- self.k[0] = self.area * (self.a_0
243
+ if self._surface_type_ == "EXTERIOR":
244
+ self.k[0] = self.area * (
245
+ self.a_0
113
246
  - self.parameter("h_cv").value[0]
114
247
  - self.H_RD * self.radiant_property("alpha", "long_wave", 0)
115
248
  )
116
249
  self.k[1] = self.area * (self.a_1 - self.parameter("h_cv").value[1])
117
- elif surface_type == "INTERIOR":
118
- # interior
250
+ elif self._surface_type_ == "INTERIOR":
119
251
  self.k[0] = self.area * (self.a_0 - self.parameter("h_cv").value[0])
120
252
  self.k[1] = self.area * (self.a_1 - self.parameter("h_cv").value[1])
121
- elif surface_type == "UNDERGROUND":
122
- # Underground
253
+ elif self._surface_type_ == "UNDERGROUND":
123
254
  self.k[0] = 1 # not used
124
- self.k[1] = self.area * (self.a_1 - self.parameter("h_cv").value[0]) # only one defined
125
-
255
+ self.k[1] = self.area * (
256
+ self.a_1 - self.parameter("h_cv").value[1]
257
+ ) # only one defined
126
258
 
127
- #________ pre_iteration ____________
259
+ # ________ pre_iteration ____________
128
260
  def pre_iteration(self, time_i, date, daylight_saving):
129
261
  super().pre_iteration(time_i, date, daylight_saving)
130
262
  # Meterological data
131
263
  self._T_ext = self._file_met.variable("temperature").values[time_i]
132
264
  if self._surface_type_ != "VIRTUAL":
133
265
  # Trasient part
134
- self._p_0_, self._p_1_ = self.parameter("construction").component.get_P(
266
+ self._p_0_, self._p_1_ = self._construction_.get_P(
135
267
  time_i,
136
268
  self.variable("T_s0").values,
137
269
  self.variable("T_s1").values,
138
270
  self.variable("q_cd0").values,
139
271
  self.variable("q_cd1").values,
140
- self._T_ini)
272
+ self._T_ini,
273
+ )
141
274
  self.variable("p_0").values[time_i] = self._p_0_
142
275
  self.variable("p_1").values[time_i] = self._p_1_
143
276
  if self._surface_type_ == "EXTERIOR":
144
277
  self._pre_iteration_exterior_(time_i)
145
- elif self._surface_type_ == "UNDERGROUND":
146
- self.variable("T_s0").values[time_i] = self._file_met.variable("underground_temperature").values[time_i]
147
-
278
+ if self._surface_type_ == "UNDERGROUND":
279
+ year_start = date.replace(month=1, day=1, hour=0, minute=0, second=0)
280
+ seconds_elapsed = (date - year_start).total_seconds()
281
+ DT = self._file_met.DT_monthly_amplitude/2
282
+ T_avg = self._file_met.T_average
283
+ tau = self._file_met.month_min_temp # One month of delay for the minimum temperature, as in UNE-EN ISO 13370:2017
284
+ m = seconds_elapsed / 2628000
285
+ self.variable("T_s0").values[time_i] = (
286
+ T_avg
287
+ - self._U_hat_ext_ / self._U_tot_ * DT * math.cos(2 * math.pi * (m-tau) / 12)
288
+ )
289
+
148
290
  def _pre_iteration_exterior_(self, time_i):
149
291
  # Meterological data
150
292
  hor_sol_dif = self._file_met.variable("sol_diffuse").values[time_i]
@@ -153,33 +295,40 @@ class Building_surface(Surface):
153
295
 
154
296
  # Diffuse solar radiation
155
297
  E_dif_sunny = self._file_met.solar_diffuse_rad(
156
- time_i,
157
- self.orientation_angle("azimuth", 0),
158
- self.orientation_angle("altitude", 0),
159
- )
160
- E_dif_sunny = E_dif_sunny + (1 - self._F_sky) * self._albedo * (hor_sol_dif + hor_sol_dir)
298
+ time_i,
299
+ self.orientation_angle("azimuth", 0),
300
+ self.orientation_angle("altitude", 0),
301
+ )
302
+ E_dif_sunny = E_dif_sunny + (1 - self._F_sky) * self._albedo * (
303
+ hor_sol_dif + hor_sol_dir
304
+ )
161
305
  self.variable("E_dif_sunny").values[time_i] = E_dif_sunny
162
- diffuse_sunny_fraction = self.project().env_3D.get_diffuse_sunny_fraction(self._sunny_index_)
306
+ diffuse_sunny_fraction = self.project().env_3D.get_diffuse_sunny_fraction(
307
+ self._sunny_index_
308
+ )
163
309
  E_dif = E_dif_sunny * diffuse_sunny_fraction
164
310
  self.variable("E_dif").values[time_i] = E_dif
165
311
  # Direct solar radiation
166
312
  E_dir_sunny = self._file_met.solar_direct_rad(
167
- time_i,
168
- self.orientation_angle("azimuth", 0),
169
- self.orientation_angle("altitude", 0),
313
+ time_i,
314
+ self.orientation_angle("azimuth", 0),
315
+ self.orientation_angle("altitude", 0),
170
316
  )
171
317
  self.variable("E_dir_sunny").values[time_i] = E_dir_sunny
172
318
  E_dir = E_dir_sunny * self._calculate_direct_sunny_fraction_(time_i)
173
319
  self.variable("E_dir").values[time_i] = E_dir
174
320
  q_sol0 = self.radiant_property("alpha", "solar_diffuse", 0) * (E_dif + E_dir)
175
321
  self.variable("q_sol0").values[time_i] = q_sol0
176
- # Mean radiant temperature
322
+ # Mean radiant temperature
177
323
  T_rm = self._F_sky * T_sky + (1 - self._F_sky) * self._T_ext
178
324
  self.variable("T_rm").values[time_i] = T_rm
179
325
  # f_0
180
326
  h_rd = self.H_RD * self.radiant_property("alpha", "long_wave", 0)
181
327
  self.f_0 = self.area * (
182
- -self._p_0_ - self.parameter("h_cv").value[0] * self._T_ext - h_rd * T_rm - q_sol0
328
+ -self._p_0_
329
+ - self.parameter("h_cv").value[0] * self._T_ext
330
+ - h_rd * T_rm
331
+ - q_sol0
183
332
  )
184
333
 
185
334
  def _calculate_direct_sunny_fraction_(self, time_i):
@@ -202,8 +351,7 @@ class Building_surface(Surface):
202
351
  direct_sunny_fraction = 1
203
352
  return direct_sunny_fraction
204
353
 
205
-
206
- #________ post_iteration ____________
354
+ # ________ post_iteration ____________
207
355
  def post_iteration(self, time_index, date, daylight_saving, converged):
208
356
  super().post_iteration(time_index, date, daylight_saving, converged)
209
357
  if self._surface_type_ == "EXTERIOR":
@@ -236,7 +384,8 @@ class Building_surface(Surface):
236
384
  )
237
385
  h_rd = self.H_RD * self.radiant_property("alpha", "long_wave", 0)
238
386
  self.variable("q_lwt0").values[time_i] = h_rd * (
239
- self.variable("T_rm").values[time_i] - self.variable("T_s0").values[time_i]
387
+ self.variable("T_rm").values[time_i]
388
+ - self.variable("T_s0").values[time_i]
240
389
  )
241
390
  self.variable("q_lwt1").values[time_i] = (
242
391
  -self.variable("q_cd1").values[time_i]
@@ -269,8 +418,10 @@ class Building_surface(Surface):
269
418
  - self.variable("q_lwig1").values[time_i]
270
419
  )
271
420
  elif self._surface_type_ == "UNDERGROUND":
272
- self.variable("q_cv0").values[time_i] = -self.variable("q_cd0").values[time_i]
273
- self.variable("q_cv1").values[time_i] = self.parameter("h_cv").value * (
421
+ self.variable("q_cv0").values[time_i] = -self.variable("q_cd0").values[
422
+ time_i
423
+ ]
424
+ self.variable("q_cv1").values[time_i] = self.parameter("h_cv").value[1] * (
274
425
  self.get_space().variable("temperature").values[time_i]
275
426
  - self.variable("T_s1").values[time_i]
276
427
  )
@@ -306,13 +457,37 @@ class Building_surface(Surface):
306
457
  )
307
458
  elif self._surface_type_ == "INTERIOR":
308
459
  return Polygon_3D(
309
- name, origin, azimuth, altitude, pol_2D, holes_2D, color="green", shading=False, calculate_shadows=False
460
+ name,
461
+ origin,
462
+ azimuth,
463
+ altitude,
464
+ pol_2D,
465
+ holes_2D,
466
+ color="green",
467
+ shading=False,
468
+ calculate_shadows=False,
310
469
  )
311
- elif self._surface_type_ == "UNDERGROUND":
470
+ elif self._surface_type_ == "UNDERGROUND":
312
471
  return Polygon_3D(
313
- name, origin, azimuth, altitude, pol_2D, holes_2D, color="brown", shading=False, calculate_shadows=False
472
+ name,
473
+ origin,
474
+ azimuth,
475
+ altitude,
476
+ pol_2D,
477
+ holes_2D,
478
+ color="brown",
479
+ shading=False,
480
+ calculate_shadows=False,
314
481
  )
315
482
  elif self._surface_type_ == "VIRTUAL":
316
483
  return Polygon_3D(
317
- name, origin, azimuth, altitude, pol_2D, color="green", opacity=0.4, shading=False, calculate_shadows=False
484
+ name,
485
+ origin,
486
+ azimuth,
487
+ altitude,
488
+ pol_2D,
489
+ color="green",
490
+ opacity=0.4,
491
+ shading=False,
492
+ calculate_shadows=False,
318
493
  )
@@ -28,6 +28,20 @@ class Construction(Component):
28
28
  errors.append(Message(msg, "error"))
29
29
  return errors
30
30
 
31
+ def add_exterior_layer(self, material_name, thickness):
32
+ """Add a new exterior layer (at index 0) to the construction.
33
+
34
+ Args:
35
+ material_name (str): Name of the material component to add
36
+ thickness (float): Thickness of the layer in meters
37
+ """
38
+ materials = self.parameter("materials").value
39
+ thicknesses = self.parameter("thicknesses").value
40
+ materials.insert(0, material_name)
41
+ thicknesses.insert(0, thickness)
42
+ self.parameter("materials").value = materials
43
+ self.parameter("thicknesses").value = thicknesses
44
+
31
45
  # Functions for Transfer Function Calculation
32
46
 
33
47
  def pre_simulation(self, n_time_steps, delta_t):
@@ -3,11 +3,11 @@ from opensimula.Parameters import Parameter_float, Parameter_float_list, Paramet
3
3
  from opensimula.Component import Component
4
4
  from scipy.optimize import fsolve
5
5
 
6
- class HVAC_DX_equipment(Component):
6
+ class DX_unit(Component):
7
7
  def __init__(self, name, project):
8
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"
9
+ self.parameter("type").value = "DX_unit"
10
+ self.parameter("description").value = "Direct Expansion unit manufacturer information"
11
11
  self.add_parameter(Parameter_float("nominal_air_flow", 1, "m³/s", min=0))
12
12
  self.add_parameter(Parameter_float("nominal_total_cooling_capacity", 0, "W", min=0))
13
13
  self.add_parameter(Parameter_float("nominal_sensible_cooling_capacity", 0, "W", min=0))