iso52016 0.0.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.
Files changed (39) hide show
  1. iso52016-0.0.1/LICENSE +29 -0
  2. iso52016-0.0.1/PKG-INFO +109 -0
  3. iso52016-0.0.1/README.md +73 -0
  4. iso52016-0.0.1/iso52016/__init__.py +16 -0
  5. iso52016-0.0.1/iso52016/building.py +314 -0
  6. iso52016-0.0.1/iso52016/door.py +23 -0
  7. iso52016-0.0.1/iso52016/functions.py +241 -0
  8. iso52016-0.0.1/iso52016/generals.py +38 -0
  9. iso52016-0.0.1/iso52016/ground_temperature.py +262 -0
  10. iso52016-0.0.1/iso52016/layer.py +25 -0
  11. iso52016-0.0.1/iso52016/material.py +20 -0
  12. iso52016-0.0.1/iso52016/node.py +25 -0
  13. iso52016-0.0.1/iso52016/opaque_element.py +242 -0
  14. iso52016-0.0.1/iso52016/orientation.py +14 -0
  15. iso52016-0.0.1/iso52016/thermal_bridges.py +11 -0
  16. iso52016-0.0.1/iso52016/ventilation.py +19 -0
  17. iso52016-0.0.1/iso52016/weather.py +153 -0
  18. iso52016-0.0.1/iso52016/window.py +78 -0
  19. iso52016-0.0.1/iso52016/zone.py +101 -0
  20. iso52016-0.0.1/iso52016.egg-info/PKG-INFO +109 -0
  21. iso52016-0.0.1/iso52016.egg-info/SOURCES.txt +38 -0
  22. iso52016-0.0.1/iso52016.egg-info/dependency_links.txt +1 -0
  23. iso52016-0.0.1/iso52016.egg-info/requires.txt +16 -0
  24. iso52016-0.0.1/iso52016.egg-info/top_level.txt +1 -0
  25. iso52016-0.0.1/pyproject.toml +79 -0
  26. iso52016-0.0.1/setup.cfg +59 -0
  27. iso52016-0.0.1/setup.py +4 -0
  28. iso52016-0.0.1/tests/test_ASHRAE140_600.py +95 -0
  29. iso52016-0.0.1/tests/test_determine_hci.py +10 -0
  30. iso52016-0.0.1/tests/test_functions.py +125 -0
  31. iso52016-0.0.1/tests/test_ground_temperature.py +60 -0
  32. iso52016-0.0.1/tests/test_industrial.py +127 -0
  33. iso52016-0.0.1/tests/test_iso52016_heavy_example.py +293 -0
  34. iso52016-0.0.1/tests/test_iso52016_light_example.py +283 -0
  35. iso52016-0.0.1/tests/test_layer.py +104 -0
  36. iso52016-0.0.1/tests/test_multizone.py +60 -0
  37. iso52016-0.0.1/tests/test_simple_cases.py +694 -0
  38. iso52016-0.0.1/tests/test_validation.py +69 -0
  39. iso52016-0.0.1/tests/test_weather.py +116 -0
