fdsreader 0.9.20__tar.gz → 1.2.8__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.
- {fdsreader-0.9.20 → fdsreader-1.2.8}/LICENSE +0 -0
- {fdsreader-0.9.20 → fdsreader-1.2.8}/MANIFEST.in +0 -0
- fdsreader-1.2.8/PKG-INFO +86 -0
- {fdsreader-0.9.20 → fdsreader-1.2.8}/README.md +1 -1
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/__init__.py +0 -0
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/_version.py +1 -1
- fdsreader-1.2.8/fdsreader/bndf/ObstructionCollection.py +53 -0
- fdsreader-1.2.8/fdsreader/bndf/__init__.py +3 -0
- fdsreader-1.2.8/fdsreader/bndf/obstruction.py +348 -0
- fdsreader-1.2.8/fdsreader/bndf/utils.py +33 -0
- fdsreader-1.2.8/fdsreader/evac/EvacCollection.py +453 -0
- fdsreader-1.2.8/fdsreader/evac/__init__.py +3 -0
- fdsreader-1.2.8/fdsreader/evac/evacuation.py +196 -0
- fdsreader-1.2.8/fdsreader/fds_classes/MeshCollection.py +21 -0
- fdsreader-1.2.8/fdsreader/fds_classes/__init__.py +7 -0
- {fdsreader-0.9.20/fdsreader/utils → fdsreader-1.2.8/fdsreader}/fds_classes/mesh.py +56 -23
- {fdsreader-0.9.20/fdsreader/utils → fdsreader-1.2.8/fdsreader}/fds_classes/surface.py +0 -0
- {fdsreader-0.9.20/fdsreader/utils → fdsreader-1.2.8/fdsreader}/fds_classes/ventilation.py +2 -1
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/geom/GeometryCollection.py +8 -4
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/geom/__init__.py +0 -0
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/geom/geometry.py +108 -13
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/isof/IsosurfaceCollection.py +7 -3
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/isof/__init__.py +0 -0
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/isof/isosurface.py +7 -8
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/part/ParticleCollection.py +37 -26
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/part/__init__.py +0 -0
- fdsreader-1.2.8/fdsreader/part/particle.py +113 -0
- fdsreader-0.9.20/fdsreader/pl3d/Plot3dCollection.py → fdsreader-1.2.8/fdsreader/pl3d/Plot3DCollection.py +11 -3
- fdsreader-1.2.8/fdsreader/pl3d/__init__.py +3 -0
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/pl3d/pl3d.py +23 -7
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/settings.py +3 -2
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/simulation.py +344 -159
- fdsreader-1.2.8/fdsreader/slcf/GeomSliceCollection.py +62 -0
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/slcf/SliceCollection.py +9 -5
- fdsreader-1.2.8/fdsreader/slcf/__init__.py +7 -0
- fdsreader-1.2.8/fdsreader/slcf/geomslice.py +458 -0
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/slcf/slice.py +150 -42
- fdsreader-1.2.8/fdsreader/smoke3d/Smoke3DCollection.py +28 -0
- fdsreader-1.2.8/fdsreader/smoke3d/__init__.py +3 -0
- fdsreader-1.2.8/fdsreader/smoke3d/smoke3d.py +250 -0
- fdsreader-1.2.8/fdsreader/utils/__init__.py +7 -0
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/utils/data.py +64 -17
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/utils/dimension.py +0 -0
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/utils/extent.py +13 -7
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/utils/fortran_data.py +3 -3
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader/utils/misc.py +0 -0
- fdsreader-1.2.8/fdsreader.egg-info/PKG-INFO +86 -0
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader.egg-info/SOURCES.txt +16 -6
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader.egg-info/dependency_links.txt +0 -0
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader.egg-info/requires.txt +0 -0
- {fdsreader-0.9.20 → fdsreader-1.2.8}/fdsreader.egg-info/top_level.txt +0 -0
- fdsreader-1.2.8/requirements.txt +113 -0
- {fdsreader-0.9.20 → fdsreader-1.2.8}/setup.cfg +0 -0
- {fdsreader-0.9.20 → fdsreader-1.2.8}/setup.py +0 -0
- fdsreader-0.9.20/PKG-INFO +0 -83
- fdsreader-0.9.20/fdsreader/bndf/ObstructionCollection.py +0 -51
- fdsreader-0.9.20/fdsreader/bndf/__init__.py +0 -3
- fdsreader-0.9.20/fdsreader/bndf/obstruction.py +0 -376
- fdsreader-0.9.20/fdsreader/part/particle.py +0 -71
- fdsreader-0.9.20/fdsreader/pl3d/__init__.py +0 -3
- fdsreader-0.9.20/fdsreader/slcf/__init__.py +0 -3
- fdsreader-0.9.20/fdsreader/utils/__init__.py +0 -13
- fdsreader-0.9.20/fdsreader/utils/fds_classes/__init__.py +0 -1
- fdsreader-0.9.20/fdsreader.egg-info/PKG-INFO +0 -83
- fdsreader-0.9.20/requirements.txt +0 -48
|
File without changes
|
|
File without changes
|
fdsreader-1.2.8/PKG-INFO
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: fdsreader
|
|
3
|
+
Version: 1.2.8
|
|
4
|
+
Summary: Python reader for data generated by FDS.
|
|
5
|
+
Home-page: https://github.com/JanVogelsang/fdsreader
|
|
6
|
+
Author: FZJ IAS-7 (Prof. Dr. Lukas Arnold, Jan Vogelsang)
|
|
7
|
+
Author-email: j.vogelsang@fz-juelich.de
|
|
8
|
+
License: UNKNOWN
|
|
9
|
+
Platform: UNKNOWN
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.6
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
|
|
17
|
+
# FDSReader
|
|
18
|
+
> Fast and easy-to-use Python reader for FDS data
|
|
19
|
+
|
|
20
|
+
[](https://travis-ci.com/FireDynamics/fdsreader)
|
|
21
|
+
[](https://badge.fury.io/py/fdsreader)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
The package is available on PyPI and can be installed using pip:
|
|
27
|
+
```sh
|
|
28
|
+
pip install fdsreader
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage example
|
|
32
|
+
```python
|
|
33
|
+
import fdsreader as fds
|
|
34
|
+
|
|
35
|
+
# Creates an instance of a simulation master-class which manages all data for a given simulation
|
|
36
|
+
sim = fds.Simulation("./sample_data")
|
|
37
|
+
|
|
38
|
+
# Examples of data that can be easily accessed
|
|
39
|
+
print(sim.meshes, sim.surfaces, sim.slices, sim.data_3d, sim.isosurfaces, sim.particles, sim.obstructions)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
More advanced examples can be found in the respective data type directories inside of the examples directory.
|
|
43
|
+
|
|
44
|
+
### Configuration
|
|
45
|
+
The package provides a few configuration options that can be set using the `settings` module.
|
|
46
|
+
```python
|
|
47
|
+
fds.settings.KEY = VALUE
|
|
48
|
+
|
|
49
|
+
# Example
|
|
50
|
+
fds.settings.DEBUG = True
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
| KEY | VALUE | Default | Description |
|
|
54
|
+
|----------------|---------|---------|-------------|
|
|
55
|
+
| LAZY_LOAD | boolean | True | Load all data when initially loading the simulation (False) or only when specific data is needed (True). |
|
|
56
|
+
| ENABLE_CACHING | boolean | True | Cache the loaded simulation to reduce startup times when loading the same simulation again. |
|
|
57
|
+
| DEBUG | boolean | False | Crash on non-critical errors with an exception (True) or hide non-critical errors (False). |
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### Data structure
|
|
61
|
+

|
|
62
|
+
|
|
63
|
+
Beware that not all attributes and methods are covered in this diagram. For a complete
|
|
64
|
+
documentation of all classes check the API Documentation below.
|
|
65
|
+
|
|
66
|
+
## API Documentation
|
|
67
|
+
[https://firedynamics.github.io/fdsreader/](https://firedynamics.github.io/fdsreader/)
|
|
68
|
+
|
|
69
|
+
## Meta
|
|
70
|
+
|
|
71
|
+
* Jan Vogelsang – j.vogelsang@fz-juelich.de
|
|
72
|
+
* Prof. Dr. Lukas Arnold - l.arnold@fz-juelich.de
|
|
73
|
+
|
|
74
|
+
Distributed under the LGPLv3 (GNU Lesser General Public License v3) license. See ``LICENSE`` for more information.
|
|
75
|
+
|
|
76
|
+
[https://github.com/FireDynamics/fdsreader](https://github.com/FireDynamics/fdsreader)
|
|
77
|
+
|
|
78
|
+
## Contributing
|
|
79
|
+
|
|
80
|
+
1. Fork it (<https://github.com/FireDynamics/fdsreader/fork>)
|
|
81
|
+
2. Create your feature branch (`git checkout -b feature/fooBar`)
|
|
82
|
+
3. Commit your changes (`git commit -am 'Add some fooBar'`)
|
|
83
|
+
4. Push to the branch (`git push origin feature/fooBar`)
|
|
84
|
+
5. Create a new Pull Request
|
|
85
|
+
|
|
86
|
+
|
|
@@ -20,7 +20,7 @@ import fdsreader as fds
|
|
|
20
20
|
sim = fds.Simulation("./sample_data")
|
|
21
21
|
|
|
22
22
|
# Examples of data that can be easily accessed
|
|
23
|
-
print(sim.meshes, sim.surfaces, sim.slices, sim.data_3d, sim.isosurfaces, sim.particles, sim.obstructions
|
|
23
|
+
print(sim.meshes, sim.surfaces, sim.slices, sim.data_3d, sim.isosurfaces, sim.particles, sim.obstructions)
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
More advanced examples can be found in the respective data type directories inside of the examples directory.
|
|
File without changes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from typing import Iterable, Tuple, List
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from fdsreader.bndf import Obstruction
|
|
5
|
+
from fdsreader.utils.data import FDSDataCollection, Quantity
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ObstructionCollection(FDSDataCollection):
|
|
9
|
+
"""Collection of :class:`Obstruction` objects. Offers extensive functionality for filtering and
|
|
10
|
+
using obstructions as well as dependent such as :class:`Boundary`.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, *obstructions: Iterable[Obstruction]):
|
|
14
|
+
super().__init__(*obstructions)
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def quantities(self) -> List[Quantity]:
|
|
18
|
+
qs = set()
|
|
19
|
+
for obst in self:
|
|
20
|
+
for q in obst.quantities:
|
|
21
|
+
qs.add(q)
|
|
22
|
+
return list(qs)
|
|
23
|
+
|
|
24
|
+
def filter_by_boundary_data(self):
|
|
25
|
+
"""Filters all obstructions for which output data exists.
|
|
26
|
+
"""
|
|
27
|
+
return ObstructionCollection(x for x in self if x.has_boundary_data)
|
|
28
|
+
|
|
29
|
+
def get_by_id(self, obst_id: str):
|
|
30
|
+
"""Get the obstruction with corresponding id if it exists.
|
|
31
|
+
"""
|
|
32
|
+
return next((obst for obst in self if obst.id == obst_id), None)
|
|
33
|
+
|
|
34
|
+
def get_nearest_obstruction(self, point: Tuple[float, float, float]) -> Obstruction:
|
|
35
|
+
"""Filters the obstruction with the shortest distance to the given point.
|
|
36
|
+
"""
|
|
37
|
+
d_min = np.finfo(float).max
|
|
38
|
+
obst_min = None
|
|
39
|
+
|
|
40
|
+
for obst in self:
|
|
41
|
+
for subobst in obst:
|
|
42
|
+
dx = max(subobst.extent.x_start - point[0], 0, point[0] - subobst.extent.x_end)
|
|
43
|
+
dy = max(subobst.extent.y_start - point[1], 0, point[1] - subobst.extent.y_end)
|
|
44
|
+
dz = max(subobst.extent.z_start - point[2], 0, point[2] - subobst.extent.z_end)
|
|
45
|
+
d = np.sqrt(dx * dx + dy * dy + dz * dz)
|
|
46
|
+
if d < d_min:
|
|
47
|
+
d_min = d
|
|
48
|
+
obst_min = obst
|
|
49
|
+
|
|
50
|
+
return obst_min
|
|
51
|
+
|
|
52
|
+
def __repr__(self):
|
|
53
|
+
return "ObstructionCollection(" + super(ObstructionCollection, self).__repr__() + ")"
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import List, Dict, Tuple, Union, Sequence
|
|
3
|
+
from typing_extensions import Literal
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from fdsreader.utils import Extent, Quantity, Dimension
|
|
7
|
+
import fdsreader.utils.fortran_data as fdtype
|
|
8
|
+
from fdsreader import settings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Patch:
|
|
12
|
+
"""Container for the actual data which is stored as rectangular plane with specific orientation
|
|
13
|
+
and extent.
|
|
14
|
+
|
|
15
|
+
:ivar dimension: :class:`Dimension` object containing information about steps in each dimension.
|
|
16
|
+
:ivar extent: :class:`Extent` object containing 3-dimensional extent information.
|
|
17
|
+
:ivar orientation: The direction the patch is facing (x={-1;1}, y={-2;2}, z={-3;3}).
|
|
18
|
+
:ivar cell_centered: Indicates whether centered positioning for data is used.
|
|
19
|
+
:ivar n_t: Total number of time steps for which output data has been written.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, file_path: str, dimension: Dimension, extent: Extent, orientation: int, cell_centered: bool,
|
|
23
|
+
patch_offset: int, initial_offset: int, n_t: int):
|
|
24
|
+
self.file_path = file_path
|
|
25
|
+
self.dimension = dimension
|
|
26
|
+
self.extent = extent
|
|
27
|
+
self.orientation = orientation
|
|
28
|
+
self.cell_centered = cell_centered
|
|
29
|
+
self._patch_offset = patch_offset
|
|
30
|
+
self._initial_offset = initial_offset
|
|
31
|
+
self._time_offset = -1
|
|
32
|
+
self.n_t = n_t
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def shape(self) -> Tuple:
|
|
36
|
+
"""Convenience function to calculate the shape of the array containing data for this patch.
|
|
37
|
+
"""
|
|
38
|
+
return self.dimension.shape(self.cell_centered)
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def size(self) -> int:
|
|
42
|
+
"""Convenience function to calculate the number of data points in the array for this patch.
|
|
43
|
+
"""
|
|
44
|
+
return self.dimension.size(self.cell_centered)
|
|
45
|
+
|
|
46
|
+
def _post_init(self, time_offset: int):
|
|
47
|
+
"""Fully initialize the patch as soon as the number of timesteps is known.
|
|
48
|
+
"""
|
|
49
|
+
self._time_offset = time_offset
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def data(self):
|
|
53
|
+
"""Method to load the quantity data for a single patch.
|
|
54
|
+
"""
|
|
55
|
+
if not hasattr(self, "_data"):
|
|
56
|
+
dtype_data = fdtype.new((('f', self.dimension.size(cell_centered=False)),))
|
|
57
|
+
|
|
58
|
+
if self.n_t == -1:
|
|
59
|
+
self.n_t = (os.stat(self.file_path).st_size - self._initial_offset) // self._time_offset
|
|
60
|
+
|
|
61
|
+
self._data = np.empty((self.n_t,) + self.shape)
|
|
62
|
+
with open(self.file_path, 'rb') as infile:
|
|
63
|
+
for t in range(self.n_t):
|
|
64
|
+
infile.seek(self._initial_offset + self._patch_offset + t * self._time_offset)
|
|
65
|
+
data = np.fromfile(infile, dtype_data, 1)[0][1].reshape(
|
|
66
|
+
self.dimension.shape(cell_centered=False), order='F')
|
|
67
|
+
if self.cell_centered:
|
|
68
|
+
self._data[t, :] = data[:-1, :-1]
|
|
69
|
+
else:
|
|
70
|
+
self._data[t, :] = data
|
|
71
|
+
return self._data
|
|
72
|
+
|
|
73
|
+
def clear_cache(self):
|
|
74
|
+
"""Remove all data from the internal cache that has been loaded so far to free memory.
|
|
75
|
+
"""
|
|
76
|
+
if hasattr(self, "_data"):
|
|
77
|
+
del self._data
|
|
78
|
+
|
|
79
|
+
def __repr__(self, *args, **kwargs):
|
|
80
|
+
return f"Patch(shape={self.shape}, orientation={self.orientation}, extent={self.extent})"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Boundary:
|
|
84
|
+
"""Container for boundary data specific to one quantity.
|
|
85
|
+
|
|
86
|
+
:ivar quantity: Quantity object containing information about the quantity calculated for this
|
|
87
|
+
:class:`Obstruction` with the corresponding short_name and unit.
|
|
88
|
+
:ivar times: Numpy array containing all times for which data has been recorded.
|
|
89
|
+
:ivar cell_centered: Indicates whether centered positioning for data is used.
|
|
90
|
+
:ivar lower_bounds: Dictionary with lower bounds for each timestep with meshes as keys.
|
|
91
|
+
:ivar upper_bounds: Dictionary with upper bounds for each timestep with meshes as keys.
|
|
92
|
+
:ivar n_t: Total number of time steps for which output data has been written.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(self, quantity: Quantity, cell_centered: bool, times: np.ndarray, n_t: int, patches: List[Patch],
|
|
96
|
+
lower_bounds: np.ndarray, upper_bounds: np.ndarray):
|
|
97
|
+
self.quantity = quantity
|
|
98
|
+
self.cell_centered = cell_centered
|
|
99
|
+
self.patches = patches
|
|
100
|
+
self.times = times
|
|
101
|
+
self.n_t = n_t
|
|
102
|
+
self.lower_bounds = lower_bounds
|
|
103
|
+
self.upper_bounds = upper_bounds
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def data(self) -> Dict[int, Patch]:
|
|
107
|
+
"""The :class:`Patch` in each direction (-3=-z, -2=-y, -1=-x, 1=x, 2=y, 3=y).
|
|
108
|
+
"""
|
|
109
|
+
return {p.orientation: p for p in self.patches}
|
|
110
|
+
|
|
111
|
+
def vmin(self, orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float:
|
|
112
|
+
"""Minimum value of all patches at any time.
|
|
113
|
+
|
|
114
|
+
:param orientation: Optionally filter by patches with a specific orientation.
|
|
115
|
+
"""
|
|
116
|
+
if orientation == 0:
|
|
117
|
+
curr_min = np.min(self.lower_bounds)
|
|
118
|
+
if curr_min == 0.0:
|
|
119
|
+
return min(np.min(p.data) for p in self.patches)
|
|
120
|
+
return curr_min
|
|
121
|
+
else:
|
|
122
|
+
return np.min(self.data[orientation].data)
|
|
123
|
+
|
|
124
|
+
def vmax(self, orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float:
|
|
125
|
+
"""Maximum value of all patches at any time.
|
|
126
|
+
|
|
127
|
+
:param orientation: Optionally filter by patches with a specific orientation.
|
|
128
|
+
"""
|
|
129
|
+
if orientation == 0:
|
|
130
|
+
curr_max = np.max(self.upper_bounds)
|
|
131
|
+
if curr_max == np.float32(-1e33):
|
|
132
|
+
return max(np.max(p.data) for p in self.patches)
|
|
133
|
+
return curr_max
|
|
134
|
+
else:
|
|
135
|
+
return np.max(self.data[orientation].data)
|
|
136
|
+
|
|
137
|
+
def clear_cache(self):
|
|
138
|
+
"""Remove all data from the internal cache that has been loaded so far to free memory.
|
|
139
|
+
"""
|
|
140
|
+
for p in self.patches:
|
|
141
|
+
p.clear_cache()
|
|
142
|
+
|
|
143
|
+
def __repr__(self):
|
|
144
|
+
return f"Boundary(Quantity={self.quantity}, Patches={len(self.patches)})"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class SubObstruction:
|
|
148
|
+
"""An :class:`Obstruction` consists of 1 or more SubObstructions which can be hidden at specific points in time.
|
|
149
|
+
|
|
150
|
+
:ivar extent: :class:`Extent` object containing 3-dimensional extent information.
|
|
151
|
+
:ivar bound_indices: Indices used to define obstruction bounds in terms of mesh locations.
|
|
152
|
+
:ivar side_surfaces: Tuple of six :class:`Surface`s for each side of the cuboid.
|
|
153
|
+
:ivar hide_times: List with points in time from when on the SubObstruction will be hidden.
|
|
154
|
+
:ivar show_times: List with points in time from when on the SubObstruction will be shown.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
def __init__(self, side_surfaces: Tuple, bound_indices: Tuple[int, int, int, int, int, int],
|
|
158
|
+
extent: Extent):
|
|
159
|
+
self.extent = extent
|
|
160
|
+
self.side_surfaces = side_surfaces
|
|
161
|
+
self.bound_indices = {'x': (bound_indices[0], bound_indices[1]),
|
|
162
|
+
'y': (bound_indices[2], bound_indices[3]),
|
|
163
|
+
'z': (bound_indices[4], bound_indices[5])}
|
|
164
|
+
|
|
165
|
+
self._boundary_data: Dict[int, Boundary] = dict()
|
|
166
|
+
|
|
167
|
+
self.hide_times = list()
|
|
168
|
+
self.show_times = list()
|
|
169
|
+
|
|
170
|
+
def _add_patches(self, bid: int, cell_centered: bool, quantity: str, short_name: str, unit: str,
|
|
171
|
+
patches: List[Patch], times: np.ndarray, n_t: int, lower_bounds: np.ndarray,
|
|
172
|
+
upper_bounds: np.ndarray):
|
|
173
|
+
if bid not in self._boundary_data:
|
|
174
|
+
self._boundary_data[bid] = Boundary(Quantity(quantity, short_name, unit), cell_centered, times, n_t,
|
|
175
|
+
patches, lower_bounds, upper_bounds)
|
|
176
|
+
|
|
177
|
+
if not settings.LAZY_LOAD:
|
|
178
|
+
_ = self._boundary_data[bid].data
|
|
179
|
+
|
|
180
|
+
def get_data(self, quantity: Union[str, Quantity]):
|
|
181
|
+
if type(quantity) == Quantity:
|
|
182
|
+
quantity = quantity.name
|
|
183
|
+
return next(b for b in self._boundary_data.values() if
|
|
184
|
+
b.quantity.name.lower() == quantity.lower() or b.quantity.short_name.lower() == quantity.lower())
|
|
185
|
+
|
|
186
|
+
def __getitem__(self, item):
|
|
187
|
+
if type(item) == int:
|
|
188
|
+
return self._boundary_data[item]
|
|
189
|
+
return self.get_data(item)
|
|
190
|
+
|
|
191
|
+
def _hide(self, time: float):
|
|
192
|
+
self.hide_times.append(time)
|
|
193
|
+
self.hide_times.sort()
|
|
194
|
+
|
|
195
|
+
def _show(self, time: float):
|
|
196
|
+
self.show_times.append(time)
|
|
197
|
+
self.show_times.sort()
|
|
198
|
+
|
|
199
|
+
def visible_times(self, times: Sequence[float]) -> np.ndarray:
|
|
200
|
+
"""Returns an ndarray containing all time steps when there is data available for the SubObstruction. Will return an
|
|
201
|
+
empty list when no data is output at all.
|
|
202
|
+
|
|
203
|
+
:param times: All timesteps of the simulation.
|
|
204
|
+
"""
|
|
205
|
+
ret = list()
|
|
206
|
+
|
|
207
|
+
hidden = False
|
|
208
|
+
for time in times:
|
|
209
|
+
if time in self.show_times:
|
|
210
|
+
hidden = False
|
|
211
|
+
if time in self.hide_times:
|
|
212
|
+
hidden = True
|
|
213
|
+
if not hidden:
|
|
214
|
+
ret.append(time)
|
|
215
|
+
|
|
216
|
+
return np.array(ret)
|
|
217
|
+
|
|
218
|
+
def vmin(self, quantity: Union[str, Quantity], orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float:
|
|
219
|
+
"""Minimum value of all patches at any time for a specific quantity.
|
|
220
|
+
|
|
221
|
+
:param orientation: Optionally filter by patches with a specific orientation.
|
|
222
|
+
"""
|
|
223
|
+
return self.get_data(quantity).vmin(orientation)
|
|
224
|
+
|
|
225
|
+
def vmax(self, quantity: Union[str, Quantity], orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float:
|
|
226
|
+
"""Maximum value of all patches at any time for a specific quantity.
|
|
227
|
+
|
|
228
|
+
:param orientation: Optionally filter by patches with a specific orientation.
|
|
229
|
+
"""
|
|
230
|
+
return self.get_data(quantity).vmax(orientation)
|
|
231
|
+
|
|
232
|
+
def clear_cache(self):
|
|
233
|
+
"""Remove all data from the internal cache that has been loaded so far to free memory.
|
|
234
|
+
"""
|
|
235
|
+
for bndf in self._boundary_data.values():
|
|
236
|
+
bndf.clear_cache()
|
|
237
|
+
|
|
238
|
+
def __repr__(self):
|
|
239
|
+
return f"SubObstruction(Extent={self.extent})"
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class Obstruction:
|
|
243
|
+
"""A box-shaped obstruction with specific surfaces (materials) on each side.
|
|
244
|
+
|
|
245
|
+
:ivar id: ID of the obstruction.
|
|
246
|
+
:ivar color_index: Type of coloring used to color obstruction.
|
|
247
|
+
\n-1 - default color
|
|
248
|
+
\n-2 - invisible
|
|
249
|
+
\n-3 - use red, green, blue and alpha (rgba attribute)
|
|
250
|
+
\nn>0 - use n’th color table entry
|
|
251
|
+
:ivar block_type: Defines how the obstruction is drawn.
|
|
252
|
+
\n-1 - use surface to obtain blocktype
|
|
253
|
+
\n0 - regular block
|
|
254
|
+
\n2 - outline
|
|
255
|
+
:ivar texture_origin: Origin position of the texture provided by the surface. When the texture
|
|
256
|
+
does have a pattern, for example windows or bricks, the texture_origin specifies where the
|
|
257
|
+
pattern should begin.
|
|
258
|
+
:ivar rgba: Optional color of the obstruction in form of a 4-element tuple
|
|
259
|
+
(ranging from 0.0 to 1.0).
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
def __init__(self, oid: int, color_index: int, block_type: int, texture_origin: Tuple[float, float, float],
|
|
263
|
+
rgba: Union[Tuple[()], Tuple[float, float, float, float]] = ()):
|
|
264
|
+
self.id = oid
|
|
265
|
+
self.color_index = color_index
|
|
266
|
+
self.block_type = block_type
|
|
267
|
+
self.texture_origin = texture_origin
|
|
268
|
+
if len(rgba) != 0:
|
|
269
|
+
self.rgba = rgba
|
|
270
|
+
|
|
271
|
+
self._subobstructions: List[SubObstruction] = list()
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
def bounding_box(self) -> Extent:
|
|
275
|
+
""":class:`Extent` object representing the bounding box around the Obstruction.
|
|
276
|
+
"""
|
|
277
|
+
extents = [sub.extent for sub in self._subobstructions]
|
|
278
|
+
|
|
279
|
+
return Extent(min(extents, key=lambda e: e.x_start).x_start, max(extents, key=lambda e: e.x_end).x_end,
|
|
280
|
+
min(extents, key=lambda e: e.y_start).y_start, max(extents, key=lambda e: e.y_end).y_end,
|
|
281
|
+
min(extents, key=lambda e: e.z_start).z_start, max(extents, key=lambda e: e.z_end).z_end)
|
|
282
|
+
|
|
283
|
+
@property
|
|
284
|
+
def quantities(self) -> List[Quantity]:
|
|
285
|
+
"""Get a list of all quantities for which boundary data exists.
|
|
286
|
+
"""
|
|
287
|
+
return [b.quantity for b in self._subobstructions[0]._boundary_data.values()]
|
|
288
|
+
|
|
289
|
+
def filter_by_orientation(self, orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> List[SubObstruction]:
|
|
290
|
+
"""Filter all SubObstructions by a specific orientation. All returned SubObstructions will contain boundary data
|
|
291
|
+
in the specified orientation.
|
|
292
|
+
"""
|
|
293
|
+
return [subobst for subobst in self._subobstructions if
|
|
294
|
+
orientation in next(iter(subobst._boundary_data.values())).data.keys()]
|
|
295
|
+
|
|
296
|
+
def get_boundary_data(self, quantity: Union[Quantity, str],
|
|
297
|
+
orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> List[Boundary]:
|
|
298
|
+
"""Gets the boundary data for a specific quantity of all SubObstructions.
|
|
299
|
+
|
|
300
|
+
:param quantity: The quantity to filter by.
|
|
301
|
+
:param orientation: Optionally filter by a specific orientation as well (-3=-z, -2=-y, -1=-x, 1=x, 2=y, 3=z).
|
|
302
|
+
A value of 0 indicates to no filter.
|
|
303
|
+
"""
|
|
304
|
+
if type(quantity) == Quantity:
|
|
305
|
+
quantity = quantity.name
|
|
306
|
+
|
|
307
|
+
ret = [subobst.get_data(quantity) for subobst in self._subobstructions]
|
|
308
|
+
if orientation == 0:
|
|
309
|
+
return ret
|
|
310
|
+
return [bndf for bndf in ret if orientation in bndf.data.keys()]
|
|
311
|
+
|
|
312
|
+
@property
|
|
313
|
+
def has_boundary_data(self):
|
|
314
|
+
"""Whether boundary data has been output in the simulation.
|
|
315
|
+
"""
|
|
316
|
+
return len(self._subobstructions[0]._boundary_data) != 0
|
|
317
|
+
|
|
318
|
+
def clear_cache(self):
|
|
319
|
+
"""Remove all data from the internal cache that has been loaded so far to free memory.
|
|
320
|
+
"""
|
|
321
|
+
for s in self._subobstructions:
|
|
322
|
+
s.clear_cache()
|
|
323
|
+
|
|
324
|
+
def vmin(self, quantity: Union[str, Quantity], orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float:
|
|
325
|
+
"""Minimum value of all patches at any time for a specific quantity.
|
|
326
|
+
|
|
327
|
+
:param orientation: Optionally filter by patches with a specific orientation.
|
|
328
|
+
"""
|
|
329
|
+
return min(s.vmin(quantity, orientation) for s in self._subobstructions)
|
|
330
|
+
|
|
331
|
+
def vmax(self, quantity: Union[str, Quantity], orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float:
|
|
332
|
+
"""Maximum value of all patches at any time for a specific quantity.
|
|
333
|
+
|
|
334
|
+
:param orientation: Optionally filter by patches with a specific orientation.
|
|
335
|
+
"""
|
|
336
|
+
return max(s.vmax(quantity, orientation) for s in self._subobstructions)
|
|
337
|
+
|
|
338
|
+
def __getitem__(self, index):
|
|
339
|
+
"""Gets the nth :class:`SubObstruction`.
|
|
340
|
+
"""
|
|
341
|
+
return self._subobstructions[index]
|
|
342
|
+
|
|
343
|
+
def __eq__(self, other):
|
|
344
|
+
return self.id == other.id
|
|
345
|
+
|
|
346
|
+
def __repr__(self, *args, **kwargs):
|
|
347
|
+
return f"Obstruction(id={self.id}, Bounding-Box={self.bounding_box}, SubObstructions={len(self._subobstructions)}" + (
|
|
348
|
+
f", Quantities={[q.short_name for q in self.quantities]}" if self.has_boundary_data else "") + ")"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from fdsreader.bndf import Patch
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def sort_patches_cartesian(patches_in: List[Patch]):
|
|
7
|
+
"""Returns all patches (of same orientation!) sorted in cartesian coordinates.
|
|
8
|
+
"""
|
|
9
|
+
patches = patches_in.copy()
|
|
10
|
+
if len(patches) != 0:
|
|
11
|
+
patches_cart = [[patches[0]]]
|
|
12
|
+
orientation = abs(patches[0].orientation)
|
|
13
|
+
if orientation == 1: # x
|
|
14
|
+
patches.sort(key=lambda p: (p.extent.y_start, p.extent.z_start))
|
|
15
|
+
elif orientation == 2: # y
|
|
16
|
+
patches.sort(key=lambda p: (p.extent.x_start, p.extent.z_start))
|
|
17
|
+
elif orientation == 3: # z
|
|
18
|
+
patches.sort(key=lambda p: (p.extent.x_start, p.extent.y_start))
|
|
19
|
+
|
|
20
|
+
if orientation == 1:
|
|
21
|
+
for patch in patches[1:]:
|
|
22
|
+
if patch.extent.y_start == patches_cart[-1][-1].extent.y_start:
|
|
23
|
+
patches_cart[-1].append(patch)
|
|
24
|
+
else:
|
|
25
|
+
patches_cart.append([patch])
|
|
26
|
+
else:
|
|
27
|
+
for patch in patches[1:]:
|
|
28
|
+
if patch.extent.x_start == patches_cart[-1][-1].extent.x_start:
|
|
29
|
+
patches_cart[-1].append(patch)
|
|
30
|
+
else:
|
|
31
|
+
patches_cart.append([patch])
|
|
32
|
+
return patches_cart
|
|
33
|
+
return patches
|