### Post-Initialization Setup Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/bndf/obstruction Completes the initialization of the patch once the total number of timesteps is known. This method sets the internal time offset. ```Python def _post_init(self, time_offset: int): """Fully initialize the patch as soon as the number of timesteps is known. """ self._time_offset = time_offset ``` -------------------------------- ### Get Number of Timesteps Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/smoke3d/smoke3d Returns the total number of timesteps for which data was output. This is determined by the length of the 'times' attribute. ```Python @property def n_t(self) -> int: """Get the number of timesteps for which data was output. """ return len(self.times) ``` -------------------------------- ### Get Maximum Value Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/slcf/slice Calculates the maximum value across all subslices, leveraging NumPy's amax functionality. ```python @implements(np.amax) def _max(self): return max(subsclice.vmax for subsclice in self._subslices.values()) ``` -------------------------------- ### Load Device Configuration Data Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/simulation Loads device configuration data from a 'devc' file. It reads units and names for device quantities, preparing them for further processing. ```Python def _load_DEVC_data(self): with open(self.devc_path, 'r') as infile: units = infile.readline().split(',') names = [name.replace('"', '').replace('\n', '').strip() for name in infile.readline().split(',"')] ``` -------------------------------- ### FDSReader Initialization and Data Loading Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/simulation This snippet illustrates the __init__ method of the FDSReader class. It handles file path resolution, initialization of various data containers (geometries, slices, 3D data, etc.), parsing the simulation file, loading CPU and profile data, and finally organizing the collected data into specialized collection objects for easier access and manipulation. It also includes logic for handling device data callbacks and conditional initialization for particles and evacs. ```python import warnings import os from typing import List, Dict # Assuming necessary imports for classes like Geometry, Surface, Ventilation, SubObstruction, Plot3D, Mesh, Profile, GeometryCollection, SliceCollection, GeomSliceCollection, Smoke3DCollection, IsosurfaceCollection, DeviceCollection, ObstructionCollection, ParticleCollection, EvacCollection, MeshCollection, get_smv_file, settings, __version__ class FDSReader: def __init__(self, path: str): """ :param path: Either the path to the directory containing the simulation data or direct path to the .smv file for the simulation in case that multiple simulation output was written to the same directory. """ if settings.IGNORE_ERRORS: warnings.filterwarnings("ignore") # Check if the file has already been instantiated via a cached pickle file if not hasattr(self, "_hash"): self.reader_version = __version__ self.smv_file_path = get_smv_file(path) self.root_path = os.path.dirname(self.smv_file_path) self.geoms: List[Geometry] = list() self.surfaces: List[Surface] = list() self.ventilations = dict() # Will only be used during the loading process to map boundary data to the correct # obstruction self._subobstructions: Dict[str, List[SubObstruction]] = dict() # First collect all meta-information for any FDS data to later combine the gathered # information into data collections. While collecting the meta-data simple python # containers are used. self._obstructions = list() self._slices = dict() self._geomslices = dict() self.data_3d = Plot3DCollection([Plot3D(self.root_path) for _ in range(5)]) self._smoke_3d = dict() self._isosurfaces = dict() self._particles = list() self._evacs = list() self._geom_data = list() self._meshes: List[Mesh] = list() self._devices = dict() self.profiles: Dict[str, Profile] = dict() self.parse_smv_file() self.cpu = self._load_CPU_data() self._load_profiles() # POST INIT (post read) self.out_file_path = os.path.join(self.root_path, self.chid + ".out") self.ventilations: List[Ventilation] = list(self.ventilations.values()) for device_id, device in self._devices.items(): if type(device) == list: for devc in device: devc._data_callback = self._load_DEVC_data else: device._data_callback = self._load_DEVC_data # Combine the gathered temporary information into data collections self.geom_data = GeometryCollection(self._geom_data) self.slices = SliceCollection( Slice(self.root_path, slice_data[0]["id"], slice_data[0]["cell_centered"], slice_data[0]["times"], slice_data[1:]) for slice_data in self._slices.values()) self.geomslices = GeomSliceCollection( GeomSlice(self.root_path, slice_data[0]["id"], slice_data[0]["times"], slice_data[1:]) for slice_data in self._geomslices.values()) self.smoke_3d = Smoke3DCollection(self._smoke_3d.values()) self.isosurfaces = IsosurfaceCollection(self._isosurfaces.values()) self.devices = DeviceCollection(self._devices.values()) self.obstructions = ObstructionCollection(self._obstructions) # If no particles are simulated, initialize empty data container for consistency if type(self._particles) == list: self.particles = ParticleCollection((), ()) else: self.particles = self._particles self.particles._post_init() # If no evacs are simulates, initialize empty data container for consistency if len(self._evacs) == 0: self.evacs = EvacCollection((), "", ()) self.meshes = MeshCollection(self._meshes) def _build_simulation_string(self): """Builds a string representation of the simulation parameters.""" r = f"Simulation(chid={self.chid}, " + f" meshes={len(self.meshes)}, " + (f" obstructions={len(self.obstructions)}, " if len(self.obstructions) > 0 else "") + (f" geoms={len(self.geoms)}, " if len(self.geoms) > 0 else "") + (f" slices={len(self.slices)}, " if len(self.slices) > 0 else "") + (f" geomslices={len(self.geomslices)}, " if len(self.geomslices) > 0 else "") + (f" data_3d={len(self.data_3d)}, " if len(self.data_3d) > 0 else "") + (f" smoke_3d={len(self.smoke_3d)}, " if len(self.smoke_3d) > 0 else "") + (f" isosurfaces={len(self.isosurfaces)}, " if len(self.isosurfaces) > 0 else "") + (f" particles={len(self.particles)}, " if len(self.particles) > 0 else "") + (f" evacs={len(self.evacs)}, " if len(self.evacs) > 0 else "") + (f" devices={len(self.devices)}, " if len(self.devices) > 0 else "") return r[:-2] + ')' ``` -------------------------------- ### Get Minimum Value Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/slcf/slice Calculates the minimum value across all subslices, leveraging NumPy's amin functionality. ```python @implements(np.amin) def _min(self): return min(subsclice.vmin for subsclice in self._subslices.values()) ``` -------------------------------- ### Load Evacuation Data from SMV File Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/simulation Loads evacuation data from an .smv file, linking it to a specific mesh and z-offset. It utilizes the _load_prt5_meta helper and initializes an EvacCollection. ```Python @log_error("evac") def _load_evac_data(self, smv_file: TextIO, line: str): file_path = os.path.join(self.root_path, smv_file.readline().strip()) mesh_index, z_offset = line.split()[1:] mesh = self._meshes[int(mesh_index) - 1] times = self._load_prt5_meta(self._evacs, file_path + '.bnd', mesh)[1:] # First timestep is weird somehow if type(self._evacs) == list: self.evacs = EvacCollection(self._evacs, os.path.join(self.root_path, self.chid + "_evac"), times) self.evacs.z_offsets[mesh.id] = float(z_offset) self.evacs._file_paths[mesh.id] = file_path n_evacs = int(smv_file.readline().strip()) for i in range(n_evacs): smv_file.readline() # Skip "N" values ``` -------------------------------- ### SubSlice.n_t Property Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/slcf/slice Gets the number of timesteps for which data was output. This property retrieves the count from the parent slice object. ```python @property def n_t(self) -> int: """Get the number of timesteps for which data was output. """ return self._parent_slice.n_t ``` -------------------------------- ### Obstruction Class Methods Source: https://firedynamics.github.io/fdsreader/bndf API documentation for methods related to the Obstruction class, which represents a collection of patches cut by a slice. It includes functions for finding nearest mesh elements, retrieving time step data, and accessing simulation properties. ```APIDOC class fdsreader.bndf.obstruction.Obstruction: """API documentation for the Obstruction class.""" get_nearest_index(dimension: Literal['x', 'y', 'z'], orientation: int, value: float) -> int Get the nearest mesh coordinate index in a specific dimension for a specific orientation. get_nearest_patch(x: float = None, y: float = None, z: float = None) Gets the patch of the `SubObstruction` that has the least distance to the given point. If there are multiple patches with the same distance, a random one will be selected. get_nearest_timestep(time: float, visible_only: bool = False) -> int Calculates the nearest timestep for which data has been output for this obstruction. get_visible_times(times: Sequence[float]) Returns an ndarray filtering all time steps when the SubObstruction is visible/not hidden. n_t(count_duplicates=True) -> int Returns the number of timesteps for which boundary data is available. :param count_duplicates: If true, return the total number of data points, even if there is duplicate data for a timestep. Duplicate data might be output when restarting the simulation in between the simulation run. vmax(quantity: str | Quantity, orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float Maximum value of all patches at any time for a specific quantity. Parameters: : **orientation** – Optionally filter by patches with a specific orientation. vmin(quantity: str | Quantity, orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float Minimum value of all patches at any time for a specific quantity. Parameters: : **orientation** – Optionally filter by patches with a specific orientation. Properties: has_boundary_data: Whether boundary data has been output in the simulation. meshes: Returns a list of all meshes this slice cuts through. orientations: Return all orientations for which there is data available. quantities: Get a list of all quantities for which boundary data exists. times: Return all timesteps for which boundary data is available, if any. ``` -------------------------------- ### Obstruction Class Methods Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/bndf/obstruction API documentation for the Obstruction class, detailing methods for cache management, value retrieval (vmin, vmax), item access, equality checks, and object representation. ```APIDOC clear_cache() Remove all data from the internal cache that has been loaded so far to free memory. ``` ```APIDOC vmin(quantity: Union[str, Quantity], orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float Minimum value of all patches at any time for a specific quantity. Parameters: quantity: The quantity to find the minimum value for (string or Quantity object). orientation: Optionally filter by patches with a specific orientation (integer from -3 to 3, default is 0). Returns: The minimum value as a float, or np.nan if boundary data is not available. ``` ```APIDOC vmax(quantity: Union[str, Quantity], orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float Maximum value of all patches at any time for a specific quantity. Parameters: quantity: The quantity to find the maximum value for (string or Quantity object). orientation: Optionally filter by patches with a specific orientation (integer from -3 to 3, default is 0). Returns: The maximum value as a float, or np.nan if boundary data is not available. ``` ```APIDOC __getitem__(key: Union[int, str, 'Mesh']) -> Union[SubObstruction, Tuple[SubObstruction, ...]] Gets either the nth SubObstruction or the one with the given mesh-id. Parameters: key: An integer index, a string mesh-id, or a Mesh object to identify the SubObstruction. Returns: The requested SubObstruction object or a tuple of all SubObstructions. ``` ```APIDOC __eq__(other: object) -> bool Compares two Obstruction objects for equality based on their IDs. Parameters: other: The object to compare with. Returns: True if the objects have the same ID, False otherwise. ``` ```APIDOC __repr__(*args, **kwargs) -> str Returns a string representation of the Obstruction object, including its ID, bounding box, and sub-obstruction details. Returns: A formatted string representing the Obstruction object. ``` -------------------------------- ### Get All Meshes Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/slcf/slice Returns a list of all `Mesh` objects associated with the `SubSlice` collection. Each `SubSlice` is typically derived from a unique mesh. ```python @property def meshes(self) -> List[Mesh]: """Returns a list of all meshes this slice cuts through. """ return [subslc.mesh for subslc in self._subslices.values()] ``` -------------------------------- ### Obstruction Class Methods Source: https://firedynamics.github.io/fdsreader/index API documentation for methods related to the Obstruction class, which represents a collection of patches cut by a slice. It includes functions for finding nearest mesh elements, retrieving time step data, and accessing simulation properties. ```APIDOC class fdsreader.bndf.obstruction.Obstruction: """API documentation for the Obstruction class.""" get_nearest_index(dimension: Literal['x', 'y', 'z'], orientation: int, value: float) -> int Get the nearest mesh coordinate index in a specific dimension for a specific orientation. get_nearest_patch(x: float = None, y: float = None, z: float = None) Gets the patch of the `SubObstruction` that has the least distance to the given point. If there are multiple patches with the same distance, a random one will be selected. get_nearest_timestep(time: float, visible_only: bool = False) -> int Calculates the nearest timestep for which data has been output for this obstruction. get_visible_times(times: Sequence[float]) Returns an ndarray filtering all time steps when the SubObstruction is visible/not hidden. n_t(count_duplicates=True) -> int Returns the number of timesteps for which boundary data is available. :param count_duplicates: If true, return the total number of data points, even if there is duplicate data for a timestep. Duplicate data might be output when restarting the simulation in between the simulation run. vmax(quantity: str | Quantity, orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float Maximum value of all patches at any time for a specific quantity. Parameters: : **orientation** – Optionally filter by patches with a specific orientation. vmin(quantity: str | Quantity, orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float Minimum value of all patches at any time for a specific quantity. Parameters: : **orientation** – Optionally filter by patches with a specific orientation. Properties: has_boundary_data: Whether boundary data has been output in the simulation. meshes: Returns a list of all meshes this slice cuts through. orientations: Return all orientations for which there is data available. quantities: Get a list of all quantities for which boundary data exists. times: Return all timesteps for which boundary data is available, if any. ``` -------------------------------- ### Get Boundary Orientations Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/bndf/obstruction Returns a list of orientations for which boundary data is available. Assumes all boundary data shares the same set of orientations. ```Python @property def orientations(self): """Return all orientations for which there is data available. """ if self.has_boundary_data: return next(iter(self._boundary_data.values())).orientations return [] ``` -------------------------------- ### SubSurface Class API Documentation Source: https://firedynamics.github.io/fdsreader/isof API reference for the SubSurface class, detailing its methods and properties for accessing and managing isosurface data. ```APIDOC SubSurface: Description: Part of an isosurface with data for a specific mesh. Variables: mesh: The mesh containing all data for this `SubSurface`. file_path: Path to the binary data file. v_file_path: Path to the binary data file containing color data. n_vertices: The number of vertices for this subsurface. n_triangles: The number of triangles for this subsurface. n_t: Total number of time steps for which output data has been written. Methods: clear_cache(): Description: Remove all data from the internal cache that has been loaded so far to free memory. Signature: clear_cache() Properties: colors: Description: Property to lazy load the color data that might be associated with the isosurfaces. Signature: colors has_color_data: Description: Defines whether there is color data for this subsurface or not. Signature: has_color_data surfaces: Description: Property to lazy load a list that maps triangles to an isosurface for a specific level. The list has the size n_triangles, while the indices correspond to indices of the triangles. Signature: surfaces triangles: Description: Property to lazy load all triangles of any level. Signature: triangles vertices: Description: Property to lazy load all vertices for all triangles of any level. Signature: vertices ``` -------------------------------- ### Get Slice Type (2D/3D) Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/slcf/slice Determines if the current slice orientation represents a 2D or 3D dataset based on the orientation property. ```python @property def type(self) -> Literal['2D', '3D']: if self.orientation == 0: return '3D' return '2D' ``` -------------------------------- ### Boundary Class Initialization Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/bndf/obstruction Initializes a Boundary object with quantity, time data, patches, and bounds. It stores information about a specific quantity's boundary data across simulation timesteps. ```python class Boundary: """Container for boundary data specific to one quantity. :ivar quantity: Quantity object containing information about the quantity calculated for this :class:`Obstruction` with the corresponding short_name and unit. :ivar times: Numpy array containing all times for which data has been recorded. :ivar cell_centered: Indicates whether centered positioning for data is used. :ivar lower_bounds: Dictionary with lower bounds for each timestep with meshes as keys. :ivar upper_bounds: Dictionary with upper bounds for each timestep with meshes as keys. :ivar n_t: Total number of time steps for which output data has been written. """ def __init__(self, quantity: Quantity, cell_centered: bool, times: Sequence[float], patches: List[Patch], lower_bounds: np.ndarray, upper_bounds: np.ndarray): self.quantity = quantity self.cell_centered = cell_centered self._patches = patches self.times = times self.lower_bounds = lower_bounds self.upper_bounds = upper_bounds ``` -------------------------------- ### Get Number of SubSlices Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/slcf/slice Returns the total number of `SubSlice` objects managed by this instance, typically corresponding to the number of meshes or slices. ```python def __len__(self): return len(self._subslices) ``` -------------------------------- ### Mesh: Get Obstruction Mask Slice Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/fds_classes/mesh Generates an obstruction mask for a specific 2D slice of the mesh. This method is useful for visualizing obstructions on a planar cut. ```python def get_obstruction_mask_slice(self, subslice): """Marks all cells of a single subslice which are blocked by an obstruction. :returns: A 4-dimensional array with time as first and x,y,z as last dimensions. The array depends on time as obstructions may be hidden at specific points in time. """ orientation = subslice.orientation value = subslice.extent[orientation][0] cell_centered = subslice.cell_centered slc_index = self.coordinate_to_index((value,), dimension=(orientation,), cell_centered=cell_centered)[0] mask_indices = [slice(None)] * 4 mask_indices[orientation] = slice(slc_index, slc_index + 1, 1) mask_indices = tuple(mask_indices) return self.get_obstruction_mask(subslice.times, cell_centered=cell_centered)[mask_indices] ``` -------------------------------- ### Load Particle Data from SMV File Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/simulation Loads particle data by reading a .smv file, resolving the associated .bnd file path, and calling the metadata loading function. It initializes or updates a ParticleCollection object. ```Python @log_error("part") def _load_particle_data(self, smv_file: TextIO, line: str): file_path = os.path.join(self.root_path, smv_file.readline().strip()) mesh_index = int(line.split()[1].strip()) - 1 mesh = self._meshes[mesh_index] times = self._load_prt5_meta(self._particles, file_path + '.bnd', mesh) if type(self._particles) == list: self._particles = ParticleCollection(times, self._particles) self._particles._file_paths[mesh.id] = file_path n_classes = int(smv_file.readline().strip()) for i in range(n_classes): smv_file.readline() # Skip "N" values ``` -------------------------------- ### Mesh: Get Obstruction Mask Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/fds_classes/mesh Generates a boolean mask indicating blocked cells over specified timesteps. It accounts for obstructions that may change visibility over time. ```python def get_obstruction_mask(self, times: Sequence[float], cell_centered=False) -> np.ndarray: """Marks all cells which are blocked by an obstruction. :param times: All timesteps of the simulation. :returns: A 4-dimensional array with time as first and x,y,z as last dimensions. The array depends on time as obstructions may be hidden as specific points in time. """ shape = self.dimension.shape(cell_centered=cell_centered) mask = np.ones((len(times), shape[0], shape[1], shape[2]), dtype=bool) c = 1 if cell_centered else 0 for obst in self.obstructions: subobst = obst[self] x1, x2 = subobst.bound_indices['x'] y1, y2 = subobst.bound_indices['y'] z1, z2 = subobst.bound_indices['z'] t_idx = 0 for t in subobst.get_visible_times(times): while not np.isclose(t, times[t_idx]): t_idx += 1 mask[t_idx, x1:max(x2 + c, x1 + 1), y1:max(y2 + c, y1 + 1), z1:max(z2 + c, z1 + 1)] = False return mask ``` -------------------------------- ### SubSurface Class API Source: https://firedynamics.github.io/fdsreader/isof API documentation for the SubSurface class, representing a part of an isosurface with data for a specific mesh. It details its constructor and potential methods/attributes. ```APIDOC SubSurface Class: __init__(mesh: Mesh, iso_filepath: str, times: List, viso_filepath: str = '') Part of an isosurface with data for a specific mesh. Parameters: mesh: The mesh object associated with this subsurface. iso_filepath: Filepath to the isosurface data. times: List of timesteps available for this subsurface. viso_filepath: Optional filepath for visual data. ``` -------------------------------- ### Get Specific Quantity Data Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/evac/evacuation Returns simulation data for a specified quantity. If the quantity is not found or not available, an empty list is returned. ```python def get_data(self, quantity: Union[Quantity, str]) -> List[np.ndarray]: """Returns a list with a numpy array for each timestep which contains data about the specified quantity for each person in that timestep. """ if self.has_quantity(quantity): if type(quantity) == Quantity: quantity = quantity.name return self.data[quantity] return [] ``` -------------------------------- ### Obstruction Methods for Temporal and Spatial Queries Source: https://firedynamics.github.io/fdsreader/bndf Provides methods for finding nearest data points in time and space, retrieving maximum/minimum values, and checking visibility across time steps. ```APIDOC Methods (continued): get_nearest_index(dimension, orientation, value) Get the nearest mesh coordinate index in a specific dimension for a specific orientation. get_nearest_patch(x=None, y=None, z=None) Gets the patch of the `SubObstruction` that has the least distance to the given point. get_nearest_timestep(time, visible_only=None) Calculates the nearest timestep for which data has been output for this obstruction. get_visible_times(times) Returns an ndarray filtering all time steps when the SubObstruction is visible/not hidden. n_t(count_duplicates=False) Returns the number of timesteps for which boundary data is available. If true, returns total data points, including duplicates from restarts. vmax(quantity, orientation=0) Maximum value of all patches at any time for a specific quantity. vmin(quantity, orientation=0) Minimum value of all patches at any time for a specific quantity. ``` -------------------------------- ### Patch Class Initialization Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/bndf/obstruction Initializes a Patch object to store simulation data. It takes file path, dimension, extent, orientation, cell centering, offsets, and timestep count as parameters. ```Python class Patch: """Container for the actual data which is stored as rectangular plane with specific orientation and extent. :ivar dimension: :class:`Dimension` object containing information about steps in each dimension. :ivar extent: :class:`Extent` object containing 3-dimensional extent information. :ivar orientation: The direction the patch is facing (x={-1;1}, y={-2;2}, z={-3;3}). :ivar cell_centered: Indicates whether centered positioning for data is used. :ivar _n_t: Total number of time steps for which output data has been written. """ def __init__(self, file_path: str, dimension: Dimension, extent: Extent, orientation: int, cell_centered: bool, patch_offset: int, initial_offset: int, n_t: int, mesh: 'Mesh'): self.file_path = file_path self.dimension = dimension self.extent = extent self.orientation = orientation self.cell_centered = cell_centered self._patch_offset = patch_offset self._initial_offset = initial_offset self._time_offset = -1 self._n_t = n_t self.mesh = mesh self._boundary_parent: 'Boundary' = None ``` -------------------------------- ### Get Minimum Value for a Quantity Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/bndf/obstruction Calculates the minimum value across all timesteps and patches for a specified quantity. Optionally filters by patch orientation. ```Python def vmin(self, quantity: Union[str, Quantity], orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> float: """Minimum value of all patches at any time for a specific quantity. :param orientation: Optionally filter by patches with a specific orientation. """ if self.has_boundary_data: ``` -------------------------------- ### FDSReader API Documentation Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/bndf/obstruction Comprehensive API documentation for FDSReader methods and properties, covering coordinate retrieval, data filtering, and mesh information. ```APIDOC get_nearest_index(dimension: Literal['x', 'y', 'z'], orientation: int, value: float) -> int Get the nearest mesh coordinate index in a specific dimension for a specific orientation. Parameters: dimension: The spatial dimension ('x', 'y', or 'z'). orientation: The orientation identifier (e.g., -3 for -z, 1 for x). value: The coordinate value to find the nearest index for. Returns: The index of the nearest mesh coordinate. Notes: Requires boundary data to be present. Returns np.nan if no boundary data exists. ``` ```APIDOC quantities (property) Get a list of all quantities for which boundary data exists. Returns: A list of Quantity objects. Notes: Returns an empty list if no boundary data is available. ``` ```APIDOC meshes (property) Returns a list of all meshes this slice cuts through. Returns: A list of Mesh objects. Notes: Returns an empty list if no subobstructions are present. ``` ```APIDOC filter_by_orientation(orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> List[SubObstruction] Filter all SubObstructions by a specific orientation. Parameters: orientation: The orientation to filter by. Defaults to 0 (no filter). -3: -z, -2: -y, -1: -x, 1: x, 2: y, 3: z. Returns: A list of SubObstruction objects that contain boundary data in the specified orientation. Notes: Returns an empty list if no boundary data exists or no subobstructions match the criteria. ``` ```APIDOC get_boundary_data(quantity: Union[Quantity, str], orientation: Literal[-3, -2, -1, 0, 1, 2, 3] = 0) -> Dict[str, Boundary] Gets the boundary data for a specific quantity of all SubObstructions. Parameters: quantity: The quantity to filter by (either a Quantity object or its string name). orientation: Optionally filter by a specific orientation. Defaults to 0 (no orientation filter). -3: -z, -2: -y, -1: -x, 1: x, 2: y, 3: z. Returns: A dictionary where keys are mesh IDs and values are Boundary objects containing the requested data. Notes: Filters results by orientation if specified. Returns an empty dictionary if no matching data is found. ``` -------------------------------- ### Get Coordinates Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/bndf/obstruction Returns a dictionary of numpy arrays containing coordinates for each dimension (x, y, z). Optionally adjusts coordinates for cell-centered data. ```Python def get_coordinates(self, ignore_cell_centered: bool = False) -> Dict[Literal['x', 'y', 'z'], np.ndarray]: """Returns a dictionary containing a numpy ndarray with coordinates for each dimension. For cell-centered boundary data, the coordinates can be adjusted to represent cell-centered coordinates. :param ignore_cell_centered: Whether to shift the coordinates when the bndf is cell_centered or not. """ coords: Dict[Literal['x', 'y', 'z'], np.ndarray] = {} for dim in ('x', 'y', 'z'): co = self.mesh.coordinates[dim].copy() # In case the slice is cell-centered, we will shift the coordinates by half a cell # and remove the last coordinate if self.cell_centered and not ignore_cell_centered: co = co[:-1] co += abs(co[1] - co[0]) / 2 coords[dim] = co[np.where(np.logical_and(co >= self.extent[dim][0], co <= self.extent[dim][1]))] if coords[dim].size == 0: coords[dim] = np.array([co[np.argmin(np.abs(co - self.extent[dim][0]))]]) return coords ``` -------------------------------- ### Load Isosurface Data (Python) Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/simulation Loads isosurface data, handling both single and double quantity types. It parses filenames, quantity details, and level data from binary files. The method manages the creation and updating of Isosurface objects. ```Python @log_error("isof") def _load_isosurface(self, smv_file: TextIO, line: str): """Loads the isosurface at current pointer position. """ double_quantity = line[0] == 'T' mesh_index = int(line.strip().split()[1]) - 1 iso_filename = smv_file.readline().strip() iso_id = int(iso_filename.split('_')[-1][:-4]) iso_file_path = os.path.join(self.root_path, iso_filename) if double_quantity: viso_file_path = os.path.join(self.root_path, smv_file.readline().strip()) quantity = smv_file.readline().strip() short_name = smv_file.readline().strip() unit = smv_file.readline().strip() if double_quantity: v_quantity = smv_file.readline().strip() v_short_name = smv_file.readline().strip() v_unit = smv_file.readline().strip() if iso_id not in self._isosurfaces: with open(iso_file_path, 'rb') as infile: nlevels = fdtype.read(infile, fdtype.INT, 3)[2][0][0] dtype_header_levels = fdtype.new((('f', nlevels),)) levels = fdtype.read(infile, dtype_header_levels, 1)[0] if double_quantity: if iso_id not in self._isosurfaces: self._isosurfaces[iso_id] = Isosurface(iso_id, double_quantity, quantity, short_name, unit, levels, v_quantity=v_quantity, v_short_name=v_short_name, v_unit=v_unit) self._isosurfaces[iso_id]._add_subsurface(self._meshes[mesh_index], iso_file_path, viso_file_path=viso_file_path) else: if iso_id not in self._isosurfaces: self._isosurfaces[iso_id] = Isosurface(iso_id, double_quantity, quantity, short_name, unit, levels) self._isosurfaces[iso_id]._add_subsurface(self._meshes[mesh_index], iso_file_path) ``` -------------------------------- ### Get Number of Timesteps (n_t) Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/bndf/obstruction Retrieves the number of timesteps for which data was output. Optionally counts duplicate timesteps, which can occur during simulation restarts. ```Python def n_t(self, count_duplicates=True) -> int: """Get the number of timesteps for which data was output. :param count_duplicates: If true, return the total number of data points, even if there is duplicate data for a timestep. Duplicate data might be output when restarting the simulation in between the simulation run. """ if count_duplicates: return self._n_t return np.unique(self._boundary_parent.times).size ``` -------------------------------- ### Isosurface Class API Source: https://firedynamics.github.io/fdsreader/isof API documentation for the Isosurface class, which serves as a data container for isosurface files. It includes methods for managing cache, exporting data, retrieving nearest timesteps, and converting data to PyVista meshes. ```APIDOC Isosurface Class: __init__(isosurface_id: int, double_quantity: bool, quantity: str, short_name: str, unit: str, levels: List[float], v_quantity: str = '', v_short_name: str = '', v_unit: str = '') Isosurface file data container including metadata. Consists of a list of vertices forming a list of triangles. Can optionally have additional color data for the surfaces. Parameters: isosurface_id: The ID of this isosurface. double_quantity: Indicates if the quantity is double precision. quantity: Quantity object containing information about the quantity calculated for this isosurface with the corresponding short_name and unit. short_name: Short name for the quantity. unit: Unit for the quantity. levels: All isosurface levels. v_quantity: Information about the color quantity. v_short_name: Short name for the color quantity. v_unit: Unit for the color quantity. Methods: clear_cache() Remove all data from the internal cache that has been loaded so far to free memory. export(file_path: str, time: float | int) Export the isosurface for a single timestep into one of many formats. Parameters: file_path: Absolute path to the file to be written. The file ending denotes the file format. time: Either the index of the timestep or an actual time value. Data for the nearest matching timestep will be used. get_nearest_timestep(time: float) -> int Calculates the nearest timestep for which data has been output for this isosurface. Parameters: time: The time value to find the nearest timestep for. Returns: The index of the nearest timestep. get_pyvista_mesh(vertices: ndarray, triangles: ndarray) Creates a PyVista mesh from the data. Parameters: vertices: Numpy array of vertices. triangles: Numpy array of triangles. join_pyvista_meshes(meshes: List) -> PolyData Combines multiple PyVista meshes. Returns: The combined mesh of class PolyData. to_global(time: int | float) -> Tuple[ndarray, List[ndarray], ndarray | None] Creates an array containing all global vertices and a list containing numpy arrays with triangles for each surface level. Parameters: time: Either the index of the timestep or an actual time value. Data for the nearest matching timestep will be used. Returns: A tuple containing global vertices, a list of triangle arrays per surface level, and optionally color data. Properties: has_color_data: bool Defines whether there is color data for this isosurface or not. surfaces: Dict[str, List[ndarray]] Gets all surfaces per mesh. times: List[float] List containing all times for which data has been recorded. triangles: Dict[str, List[ndarray]] Gets all triangles per mesh. vertices: Dict[str, List[ndarray]] Gets all vertices per mesh. ``` -------------------------------- ### Get All SubSlices Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/slcf/slice Returns a list containing all `SubSlice` objects currently stored. This property provides a convenient way to iterate over all available slices. ```python @property def subslices(self) -> List[SubSlice]: """Get a list with all SubSlices. """ return list(self._subslices.values()) ``` -------------------------------- ### Get Grid Values by Dimension Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/fds_classes/mesh Returns all grid values along a specified dimension. The dimension can be provided as an integer (0, 1, 2) or a string ('x', 'y', 'z'). ```python def __getitem__(self, dimension: Literal[0, 1, 2, 'x', 'y', 'z']) -> np.ndarray: """Get all values in given dimension. :param dimension: The dimension in which to return all grid values (0=x, 1=y, 2=z). """ # Convert possible integer input to chars if type(dimension) == int: dimension = ('x', 'y', 'z')[dimension] return self.coordinates[dimension] ``` -------------------------------- ### Python: Calculate and Populate Simulation Grid Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/bndf/obstruction This Python code calculates the necessary step sizes and grid dimensions based on sub-obstruction coordinates. It then initializes a NumPy grid and populates it with data from sub-obstructions, handling coordinate mapping, data repetition, and potential overlaps for face-centered data. Dependencies include numpy and math. ```python step_sizes_min = {'x': coord_max['x'] - coord_min['x'], 'y': coord_max['y'] - coord_min['y'], 'z': coord_max['z'] - coord_min['z']} step_sizes_max = {'x': 0, 'y': 0, 'z': 0} steps = dict() global_max = {'x': -math.inf, 'y': -math.inf, 'z': -math.inf} for dim in ('x', 'y', 'z'): for subobst in subobsts: subobst_coords = subobst.get_coordinates(ignore_cell_centered=True)[orientation_int] if len(subobst_coords[dim]) <= 1: step_size = 0 else: step_size = subobst_coords[dim][1] - subobst_coords[dim][0] step_sizes_min[dim] = min(step_size, step_sizes_min[dim]) step_sizes_max[dim] = max(step_size, step_sizes_max[dim]) global_max[dim] = max(subobst_coords[dim][-1], global_max[dim]) for dim in ('x', 'y', 'z'): if step_sizes_min[dim] == 0: step_sizes_min[dim] = math.inf steps[dim] = 1 else: steps[dim] = max(int(round((coord_max[dim] - coord_min[dim]) / step_sizes_min[dim])), 1) + ( 0 if cell_centered else 1) grid = np.full((self.n_t(count_duplicates=False), steps['x'], steps['y'], steps['z']), np.nan) start_coordinates = {'x': coord_min['x'], 'y': coord_min['y'], 'z': coord_min['z']} start_idx = dict() end_idx = dict() for subobst in subobsts: patch_data = np.expand_dims(subobst.get_data(quantity).data[orientation_int].data, axis=abs(orientation_int)) subobst_coords = subobst.get_coordinates(ignore_cell_centered=True)[orientation_int] for axis in (0, 1, 2): dim = ('x', 'y', 'z')[axis] if axis == abs(orientation_int) - 1: start_idx[dim] = 0 end_idx[dim] = 1 continue n_repeat = max( int(round((subobst_coords[dim][1] - subobst_coords[dim][0]) / step_sizes_min[dim])), 1) start_idx[dim] = int( round((subobst_coords[dim][0] - start_coordinates[dim]) / step_sizes_min[dim])) end_idx[dim] = int( round((subobst_coords[dim][-1] - start_coordinates[dim]) / step_sizes_min[dim])) # We ignore border points unless they are actually on the border of the simulation space as all # other border points actually appear twice, as the subobstructions overlap. This only # applies for face_centered data, as cell_centered data will not overlap. if not cell_centered: if axis != abs(orientation_int) - 1: reduced_shape = list(patch_data.shape) reduced_shape[axis + 1] -= 1 reduced_data_slices = tuple(slice(s) for s in reduced_shape) patch_data = patch_data[reduced_data_slices] # Temporarily save border points to add them back to the array again later if subobst_coords[dim][-1] == global_max[dim]: end_idx[dim] += 1 temp_data_slices = [slice(s) for s in patch_data.shape] temp_data_slices[axis + 1] = slice(patch_data.shape[axis + 1] - 1, None) temp_data = patch_data[tuple(temp_data_slices)] if n_repeat > 1: patch_data = np.repeat(patch_data, n_repeat, axis=axis + 1) # Add border points back again if needed if not cell_centered and subobst_coords[dim][-1] == global_max[dim]: patch_data = np.concatenate((patch_data, temp_data), axis=axis + 1) grid[:, start_idx['x']: end_idx['x'], start_idx['y']: end_idx['y'], start_idx['z']: end_idx['z']] = patch_data.reshape( (self.n_t(count_duplicates=False), end_idx['x'] - start_idx['x'], end_idx['y'] - start_idx['y'], end_idx['z'] - start_idx['z'])) # Remove empty dimensions, but make sure to note remove the time dimension if there is only a single timestep grid = np.squeeze(grid) if len(grid.shape) == 2: pass # Placeholder for potential further processing or return ``` -------------------------------- ### FDS Surface Class API Source: https://firedynamics.github.io/fdsreader/index Documentation for the Surface class, defining boundary surfaces in FDS simulations. Includes properties for material, texture, and color. ```APIDOC class fdsreader.fds_classes.surface.Surface: __init__(name: str, tmpm: float, material_emissivity: float, surface_type: int, texture_width: float, texture_height: float, texture_map: str | None, rgb: Tuple[float, float, float], transparency: float) Surface objects describe what bounding surfaces consist of. Parameters: name: Name of the surface. tmpm: Thermal properties. material_emissivity: Emissivity of the material. surface_type: Type of the surface. texture_width: Width of the texture of the surface. texture_height: Height of the texture of the surface. texture_map: Path to the texture map used for the surface. rgb: Color of the surface in form of a 3-element tuple. transparency: Transparency of the color (alpha channel). Variables: name: Name of the surface. material_emissivity: Emissivity of the material. surface_type: Type of the surface. texture_width: Width of the texture of the surface. texture_height: Height of the texture of the surface. texture_map: Path to the texture map used for the surface. rgb: Color of the surface in form of a 3-element tuple. transparency: Transparency of the color (alpha channel). id() Returns the identifier for the surface. ``` -------------------------------- ### Get Number of Timesteps Source: https://firedynamics.github.io/fdsreader/_modules/fdsreader/bndf/obstruction Returns the total number of timesteps for which boundary data is available. Optionally counts duplicate timesteps, which can occur during simulation restarts. ```Python def n_t(self, count_duplicates=True) -> int: """Returns the number of timesteps for which boundary data is available. :param count_duplicates: If true, return the total number of data points, even if there is duplicate data for a timestep. Duplicate data might be output when restarting the simulation in between the simulation run. """ if self.has_boundary_data: return next(iter(self._boundary_data.values())).n_t(count_duplicates=count_duplicates) return 0 ```