iso52016-0.0.1/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2023, Tobias Blanke
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: iso52016
3
+ Version: 0.0.1
4
+ Summary: Python package for a ISO 52016 calculation
5
+ Home-page: https://git.fh-aachen.de/tb5152e/din-en-iso-52016
6
+ Author: Tobias Blanke
7
+ Author-email: blanke@fh-aachen.de
8
+ License: BSD-3-Clause
9
+ Keywords: iso52016,building,energy,thermal,hvac,heating,cooling
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: License :: OSI Approved :: BSD License
16
+ Classifier: Operating System :: OS Independent
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: numpy>=1.24.4
21
+ Requires-Dist: pandas>=2.0.3
22
+ Requires-Dist: pvlib>=0.10.1
23
+ Requires-Dist: scipy>=1.10.1
24
+ Provides-Extra: dev
25
+ Requires-Dist: flake8>=4.0.1; extra == "dev"
26
+ Requires-Dist: pytest>=7.1.2; extra == "dev"
27
+ Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
28
+ Requires-Dist: mypy>=0.961; extra == "dev"
29
+ Requires-Dist: bandit>=1.7.4; extra == "dev"
30
+ Requires-Dist: pylint>=2.14.5; extra == "dev"
31
+ Requires-Dist: isort>=5.10.1; extra == "dev"
32
+ Requires-Dist: black>=22.6.0; extra == "dev"
33
+ Requires-Dist: anybadge==1.9.0; extra == "dev"
34
+ Requires-Dist: pandas-stubs>=2.1.1; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # DIN EN ISO 52016
38
+
39
+ [![pipeline status](https://git.fh-aachen.de/tb5152e/din-en-iso-52016/badges/main/pipeline.svg)](https://git.fh-aachen.de/tb5152e/din-en-iso-52016/-/commits/main)
40
+ [![coverage report](https://git.fh-aachen.de/tb5152e/din-en-iso-52016/badges/main/coverage.svg)](https://git.fh-aachen.de/tb5152e/din-en-iso-52016/-/commits/main)
41
+ [![Documentation Status](https://readthedocs.org/projects/din-en-iso-52016/badge/?version=latest)](https://din-en-iso-52016.readthedocs.io/en/latest/?badge=latest)
42
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
43
+
44
+ Python implementation of the **DIN EN ISO 52016** standard for calculating the energy performance of buildings. The package computes hourly heating and cooling loads for thermal zones using a 5-node thermal-network model per opaque element, solar irradiation via `pvlib`, and ground-temperature coupling per ISO 13370.
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ pip install .
50
+ ```
51
+
52
+ Or for development:
53
+
54
+ ```bash
55
+ pip install -e ".[dev]"
56
+ ```
57
+
58
+ ## Quick start
59
+
60
+ ```python
61
+ import numpy as np
62
+ from iso52016 import (
63
+ Building, Layer, Material, OpaqueElement, Orientation,
64
+ Ventilation, Weather, Window, Zone,
65
+ )
66
+
67
+ # Create materials and layers
68
+ concrete = Material(density_kg_m3=2300, thermal_capacity_J_kgK=880, conductivity_W_mK=1.7)
69
+ insulation = Material(density_kg_m3=20, thermal_capacity_J_kgK=1450, conductivity_W_mK=0.035)
70
+ wall_layers = [Layer(0.2, concrete), Layer(0.1, insulation)]
71
+
72
+ # Set up weather (8760 hourly values)
73
+ weather = Weather(air_temperature=t_air, wind=wind)
74
+
75
+ # Define building envelope
76
+ wall = OpaqueElement(area=50, orientation=Orientation(90, 0), layers=wall_layers)
77
+ # ... add more walls, roof, floor
78
+
79
+ zone = Zone(
80
+ opaque_elements=[wall, ...],
81
+ usable_floor_area=100,
82
+ internal_gains=[np.zeros(8760)],
83
+ lower_temperature_limit=20,
84
+ upper_temperature_limit=26,
85
+ )
86
+
87
+ building = Building(zones=zone, weather=weather)
88
+ building.calculate()
89
+
90
+ print(f"Heating: {zone.heat_load.sum() / 1000:.0f} kWh")
91
+ print(f"Cooling: {zone.cool_load.sum() / 1000:.0f} kWh")
92
+ ```
93
+
94
+ See `examples/easy_example.py` for a complete working example.
95
+
96
+ ## Features
97
+
98
+ - Hourly heating and cooling load calculation per ISO 52016
99
+ - 5-node thermal network for opaque elements, 2-node for windows
100
+ - Solar irradiation via `pvlib` (any orientation/tilt)
101
+ - Ground-temperature coupling per ISO 13370
102
+ - Multi-zone support
103
+ - Ventilation with heat recovery
104
+
105
+ ## License
106
+
107
+ BSD 3-Clause.
108
+
109
+ Independent implementation of the calculation method described in DIN EN ISO 52016-1. This project is not affiliated with, authorized by, or endorsed by ISO, CEN, or DIN, and does not reproduce the text of the standard.
@@ -0,0 +1,73 @@
1
+ # DIN EN ISO 52016
2
+
3
+ [![pipeline status](https://git.fh-aachen.de/tb5152e/din-en-iso-52016/badges/main/pipeline.svg)](https://git.fh-aachen.de/tb5152e/din-en-iso-52016/-/commits/main)
4
+ [![coverage report](https://git.fh-aachen.de/tb5152e/din-en-iso-52016/badges/main/coverage.svg)](https://git.fh-aachen.de/tb5152e/din-en-iso-52016/-/commits/main)
5
+ [![Documentation Status](https://readthedocs.org/projects/din-en-iso-52016/badge/?version=latest)](https://din-en-iso-52016.readthedocs.io/en/latest/?badge=latest)
6
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
7
+
8
+ Python implementation of the **DIN EN ISO 52016** standard for calculating the energy performance of buildings. The package computes hourly heating and cooling loads for thermal zones using a 5-node thermal-network model per opaque element, solar irradiation via `pvlib`, and ground-temperature coupling per ISO 13370.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ pip install .
14
+ ```
15
+
16
+ Or for development:
17
+
18
+ ```bash
19
+ pip install -e ".[dev]"
20
+ ```
21
+
22
+ ## Quick start
23
+
24
+ ```python
25
+ import numpy as np
26
+ from iso52016 import (
27
+ Building, Layer, Material, OpaqueElement, Orientation,
28
+ Ventilation, Weather, Window, Zone,
29
+ )
30
+
31
+ # Create materials and layers
32
+ concrete = Material(density_kg_m3=2300, thermal_capacity_J_kgK=880, conductivity_W_mK=1.7)
33
+ insulation = Material(density_kg_m3=20, thermal_capacity_J_kgK=1450, conductivity_W_mK=0.035)
34
+ wall_layers = [Layer(0.2, concrete), Layer(0.1, insulation)]
35
+
36
+ # Set up weather (8760 hourly values)
37
+ weather = Weather(air_temperature=t_air, wind=wind)
38
+
39
+ # Define building envelope
40
+ wall = OpaqueElement(area=50, orientation=Orientation(90, 0), layers=wall_layers)
41
+ # ... add more walls, roof, floor
42
+
43
+ zone = Zone(
44
+ opaque_elements=[wall, ...],
45
+ usable_floor_area=100,
46
+ internal_gains=[np.zeros(8760)],
47
+ lower_temperature_limit=20,
48
+ upper_temperature_limit=26,
49
+ )
50
+
51
+ building = Building(zones=zone, weather=weather)
52
+ building.calculate()
53
+
54
+ print(f"Heating: {zone.heat_load.sum() / 1000:.0f} kWh")
55
+ print(f"Cooling: {zone.cool_load.sum() / 1000:.0f} kWh")
56
+ ```
57
+
58
+ See `examples/easy_example.py` for a complete working example.
59
+
60
+ ## Features
61
+
62
+ - Hourly heating and cooling load calculation per ISO 52016
63
+ - 5-node thermal network for opaque elements, 2-node for windows
64
+ - Solar irradiation via `pvlib` (any orientation/tilt)
65
+ - Ground-temperature coupling per ISO 13370
66
+ - Multi-zone support
67
+ - Ventilation with heat recovery
68
+
69
+ ## License
70
+
71
+ BSD 3-Clause.
72
+
73
+ Independent implementation of the calculation method described in DIN EN ISO 52016-1. This project is not affiliated with, authorized by, or endorsed by ISO, CEN, or DIN, and does not reproduce the text of the standard.
@@ -0,0 +1,16 @@
1
+ """
2
+ iso 52016 base instance
3
+ """
4
+ from .building import Building
5
+ from .door import Door
6
+ from .functions import ThermalCase
7
+ from .generals import general
8
+ from .layer import Layer
9
+ from .material import Material
10
+ from .opaque_element import OpaqueElement
11
+ from .orientation import Orientation
12
+ from .thermal_bridges import ThermalBridge
13
+ from .ventilation import Ventilation
14
+ from .weather import Weather
15
+ from .window import Window
16
+ from .zone import Zone
@@ -0,0 +1,314 @@
1
+ """
2
+ iso 52016 classes
3
+ """
4
+ from __future__ import annotations
5
+
6
+ import typing
7
+
8
+ import numpy as np
9
+ from scipy import linalg # type: ignore
10
+
11
+ from .functions import ThermalCase, determine_case
12
+ from .generals import SECONDS_PER_HOUR, general
13
+
14
+ if typing.TYPE_CHECKING:
15
+ from numpy.typing import NDArray
16
+
17
+ from .weather import Weather
18
+ from .zone import Zone
19
+
20
+
21
+ class Building:
22
+ """
23
+ Building class for including the zones and weather and performing the calculation
24
+ """
25
+
26
+ def __init__(self, weather: Weather, zones: list[Zone] | Zone):
27
+ """
28
+ init building class
29
+ Parameters
30
+ ----------
31
+ weather: Weather
32
+ the weather in which the building is placed
33
+ zones: list[Zone]
34
+ list of all zones in the building
35
+ """
36
+ self.weather: Weather = weather
37
+ self.zones: list[Zone] = [zones] if not isinstance(zones, list) else zones
38
+ self._basic_matrix: NDArray[np.float64] = np.empty(0)
39
+
40
+ self.res_multiplication: NDArray[np.float64] = np.empty(0)
41
+ self.res_constant: NDArray[np.float64] = np.empty(0)
42
+
43
+ def set_node_indexes(self) -> None:
44
+ """
45
+ determines all nodes indexes for the matrix calculation
46
+ Returns
47
+ -------
48
+ None
49
+ """
50
+ number = 0
51
+ for zone in self.zones:
52
+ zone.operative_temperature.idx = number # + 1
53
+ number += 1
54
+ for area in zone.opaque_elements:
55
+ for node in area.nodes:
56
+ node.idx = number
57
+ number += 1
58
+ for window in area.windows:
59
+ for node in window.nodes:
60
+ node.idx = number
61
+ number += 1
62
+
63
+ zone.air_temperature.idx = number
64
+ zone.heat_load_var.idx = number + 1
65
+ zone.cool_load_var.idx = number + 2
66
+ number += 3
67
+
68
+ def calculate(self) -> None:
69
+ """
70
+ calculate the loads for all zones in this building
71
+
72
+ Returns
73
+ -------
74
+ None
75
+ """
76
+ nodes: int = sum(zone.nodes for zone in self.zones)
77
+ for zone in self.zones:
78
+ zone.update_values(self.weather)
79
+ _ = [element.set_default_external_temperature(self.weather.t_air) for element in zone.opaque_elements]
80
+
81
+ self.set_node_indexes()
82
+ self.create_basic_matrix(nodes)
83
+ self.create_results_matrices(nodes)
84
+ temps_old = np.ones(nodes) * 20
85
+ for time_step in range(general.simulation_length):
86
+ temps = self.determine_load(time_step, [ThermalCase.FREE_FLOAT] * len(self.zones), temps_old)
87
+ cases = [determine_case(temps, zone, time_step) for zone in self.zones]
88
+ if any(case != ThermalCase.FREE_FLOAT for case in cases):
89
+ temps = self.determine_load(time_step, cases, temps_old)
90
+ cases_new = [determine_case(temps, zone, time_step) for zone in self.zones]
91
+ if any(case_old != cases_new for case_old, case_new in zip(cases, cases_new)):
92
+ temps = self.determine_load(time_step, cases_new, temps_old)
93
+
94
+ temps_old = temps
95
+ for zone in self.zones:
96
+ zone.air_temperature[time_step] = temps[zone.air_temperature.idx]
97
+ zone.operative_temperature[time_step] = temps[zone.operative_temperature.idx]
98
+ zone.heat_load_var[time_step] = temps[zone.heat_load_var.idx]
99
+ zone.cool_load_var[time_step] = temps[zone.cool_load_var.idx]
100
+ for elem in zone.opaque_elements:
101
+ for node in elem.nodes:
102
+ node[time_step] = temps[node.idx]
103
+ for window in elem.windows:
104
+ for node in window.nodes:
105
+ node[time_step] = temps[node.idx]
106
+
107
+ def determine_load(self, time_step: int, cases: list[ThermalCase], temps_old: NDArray[np.float64]) -> NDArray[np.float64]:
108
+ """
109
+ solves the matrix
110
+
111
+ Parameters
112
+ ----------
113
+ time_step: int
114
+ time step
115
+ cases: list[ThermalCase]
116
+ list of thermal cases for the different zones
117
+ temps_old: NDArray[np.float64]
118
+ old temperature results array
119
+ Returns
120
+ -------
121
+ NDArray[np.float64]:
122
+ solved nodes matrix
123
+ """
124
+ mat = self._basic_matrix.copy()
125
+ res = self.set_general_result_vector_values(temps_old, time_step)
126
+ for zone, case in zip(self.zones, cases):
127
+ mat[zone.air_temperature.idx, zone.air_temperature.idx] += zone.h_vent[time_step]
128
+ if case == ThermalCase.FREE_FLOAT:
129
+ mat[zone.heat_load_var.idx, zone.heat_load_var.idx] = 1
130
+ mat[zone.cool_load_var.idx, zone.cool_load_var.idx] = 1
131
+ continue
132
+ if case == ThermalCase.HEATING:
133
+ mat[zone.heat_load_var.idx, zone.reference_temp.idx] = 1
134
+ mat[zone.cool_load_var.idx, zone.cool_load_var.idx] = 1
135
+ res[zone.heat_load_var.idx] = zone.t_min[time_step]
136
+ continue
137
+ if case == ThermalCase.MAX_HEATING:
138
+ mat[zone.heat_load_var.idx, zone.heat_load_var.idx] = 1
139
+ mat[zone.cool_load_var.idx, zone.cool_load_var.idx] = 1
140
+ res[zone.heat_load_var.idx] = zone.max_heat
141
+ continue
142
+ if case == ThermalCase.MAX_COOLING:
143
+ mat[zone.heat_load_var.idx, zone.heat_load_var.idx] = 1
144
+ mat[zone.cool_load_var.idx, zone.cool_load_var.idx] = 1
145
+ res[zone.cool_load_var.idx] = zone.max_cool
146
+ continue
147
+
148
+ mat[zone.cool_load_var.idx, zone.reference_temp.idx] = 1
149
+ mat[zone.heat_load_var.idx, zone.heat_load_var.idx] = 1
150
+ res[zone.cool_load_var.idx] = zone.t_max[time_step]
151
+
152
+ try:
153
+ temps = linalg.lu_solve(
154
+ linalg.lu_factor(mat[: res.size, : res.size], overwrite_a=False, check_finite=False), res, overwrite_b=False, check_finite=False
155
+ )
156
+ except linalg.LinAlgError as exc:
157
+ raise linalg.LinAlgError(
158
+ f"Singular matrix at time step {time_step}. "
159
+ "Check that all building elements have valid thermal properties (non-zero area, conductivity, etc.)."
160
+ ) from exc
161
+ return temps
162
+
163
+ def create_basic_matrix(self, nodes: int) -> None:
164
+ """
165
+ Create the basic matrix to use it later.
166
+
167
+ Parameters
168
+ ----------
169
+ nodes: int
170
+ number of nodes
171
+
172
+ Returns
173
+ -------
174
+ None
175
+ """
176
+ matrix = np.zeros((nodes, nodes))
177
+ for zone in self.zones:
178
+ self._fill_zone_rows(matrix, zone)
179
+ for elem in zone.opaque_elements:
180
+ self._fill_opaque_element_rows(matrix, zone, elem)
181
+ for window in elem.windows:
182
+ self._fill_window_rows(matrix, zone, elem, window)
183
+ self._basic_matrix = matrix
184
+
185
+ @staticmethod
186
+ def _other_surfaces(zone: Zone, exclude_parent: object) -> list:
187
+ """Collect all surfaces in the zone except *exclude_parent* and its own windows."""
188
+ return zone.opaque_elements + [w for e in zone.opaque_elements for w in e.windows if e != exclude_parent]
189
+
190
+ def _fill_zone_rows(self, matrix: NDArray[np.float64], zone: Zone) -> None:
191
+ """Fill the air-temperature and operative-temperature rows for a zone."""
192
+ all_surfaces = zone.opaque_elements + [w for e in zone.opaque_elements for w in e.windows]
193
+ matrix[zone.air_temperature.idx, zone.air_temperature.idx] = (
194
+ zone.c_internal / SECONDS_PER_HOUR + sum(s.area * s.h_ci for s in all_surfaces) + zone.h_th_bridge
195
+ )
196
+ matrix[zone.air_temperature.idx, zone.heat_load_var.idx] = -zone.f_heat_cool
197
+ matrix[zone.air_temperature.idx, zone.cool_load_var.idx] = zone.f_heat_cool
198
+ matrix[zone.operative_temperature.idx, zone.operative_temperature.idx] = -2
199
+ matrix[zone.operative_temperature.idx, zone.air_temperature.idx] = 1
200
+
201
+ def _fill_opaque_element_rows(self, matrix: NDArray[np.float64], zone: Zone, elem: object) -> None:
202
+ """Fill matrix rows for an opaque element's 5 nodes."""
203
+ # zone-level coupling
204
+ matrix[zone.air_temperature.idx, elem.nodes[4].idx] = -1 * elem.area * elem.h_ci
205
+ matrix[zone.operative_temperature.idx, elem.nodes[4].idx] = elem.area / zone.total_area
206
+ # internal surface node (node 4)
207
+ matrix[elem.nodes[4].idx, zone.air_temperature.idx] = -1 * elem.h_ci
208
+ for window in elem.windows:
209
+ matrix[window.nodes[1].idx, zone.air_temperature.idx] = -1 * window.h_ci
210
+ matrix[elem.nodes[4].idx, elem.nodes[3].idx] = -1 * elem.heat_coefficient[3]
211
+ others = [s for s in self._other_surfaces(zone, elem) if s != elem]
212
+ matrix[elem.nodes[4].idx, elem.nodes[4].idx] = (
213
+ elem.heat_coefficient[3]
214
+ + elem.h_ci
215
+ + elem.capacities[4] / SECONDS_PER_HOUR
216
+ + elem.h_ri / zone.total_area * sum(s.area for s in others)
217
+ )
218
+ matrix[elem.nodes[4].idx, zone.heat_load_var.idx] = -1 * (1 - zone.f_heat_cool) / zone.total_area
219
+ matrix[elem.nodes[4].idx, zone.cool_load_var.idx] = (1 - zone.f_heat_cool) / zone.total_area
220
+ # radiative exchange with other surfaces
221
+ for elem_i in zone.opaque_elements:
222
+ if elem_i == elem:
223
+ continue
224
+ matrix[elem.nodes[4].idx, elem_i.nodes[4].idx] = -1 * elem.h_ri * elem_i.area / zone.total_area
225
+ for window_i in elem_i.windows:
226
+ matrix[elem.nodes[4].idx, window_i.nodes[1].idx] = -1 * elem.h_ri * window_i.area / zone.total_area
227
+ # intermediate nodes (1-3)
228
+ for i in range(1, 4):
229
+ matrix[elem.nodes[i].idx, elem.nodes[i - 1].idx] = -1 * elem.heat_coefficient[i - 1]
230
+ matrix[elem.nodes[i].idx, elem.nodes[i].idx] = elem.capacities[i] / SECONDS_PER_HOUR + elem.heat_coefficient[i] + elem.heat_coefficient[i - 1]
231
+ matrix[elem.nodes[i].idx, elem.nodes[i + 1].idx] = -1 * elem.heat_coefficient[i]
232
+ # external node (node 0)
233
+ matrix[elem.nodes[0].idx, elem.nodes[0].idx] = elem.capacities[0] / SECONDS_PER_HOUR + elem.h_ce + elem.h_re + elem.heat_coefficient[0]
234
+ matrix[elem.nodes[0].idx, elem.nodes[1].idx] = -1 * elem.heat_coefficient[0]
235
+
236
+ def _fill_window_rows(self, matrix: NDArray[np.float64], zone: Zone, parent_elem: object, window: object) -> None:
237
+ """Fill matrix rows for a window's 2 nodes."""
238
+ matrix[zone.air_temperature.idx, window.nodes[1].idx] = -1 * window.area * window.h_ci
239
+ matrix[zone.operative_temperature.idx, window.nodes[1].idx] = window.area / zone.total_area
240
+ # external node
241
+ matrix[window.nodes[0].idx, window.nodes[0].idx] = window.h_ce + window.h_re + window.heat_coefficient
242
+ matrix[window.nodes[0].idx, window.nodes[1].idx] = -1 * window.heat_coefficient
243
+ # internal node
244
+ matrix[window.nodes[1].idx, window.nodes[0].idx] = -1 * window.heat_coefficient
245
+ others = [s for s in self._other_surfaces(zone, parent_elem) if s != parent_elem]
246
+ matrix[window.nodes[1].idx, window.nodes[1].idx] = (
247
+ window.heat_coefficient + window.h_ci + window.h_ri / zone.total_area * sum(s.area for s in others)
248
+ )
249
+ matrix[window.nodes[1].idx, zone.heat_load_var.idx] = -1 * (1 - zone.f_heat_cool) / zone.total_area
250
+ matrix[window.nodes[1].idx, zone.cool_load_var.idx] = (1 - zone.f_heat_cool) / zone.total_area
251
+ # radiative exchange with other surfaces
252
+ for elem_i in zone.opaque_elements:
253
+ if parent_elem == elem_i:
254
+ continue
255
+ matrix[window.nodes[1].idx, elem_i.nodes[4].idx] = -1 * window.h_ri * elem_i.area / zone.total_area
256
+ for window_i in elem_i.windows:
257
+ matrix[window.nodes[1].idx, window_i.nodes[1].idx] = -1 * window.h_ri * window_i.area / zone.total_area
258
+
259
+ def create_results_matrices(self, nodes: int) -> None:
260
+ """
261
+ create the results matrix
262
+
263
+ Parameters
264
+ ----------
265
+ nodes: int
266
+ number of nodes
267
+
268
+ Returns
269
+ -------
270
+ None
271
+ """
272
+ self.res_multiplication = np.zeros((nodes, nodes))
273
+ self.res_constant = np.zeros((nodes, general.simulation_length))
274
+
275
+ for zone in self.zones:
276
+ self.res_multiplication[zone.air_temperature.idx, zone.air_temperature.idx] = zone.c_internal / SECONDS_PER_HOUR
277
+ self.res_constant[zone.air_temperature.idx, :] = (
278
+ sum((vent.h_air * vent.t_supply) for vent in zone.ventilation)
279
+ + zone.h_th_bridge * self.weather.t_air
280
+ + zone.internal_gains * zone.f_internal
281
+ + zone.f_sol * zone.solar_gains
282
+ )
283
+ for elem in zone.opaque_elements:
284
+ self.res_multiplication[elem.nodes[4].idx, elem.nodes[4].idx] = elem.capacities[4] / SECONDS_PER_HOUR
285
+ self.res_constant[elem.nodes[4].idx, :] = ((1 - zone.f_internal) * zone.internal_gains + (1 - zone.f_sol) * zone.solar_gains) / zone.total_area
286
+ for i in range(1, 4):
287
+ self.res_multiplication[elem.nodes[i].idx, elem.nodes[i].idx] = elem.capacities[i] / SECONDS_PER_HOUR
288
+ self.res_multiplication[elem.nodes[0].idx, elem.nodes[0].idx] = elem.capacities[0] / SECONDS_PER_HOUR
289
+ self.res_constant[elem.nodes[0].idx, :] = (
290
+ (elem.h_ce + elem.h_re) * elem.external_temperature + elem.a_sol * (elem.diffuse_irradiation + elem.direct_irradiation) - elem.sky_heat_flux
291
+ )
292
+
293
+ for window in elem.windows:
294
+ self.res_constant[window.nodes[0].idx, :] = (window.h_ce + window.h_re) * window.external_temperature - window.sky_heat_flux
295
+ self.res_constant[window.nodes[1].idx, :] = (
296
+ (1 - zone.f_internal) * zone.internal_gains + (1 - zone.f_sol) * zone.solar_gains
297
+ ) / zone.total_area
298
+
299
+ def set_general_result_vector_values(self, temps_old: NDArray[np.float64], time_step: int) -> NDArray[np.float64]:
300
+ """
301
+ update results vector with new temperatures.
302
+
303
+ Parameters
304
+ ----------
305
+ temps_old: NDArray[np.float64]
306
+ old temperature values
307
+ time_step: int
308
+ current time step
309
+
310
+ Returns
311
+ -------
312
+ NDArray[np.float64]
313
+ """
314
+ return self.res_multiplication.dot(temps_old) + self.res_constant[:, time_step]
@@ -0,0 +1,23 @@
1
+ """
2
+ script for door class
3
+ """
4
+ from iso52016.window import Window
5
+
6
+
7
+ class Door(Window):
8
+ """
9
+ Door data class
10
+ """
11
+
12
+ def __init__(self, *, area: float, u_value: float):
13
+ """
14
+ init window
15
+
16
+ Parameters
17
+ ----------
18
+ area: float
19
+ window area in m²
20
+ u_value: float
21
+ U value of window in W/m²K
22
+ """
23
+ super().__init__(area=area, u_value=u_value, g_value=0, frame_fraction=1)