2  Verfügbare sichere Evakuierungszeit (ASET)

import fdsreader
import matplotlib.pyplot as plt
import numpy as np

Dieses Beispiel demonstriert eine Analyse von Slice-Daten, um die Karte der verfügbaren sicheren Evakuierungszeit (ASET) sowie die zeitliche Entwicklung der Rauchsichtbarkeitsschicht zu bestimmen. Das verwendete Szenario ist eine Mehrraumwohnung.

pfad_zur_simulation = '../skript/01-data/apartment_01'

sim = fdsreader.Simulation(pfad_zur_simulation)
print(sim)
Simulation(chid=Appartment,
           meshes=8,
           obstructions=23,
           slices=20,
           data_3d=5,
           smoke_3d=3)
/opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/numpy/lib/_npyio_impl.py:1048: UserWarning: no explicit representation of timezones available for np.datetime64
  arr = _load_from_filelike(
# Rußdichte-Slice laden, senkrecht zur z-Achse in 1,5 m Höhe
slc = sim.slices.get_by_id('SootDensityZ_1.5m')

# Da die Simulation auf mehreren Gittern basiert, wird eine globale Datenstruktur erstellt;
# Wände werden als ungültige Datenpunkte (NaN) dargestellt
slc_data = slc.to_global(masked=True, fill=np.nan)

Zuerst wird eine Visualisierung der Daten zu einem ausgewählten Zeitpunkt mit der Funktion imshow durchgeführt.

# Zeitindex ermitteln
it = slc.get_nearest_timestep(50)

# Daten visualisieren
plt.imshow(slc_data[it,:,:].T, origin='lower', extent=slc.extent.as_list())

# Achsenbeschriftung und Farbleiste
plt.title(f'Rußdichte bei t={slc.times[it]:.2f}s')
plt.xlabel('Position / m')
plt.ylabel('Position / m')
plt.colorbar(orientation='horizontal', label=f'{slc.quantity.name} / {slc.quantity.unit}')

Nun werden die lokalen ASET-Werte berechnet:

  1. Über alle räumlichen Elemente der Slice-Daten iterieren
  2. Alle Zeitpunkte bestimmen, an denen der Begehbarkeits-Grenzwert überschritten wird
  3. Falls dies der Fall ist, den ersten Zeitpunkt als lokalen ASET-Wert setzen
# Beliebiger Grenzwert für die Begehbarkeit
grenzwert_rußdichte = 1e-4

# Karte mit maximaler ASET als Standardwert erstellen
aset_karte = np.full_like(slc_data[0], slc.times[-1])

# Wände auf NaN setzen
aset_karte[np.isnan(slc_data[0,:,:])] = np.nan

# 1D-Schleife über alle Array-Indizes, ix ist ein zweidimensionaler Index
for ix in np.ndindex(aset_karte.shape):
    # Lokale Werte, die den Grenzwert überschreiten, ermitteln
    lokaler_aset = np.where(slc_data[:, ix[0], ix[1]] > grenzwert_rußdichte)[0]
    
    # Falls vorhanden, ersten Zeitpunkt als ASET-Wert verwenden
    if len(lokaler_aset) > 0:
        aset_karte[ix] = slc.times[lokaler_aset[0]]

Mit der berechneten Karte kann nun eine grafische Darstellung erzeugt werden, ähnlich wie bei anderen Größen – hier mit diskreter Farbskala.

# Diskrete (12 Werte) Farbskala erstellen
cmap = plt.cm.get_cmap('jet_r', 12)

# Visualisierung der ASET-Karte
plt.imshow(aset_karte.T, origin='lower', extent=slc.extent.as_list(), cmap=cmap)
plt.title(f'ASET-Karte mit Grenzwert der Rußdichte {grenzwert_rußdichte:.1e}')
plt.xlabel('x-Position / m')
plt.ylabel('y-Position / m')
plt.colorbar(orientation='horizontal', label='Zeit / s')

# Ausgabe in Datei speichern (optional)
# plt.savefig('figs/appartment_aset_map.svg', bbox_inches='tight')
# plt.close()
/tmp/ipykernel_3287/2604837150.py:2: MatplotlibDeprecationWarning: The get_cmap function was deprecated in Matplotlib 3.7 and will be removed in 3.11. Use ``matplotlib.colormaps[name]`` or ``matplotlib.colormaps.get_cmap()`` or ``pyplot.get_cmap()`` instead.
  cmap = plt.cm.get_cmap('jet_r', 12)

2.1 Rauchsichtbarkeitsschicht

In diesem Beispiel wird die Höhe der Rauchsichtbarkeitsschicht analysiert. Die Unterscheidung erfolgt hier anhand eines einfachen Temperatur-Grenzwerts: Die lokale Schichthöhe ergibt sich aus dem niedrigsten Punkt, an dem eine bestimmte Temperatur überschritten wird. Die Auswertung erfolgt auf einem Slice entlang des Brenners (normal zur x-Achse).

# Slice finden
slc = sim.slices.get_by_id('BurnerTempX')

# In globale Datenstruktur umwandeln und Koordinaten extrahieren
slc_data, slc_coords = slc.to_global(masked=True, fill=np.nan, return_coordinates=True)

Zunächst erfolgt eine Visualisierung der Daten zu einem beliebigen Zeitpunkt. Weiße Bereiche stellen Hindernisse dar.

# Zeitpunkt wählen
it = slc.get_nearest_timestep(150)

# Daten visualisieren
plt.imshow(slc_data[it,:,:].T, origin='lower', vmax=200, extent=slc.extent.as_list())
plt.title(f'Temperatur bei t={slc.times[it]:.2f}s')
plt.xlabel('y-Position / m')
plt.ylabel('z-Position / m')
plt.colorbar(orientation='horizontal', label=f'{slc.quantity.name} / {slc.quantity.unit}')

# plt.savefig('figs/appartment_temp_slice.svg', bbox_inches='tight')
# plt.close()

Nun wird für jede y-Position der z-Index gesucht, an dem die Temperatur den Grenzwert überschreitet. Der niedrigste dieser Punkte ist die lokale Höhe der Rauchsichtbarkeitsschicht.

# Grenzwert für Temperatur
temperatur_grenzwert = 75

# Array zur Speicherung der lokalen Höhenwerte; Standard ist maximale z-Koordinate
schicht_hoehe = np.full(slc_data.shape[1], slc_coords['z'][-1])

# Schleife über y-Indizes
for ix in range(len(schicht_hoehe)):
    # Indizes finden die den Grenzwert überschreiten
    lt = np.where(slc_data[it, ix, :] > temperatur_grenzwert)[0]
    # Wenn welche existieren, wähle den niedrigsten von ihnen
    if len(lt) > 0:
        schicht_hoehe[ix] = slc_coords['z'][lt[0]]

Die resultierenden Werte können nun über dem Slice dargestellt werden, um die Plausibilität zu überprüfen.

# Slice Daten
plt.imshow(slc_data[it,:,:].T, origin='lower', vmax=200, extent=slc.extent.as_list())
plt.title(f'Temperatur bei t={slc.times[it]:.2f}s')
plt.xlabel('y-Position / m')
plt.ylabel('z-Position / m')
plt.colorbar(orientation='horizontal', label=f'{slc.quantity.name} / {slc.quantity.unit}')
# Rauchsichtbarkeitsschichthöhe
plt.plot(slc_coords['y'], schicht_hoehe, '.-', color='red')

# plt.savefig('figs/appartment_temp_slice_height.svg', bbox_inches='tight')
# plt.close()

Die obige Methode kann auch über alle Zeitpunkte angewendet werden, um z. B. Mittelwert und Standardabweichung der Schichthöhe zu berechnen.

mittelwert = np.zeros_like(slc.times)
standardabweichung = np.zeros_like(slc.times)
res = np.zeros(slc_data.shape[1])

for it in range(len(slc.times)):
    res[:] = slc_coords['z'][-1]
    for ix in range(len(res)):
        lt = np.where(slc_data[it, ix, :] > temperatur_grenzwert)[0]
        if len(lt) > 0:
            res[ix] = slc_coords['z'][lt[0]]
    mittelwert[it] = np.mean(res)
    standardabweichung[it] = np.std(res)
# Darstellung des Mittelwerts und der Standartabweichung als Funktion der Zeit
plt.plot(slc.times, mittelwert, label='Mittlere Schichthöhe')
plt.plot(slc.times, standardabweichung, label='Standardabweichung')
plt.grid()
plt.legend()
plt.xlabel('Zeit / s')
plt.ylabel('Höhe / m')

# Ergebnisse in Datei abspeichern
# plt.savefig('figs/appartment_layer_mean_stddev.svg', bbox_inches='tight')
# plt.close()
Text(0, 0.5, 'Höhe / m')

Beide Werte können kombiniert visualisiert werden. Die Standardabweichung ergibt ein Band um den Mittelwert.

# Mittelwert darstellen
plt.plot(slc.times, mittelwert, label='Mittlere Schichthöhe')

# Band um den Mittelwert darstellen mit Hilfe der Standartabweichung
plt.fill_between(slc.times, mittelwert-standardabweichung, mittelwert+standardabweichung, color='C0', alpha=0.3)

# Boden als Referenz zeigen
plt.ylim(bottom=0)
plt.grid()
plt.legend()
plt.xlabel('Zeit / s')
plt.ylabel('Höhe / m')

# plt.savefig('figs/appartment_layer_mean_band.svg', bbox_inches='tight')
# plt.close()
Text(0, 0.5, 'Höhe / m')

Wenn bestimmte Bereiche ausgeschlossen werden sollen, kann eine koordinatenabhängige Maske verwendet werden.

# Finde Indizes bei denen die y-Koordinate zwischen den angegebenen Werten liegt
ymin = 1
ymax = 4
koordinaten_maske = np.where((slc_coords['y'] > ymin) & (slc_coords['y'] < ymax))
# Slice Daten
plt.imshow(slc_data[it,:,:].T, origin='lower', vmax=200, extent=slc.extent.as_list())
plt.title(f'Temperatur bei t={slc.times[it]:.2f}s')
plt.xlabel('y-Position / m')
plt.ylabel('z-Position / m')
plt.colorbar(orientation='horizontal', label=f'{slc.quantity.name} / {slc.quantity.unit}')
# Rauchsichtbarkeitsschichthöhe
plt.plot(slc_coords['y'][koordinaten_maske], schicht_hoehe[koordinaten_maske], '.-', color='red')

# plt.savefig('figs/appartment_temp_slice_height_mask.svg', bbox_inches='tight')
# plt.close()

Das gleiche Verfahren kann erneut verwendet werden, wobei Mittelwert und Standardabweichung nur auf die maskierten Werte berechnet werden.

for it in range(len(slc.times)):
    res[:] = slc_coords['z'][-1]
    for ix in np.ndindex(res.shape):
        lt = np.where(slc_data[it, ix, :] > temperatur_grenzwert)[1]
        if len(lt) > 0:
            res[ix] = slc_coords['z'][lt[0]]
    # Berechnungen werden nun mit den Werten aus der Maske durchgeführt
    mittelwert[it] = np.mean(res[koordinaten_maske])
    standardabweichung[it] = np.std(res[koordinaten_maske])
# Selbe Darstellung wie zuvor
plt.plot(slc.times, mittelwert, label='Mittlere Schichthöhe')
plt.fill_between(slc.times, mittelwert-standardabweichung, mittelwert+standardabweichung, color='C0', alpha=0.3)
plt.ylim(bottom=0)
plt.grid()
plt.legend()
plt.xlabel('Zeit / s')
plt.ylabel('Höhe / m')

# plt.savefig('figs/appartment_layer_mean_band_mask.svg', bbox_inches='tight')
# plt.close()
Text(0, 0.5, 'Höhe / m')