flood-adapt 0.3.10__py3-none-any.whl → 0.3.12__py3-none-any.whl
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.
- flood_adapt/__init__.py +3 -1
- flood_adapt/adapter/fiat_adapter.py +35 -9
- flood_adapt/adapter/sfincs_adapter.py +192 -93
- flood_adapt/adapter/sfincs_offshore.py +1 -7
- flood_adapt/config/gui.py +1 -0
- flood_adapt/database_builder/database_builder.py +316 -223
- flood_adapt/dbs_classes/database.py +100 -3
- flood_adapt/dbs_classes/dbs_benefit.py +1 -0
- flood_adapt/dbs_classes/dbs_event.py +1 -0
- flood_adapt/dbs_classes/dbs_measure.py +1 -0
- flood_adapt/dbs_classes/dbs_projection.py +1 -0
- flood_adapt/dbs_classes/dbs_scenario.py +1 -0
- flood_adapt/dbs_classes/dbs_strategy.py +1 -0
- flood_adapt/dbs_classes/dbs_template.py +2 -1
- flood_adapt/flood_adapt.py +23 -1
- flood_adapt/misc/log.py +20 -12
- flood_adapt/objects/events/events.py +5 -3
- flood_adapt/objects/events/historical.py +3 -3
- flood_adapt/objects/events/hurricane.py +1 -1
- flood_adapt/objects/forcing/plotting.py +7 -34
- flood_adapt/objects/measures/measures.py +88 -66
- {flood_adapt-0.3.10.dist-info → flood_adapt-0.3.12.dist-info}/METADATA +2 -1
- {flood_adapt-0.3.10.dist-info → flood_adapt-0.3.12.dist-info}/RECORD +26 -26
- {flood_adapt-0.3.10.dist-info → flood_adapt-0.3.12.dist-info}/LICENSE +0 -0
- {flood_adapt-0.3.10.dist-info → flood_adapt-0.3.12.dist-info}/WHEEL +0 -0
- {flood_adapt-0.3.10.dist-info → flood_adapt-0.3.12.dist-info}/top_level.txt +0 -0
flood_adapt/__init__.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# has to be here at the start to avoid circular imports
|
|
2
|
-
__version__ = "0.3.
|
|
2
|
+
__version__ = "0.3.12"
|
|
3
3
|
|
|
4
4
|
from flood_adapt import adapter, dbs_classes, objects
|
|
5
5
|
from flood_adapt.config.config import Settings
|
|
6
6
|
from flood_adapt.config.site import Site
|
|
7
|
+
from flood_adapt.database_builder.database_builder import DatabaseBuilder
|
|
7
8
|
from flood_adapt.flood_adapt import FloodAdapt
|
|
8
9
|
from flood_adapt.misc.exceptions import ComponentError, DatabaseError, FloodAdaptError
|
|
9
10
|
from flood_adapt.misc.log import FloodAdaptLogging
|
|
@@ -21,6 +22,7 @@ __all__ = [
|
|
|
21
22
|
"FloodAdaptError",
|
|
22
23
|
"DatabaseError",
|
|
23
24
|
"ComponentError",
|
|
25
|
+
"DatabaseBuilder",
|
|
24
26
|
]
|
|
25
27
|
|
|
26
28
|
FloodAdaptLogging() # Initialize logging once for the entire package
|
|
@@ -153,11 +153,16 @@ class FiatAdapter(IImpactAdapter):
|
|
|
153
153
|
|
|
154
154
|
def close_files(self):
|
|
155
155
|
"""Close all open files and clean up file handles."""
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
156
|
+
loggers = [self.logger]
|
|
157
|
+
if self._model is not None:
|
|
158
|
+
loggers.append(self._model.logger)
|
|
159
|
+
|
|
160
|
+
for logger in loggers:
|
|
161
|
+
if hasattr(logger, "handlers"):
|
|
162
|
+
for handler in logger.handlers:
|
|
163
|
+
if isinstance(handler, logging.FileHandler):
|
|
164
|
+
handler.close()
|
|
165
|
+
logger.removeHandler(handler)
|
|
161
166
|
|
|
162
167
|
def __enter__(self) -> "FiatAdapter":
|
|
163
168
|
return self
|
|
@@ -198,11 +203,10 @@ class FiatAdapter(IImpactAdapter):
|
|
|
198
203
|
------
|
|
199
204
|
OSError: If the directory cannot be deleted.
|
|
200
205
|
"""
|
|
201
|
-
self.
|
|
202
|
-
|
|
206
|
+
self.close_files()
|
|
207
|
+
if self.model_root.exists():
|
|
208
|
+
self.logger.info(f"Deleting {self.model_root}")
|
|
203
209
|
shutil.rmtree(self.model_root)
|
|
204
|
-
except OSError as e_info:
|
|
205
|
-
self.logger.warning(f"{e_info}\nCould not delete {self.model_root}.")
|
|
206
210
|
|
|
207
211
|
def fiat_completed(self) -> bool:
|
|
208
212
|
"""Check if fiat has run as expected.
|
|
@@ -1539,3 +1543,25 @@ class FiatAdapter(IImpactAdapter):
|
|
|
1539
1543
|
if line.startswith("#"):
|
|
1540
1544
|
line = "#" + " " * hash_spacing + line.lstrip("#")
|
|
1541
1545
|
file.write(line)
|
|
1546
|
+
|
|
1547
|
+
def _delete_simulation_folder(self, scn: Scenario):
|
|
1548
|
+
"""
|
|
1549
|
+
Delete the Delft-FIAT simulation folder for a given scenario.
|
|
1550
|
+
|
|
1551
|
+
Parameters
|
|
1552
|
+
----------
|
|
1553
|
+
scn : Scenario
|
|
1554
|
+
The scenario for which the simulation folder should be deleted.
|
|
1555
|
+
|
|
1556
|
+
Raises
|
|
1557
|
+
------
|
|
1558
|
+
OSError
|
|
1559
|
+
If the directory cannot be deleted.
|
|
1560
|
+
"""
|
|
1561
|
+
simulation_path = (
|
|
1562
|
+
self.database.scenarios.output_path / scn.name / "Impacts" / "fiat_model"
|
|
1563
|
+
)
|
|
1564
|
+
if simulation_path.exists():
|
|
1565
|
+
self.close_files()
|
|
1566
|
+
shutil.rmtree(simulation_path)
|
|
1567
|
+
self.logger.info(f"Deleted Delft-FIAT simulation folder: {simulation_path}")
|
|
@@ -35,8 +35,8 @@ from flood_adapt.misc.path_builder import (
|
|
|
35
35
|
from flood_adapt.misc.utils import cd, resolve_filepath
|
|
36
36
|
from flood_adapt.objects.events.event_set import EventSet
|
|
37
37
|
from flood_adapt.objects.events.events import Event, Mode, Template
|
|
38
|
-
from flood_adapt.objects.events.historical import HistoricalEvent
|
|
39
38
|
from flood_adapt.objects.events.hurricane import TranslationModel
|
|
39
|
+
from flood_adapt.objects.events.synthetic import SyntheticEvent
|
|
40
40
|
from flood_adapt.objects.forcing import unit_system as us
|
|
41
41
|
from flood_adapt.objects.forcing.discharge import (
|
|
42
42
|
DischargeConstant,
|
|
@@ -257,9 +257,8 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
257
257
|
template_path = (
|
|
258
258
|
self.database.static.get_overland_sfincs_model().get_model_root()
|
|
259
259
|
)
|
|
260
|
-
shutil.copytree(template_path, sim_path, dirs_exist_ok=True)
|
|
261
260
|
|
|
262
|
-
with SfincsAdapter(model_root=
|
|
261
|
+
with SfincsAdapter(model_root=template_path) as model:
|
|
263
262
|
model._load_scenario_objects(scenario, event)
|
|
264
263
|
is_risk = "Probabilistic " if model._event_set is not None else ""
|
|
265
264
|
self.logger.info(
|
|
@@ -765,89 +764,32 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
765
764
|
|
|
766
765
|
with SfincsAdapter(model_root=sim_paths[0]) as dummymodel:
|
|
767
766
|
# read mask and bed level
|
|
768
|
-
mask = dummymodel.get_mask()
|
|
769
|
-
zb = dummymodel.get_bedlevel()
|
|
767
|
+
mask = dummymodel.get_mask()
|
|
768
|
+
zb = dummymodel.get_bedlevel()
|
|
770
769
|
|
|
771
770
|
zs_maps = []
|
|
772
771
|
for simulation_path in sim_paths:
|
|
773
772
|
# read zsmax data from overland sfincs model
|
|
774
773
|
with SfincsAdapter(model_root=simulation_path) as sim:
|
|
775
774
|
zsmax = sim._get_zsmax().load()
|
|
776
|
-
|
|
777
|
-
zs_maps.append(zs_stacked)
|
|
775
|
+
zs_maps.append(zsmax)
|
|
778
776
|
|
|
779
777
|
# Create RP flood maps
|
|
780
|
-
|
|
781
|
-
# 1a: make a table of all water levels and associated frequencies
|
|
782
|
-
zs = xr.concat(zs_maps, pd.Index(frequencies, name="frequency"))
|
|
783
|
-
# Get the indices of columns with all NaN values
|
|
784
|
-
nan_cells = np.where(np.all(np.isnan(zs), axis=0))[0]
|
|
785
|
-
# fill nan values with minimum bed levels in each grid cell, np.interp cannot ignore nan values
|
|
786
|
-
zs = xr.where(np.isnan(zs), np.tile(zb, (zs.shape[0], 1)), zs)
|
|
787
|
-
# Get table of frequencies
|
|
788
|
-
freq = np.tile(frequencies, (zs.shape[1], 1)).transpose()
|
|
789
|
-
|
|
790
|
-
# 1b: sort water levels in descending order and include the frequencies in the sorting process
|
|
791
|
-
# (i.e. each h-value should be linked to the same p-values as in step 1a)
|
|
792
|
-
sort_index = zs.argsort(axis=0)
|
|
793
|
-
sorted_prob = np.flipud(np.take_along_axis(freq, sort_index, axis=0))
|
|
794
|
-
sorted_zs = np.flipud(np.take_along_axis(zs.values, sort_index, axis=0))
|
|
795
|
-
|
|
796
|
-
# 1c: Compute exceedance probabilities of water depths
|
|
797
|
-
# Method: accumulate probabilities from top to bottom
|
|
798
|
-
prob_exceed = np.cumsum(sorted_prob, axis=0)
|
|
799
|
-
|
|
800
|
-
# 1d: Compute return periods of water depths
|
|
801
|
-
# Method: simply take the inverse of the exceedance probability (1/Pex)
|
|
802
|
-
rp_zs = 1.0 / prob_exceed
|
|
803
|
-
|
|
804
|
-
# For each return period (T) of interest do the following:
|
|
805
|
-
# For each grid cell do the following:
|
|
806
|
-
# Use the table from step [1d] as a “lookup-table” to derive the T-year water depth. Use a 1-d interpolation technique:
|
|
807
|
-
# h(T) = interp1 (log(T*), h*, log(T))
|
|
808
|
-
# in which t* and h* are the values from the table and T is the return period (T) of interest
|
|
809
|
-
# The resulting T-year water depths for all grids combined form the T-year hazard map
|
|
810
|
-
rp_da = xr.DataArray(rp_zs, dims=zs.dims)
|
|
811
|
-
|
|
812
|
-
# no_data_value = -999 # in SFINCS
|
|
813
|
-
# sorted_zs = xr.where(sorted_zs == no_data_value, np.nan, sorted_zs)
|
|
814
|
-
|
|
815
|
-
valid_cells = np.where(mask == 1)[
|
|
816
|
-
0
|
|
817
|
-
] # only loop over cells where model is not masked
|
|
818
|
-
h = matlib.repmat(
|
|
819
|
-
np.copy(zb), len(floodmap_rp), 1
|
|
820
|
-
) # if not flooded (i.e. not in valid_cells) revert to bed_level, read from SFINCS results so it is the minimum bed level in a grid cell
|
|
821
|
-
|
|
822
778
|
self.logger.info("Calculating flood risk maps, this may take some time")
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
)
|
|
831
|
-
|
|
832
|
-
# Re-fill locations that had nan water level for all simulations with nans
|
|
833
|
-
h[:, nan_cells] = np.full(h[:, nan_cells].shape, np.nan)
|
|
834
|
-
|
|
835
|
-
# If a cell has the same water-level as the bed elevation it should be dry (turn to nan)
|
|
836
|
-
diff = h - np.tile(zb, (h.shape[0], 1))
|
|
837
|
-
dry = (
|
|
838
|
-
diff < 10e-10
|
|
839
|
-
) # here we use a small number instead of zero for rounding errors
|
|
840
|
-
h[dry] = np.nan
|
|
779
|
+
rp_flood_maps = self.calc_rp_maps(
|
|
780
|
+
floodmaps=zs_maps,
|
|
781
|
+
frequencies=frequencies,
|
|
782
|
+
zb=zb,
|
|
783
|
+
mask=mask,
|
|
784
|
+
return_periods=floodmap_rp,
|
|
785
|
+
)
|
|
841
786
|
|
|
842
787
|
for ii, rp in enumerate(floodmap_rp):
|
|
843
|
-
|
|
844
|
-
zs_rp_single = xr.DataArray(
|
|
845
|
-
data=h[ii, :], coords={"z": zs["z"]}, attrs={"units": "meters"}
|
|
846
|
-
).unstack()
|
|
788
|
+
zs_rp_single = rp_flood_maps[ii]
|
|
847
789
|
zs_rp_single = zs_rp_single.rio.write_crs(
|
|
848
790
|
zsmax.raster.crs
|
|
849
791
|
) # , inplace=True)
|
|
850
|
-
zs_rp_single = zs_rp_single.to_dataset(name="risk_map")
|
|
792
|
+
zs_rp_single = zs_rp_single.to_dataset(name="risk_map").transpose()
|
|
851
793
|
fn_rp = result_path / f"RP_{rp:04d}_maps.nc"
|
|
852
794
|
zs_rp_single.to_netcdf(fn_rp)
|
|
853
795
|
|
|
@@ -885,9 +827,22 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
885
827
|
self.preprocess(scenario, event)
|
|
886
828
|
self.process(scenario, event)
|
|
887
829
|
self.postprocess(scenario, event)
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
830
|
+
|
|
831
|
+
if self.settings.config.save_simulation:
|
|
832
|
+
self._delete_simulation_folder(scenario, sub_event=event)
|
|
833
|
+
|
|
834
|
+
def _delete_simulation_folder(
|
|
835
|
+
self, scenario: Scenario, sub_event: Optional[Event] = None
|
|
836
|
+
):
|
|
837
|
+
"""Delete the simulation folder for a given scenario and optional sub-event."""
|
|
838
|
+
sim_path = self._get_simulation_path(scenario, sub_event=sub_event)
|
|
839
|
+
if sim_path.exists():
|
|
840
|
+
shutil.rmtree(sim_path, ignore_errors=True)
|
|
841
|
+
self.logger.info(f"Deleted simulation folder: {sim_path}")
|
|
842
|
+
|
|
843
|
+
if sim_path.parent.exists() and not any(sim_path.parent.iterdir()):
|
|
844
|
+
# Remove the parent directory `simulations` if it is empty
|
|
845
|
+
sim_path.parent.rmdir()
|
|
891
846
|
|
|
892
847
|
def _run_risk_scenario(self, scenario: Scenario):
|
|
893
848
|
"""Run the whole workflow for a risk scenario.
|
|
@@ -911,11 +866,12 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
911
866
|
self.calculate_rp_floodmaps(scenario)
|
|
912
867
|
|
|
913
868
|
# Cleanup
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
869
|
+
if not self.settings.config.save_simulation:
|
|
870
|
+
for i, sub_event in enumerate(event_set._events):
|
|
871
|
+
shutil.rmtree(
|
|
872
|
+
self._get_simulation_path(scenario, sub_event=sub_event),
|
|
873
|
+
ignore_errors=True,
|
|
874
|
+
)
|
|
919
875
|
|
|
920
876
|
def _ensure_no_existing_forcings(self):
|
|
921
877
|
"""Check for existing forcings in the model and raise an error if any are found."""
|
|
@@ -1819,8 +1775,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
1819
1775
|
def _add_tide_gauge_plot(
|
|
1820
1776
|
self, fig, event: Event, units: us.UnitTypesLength
|
|
1821
1777
|
) -> None:
|
|
1822
|
-
|
|
1823
|
-
if not isinstance(event, HistoricalEvent):
|
|
1778
|
+
if isinstance(event, SyntheticEvent):
|
|
1824
1779
|
return
|
|
1825
1780
|
if self.settings.tide_gauge is None:
|
|
1826
1781
|
return
|
|
@@ -1832,17 +1787,161 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
1832
1787
|
units=us.UnitTypesLength(units),
|
|
1833
1788
|
)
|
|
1834
1789
|
|
|
1835
|
-
if df_gauge is
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
)
|
|
1790
|
+
if df_gauge is None:
|
|
1791
|
+
self.logger.warning(
|
|
1792
|
+
"No water level data available for the tide gauge. Could not add it to the plot."
|
|
1793
|
+
)
|
|
1794
|
+
return
|
|
1795
|
+
|
|
1796
|
+
gauge_reference_height = self.settings.water_level.get_datum(
|
|
1797
|
+
self.settings.tide_gauge.reference
|
|
1798
|
+
).height.convert(units)
|
|
1799
|
+
|
|
1800
|
+
waterlevel = df_gauge.iloc[:, 0] + gauge_reference_height
|
|
1801
|
+
|
|
1802
|
+
# If data is available, add to plot
|
|
1803
|
+
fig.add_trace(px.line(waterlevel, color_discrete_sequence=["#ea6404"]).data[0])
|
|
1804
|
+
fig["data"][0]["name"] = "model"
|
|
1805
|
+
fig["data"][1]["name"] = "measurement"
|
|
1806
|
+
fig.update_layout(showlegend=True)
|
|
1807
|
+
|
|
1808
|
+
@staticmethod
|
|
1809
|
+
def calc_rp_maps(
|
|
1810
|
+
floodmaps: list[xr.DataArray],
|
|
1811
|
+
frequencies: list[float],
|
|
1812
|
+
zb: xr.DataArray,
|
|
1813
|
+
mask: xr.DataArray,
|
|
1814
|
+
return_periods: list[float],
|
|
1815
|
+
) -> list[xr.DataArray]:
|
|
1816
|
+
"""
|
|
1817
|
+
Calculate return period (RP) flood maps from a set of flood simulation results.
|
|
1839
1818
|
|
|
1840
|
-
|
|
1819
|
+
This function processes multiple flood simulation outputs (water level maps) and their associated frequencies
|
|
1820
|
+
to generate hazard maps for specified return periods. It interpolates water levels for each return period
|
|
1821
|
+
using exceedance probabilities and handles masked or dry cells appropriately.
|
|
1841
1822
|
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1823
|
+
Args:
|
|
1824
|
+
floodmaps (list[xr.DataArray]): List of water level maps (xarray DataArrays), one for each simulation.
|
|
1825
|
+
frequencies (list[float]): List of frequencies (probabilities of occurrence) corresponding to each floodmap.
|
|
1826
|
+
zb (np.ndarray): Array of bed elevations for each grid cell.
|
|
1827
|
+
mask (xr.DataArray): Mask indicating valid (1) and invalid (0) grid cells.
|
|
1828
|
+
return_periods (list[float]): List of return periods (in years) for which to generate hazard maps.
|
|
1829
|
+
|
|
1830
|
+
Returns
|
|
1831
|
+
-------
|
|
1832
|
+
list[xr.DataArray]: List of xarray DataArrays, each representing the hazard map for a given return period.
|
|
1833
|
+
Each DataArray contains water levels (meters) for the corresponding return period.
|
|
1834
|
+
"""
|
|
1835
|
+
floodmaps = floodmaps.copy() # avoid modifying the original list
|
|
1836
|
+
# Check that all floodmaps have the same shape and dimensions
|
|
1837
|
+
first_shape = floodmaps[0].shape
|
|
1838
|
+
first_dims = floodmaps[0].dims
|
|
1839
|
+
for i, floodmap in enumerate(floodmaps):
|
|
1840
|
+
if floodmap.shape != first_shape or floodmap.dims != first_dims:
|
|
1841
|
+
raise ValueError(
|
|
1842
|
+
f"Floodmap at index {i} does not match the shape or dimensions of the first floodmap. "
|
|
1843
|
+
f"Expected shape {first_shape} and dims {first_dims}, got shape {floodmap.shape} and dims {floodmap.dims}."
|
|
1844
|
+
)
|
|
1845
|
+
|
|
1846
|
+
# Check that zb and mask have the same shape
|
|
1847
|
+
if zb.shape != mask.shape:
|
|
1848
|
+
raise ValueError(
|
|
1849
|
+
"Bed elevation array (zb) and mask must have the same shape."
|
|
1850
|
+
)
|
|
1851
|
+
|
|
1852
|
+
# Check that floodmaps, zb, and mask all have the same shape
|
|
1853
|
+
if (
|
|
1854
|
+
len(first_shape) != len(zb.shape)
|
|
1855
|
+
or first_shape != zb.shape
|
|
1856
|
+
or first_shape != mask.shape
|
|
1857
|
+
):
|
|
1858
|
+
raise ValueError(
|
|
1859
|
+
f"Floodmaps, bed elevation array (zb), and mask must all have the same shape. "
|
|
1860
|
+
f"Floodmap shape: {first_shape}, zb shape: {zb.shape}, mask shape: {mask.shape}."
|
|
1861
|
+
)
|
|
1862
|
+
|
|
1863
|
+
# stack dimensions if floodmaps are 2D
|
|
1864
|
+
if len(floodmaps[0].shape) > 1:
|
|
1865
|
+
stacking = True
|
|
1866
|
+
for i, floodmap in enumerate(floodmaps):
|
|
1867
|
+
floodmaps[i] = floodmap.stack(z=("x", "y"))
|
|
1868
|
+
zb = zb.stack(z=("x", "y"))
|
|
1869
|
+
mask = mask.stack(z=("x", "y"))
|
|
1870
|
+
else:
|
|
1871
|
+
stacking = False
|
|
1872
|
+
|
|
1873
|
+
# 1a: make a table of all water levels and associated frequencies
|
|
1874
|
+
zs = xr.concat(floodmaps, pd.Index(frequencies, name="frequency"))
|
|
1875
|
+
# Get the indices of columns with all NaN values
|
|
1876
|
+
nan_cells = np.where(np.all(np.isnan(zs), axis=0))[0]
|
|
1877
|
+
# fill nan values with minimum bed levels in each grid cell, np.interp cannot ignore nan values
|
|
1878
|
+
zs = xr.where(np.isnan(zs), np.tile(zb, (zs.shape[0], 1)), zs)
|
|
1879
|
+
# Get table of frequencies
|
|
1880
|
+
freq = np.tile(frequencies, (zs.shape[1], 1)).transpose()
|
|
1881
|
+
|
|
1882
|
+
# 1b: sort water levels in descending order and include the frequencies in the sorting process
|
|
1883
|
+
# (i.e. each h-value should be linked to the same p-values as in step 1a)
|
|
1884
|
+
sort_index = zs.argsort(axis=0)
|
|
1885
|
+
sorted_prob = np.flipud(np.take_along_axis(freq, sort_index, axis=0))
|
|
1886
|
+
sorted_zs = np.flipud(np.take_along_axis(zs.values, sort_index, axis=0))
|
|
1887
|
+
|
|
1888
|
+
# 1c: Compute exceedance probabilities of water depths
|
|
1889
|
+
# Method: accumulate probabilities from top to bottom
|
|
1890
|
+
prob_exceed = np.cumsum(sorted_prob, axis=0)
|
|
1891
|
+
|
|
1892
|
+
# 1d: Compute return periods of water depths
|
|
1893
|
+
# Method: simply take the inverse of the exceedance probability (1/Pex)
|
|
1894
|
+
rp_zs = 1.0 / prob_exceed
|
|
1895
|
+
|
|
1896
|
+
# For each return period (T) of interest do the following:
|
|
1897
|
+
# For each grid cell do the following:
|
|
1898
|
+
# Use the table from step [1d] as a “lookup-table” to derive the T-year water depth. Use a 1-d interpolation technique:
|
|
1899
|
+
# h(T) = interp1 (log(T*), h*, log(T))
|
|
1900
|
+
# in which t* and h* are the values from the table and T is the return period (T) of interest
|
|
1901
|
+
# The resulting T-year water depths for all grids combined form the T-year hazard map
|
|
1902
|
+
rp_da = xr.DataArray(rp_zs, dims=zs.dims)
|
|
1903
|
+
|
|
1904
|
+
# no_data_value = -999 # in SFINCS
|
|
1905
|
+
# sorted_zs = xr.where(sorted_zs == no_data_value, np.nan, sorted_zs)
|
|
1906
|
+
|
|
1907
|
+
valid_cells = np.where(mask == 1)[
|
|
1908
|
+
0
|
|
1909
|
+
] # only loop over cells where model is not masked
|
|
1910
|
+
h = matlib.repmat(
|
|
1911
|
+
np.copy(zb), len(return_periods), 1
|
|
1912
|
+
) # if not flooded (i.e. not in valid_cells) revert to bed_level, read from SFINCS results so it is the minimum bed level in a grid cell
|
|
1913
|
+
|
|
1914
|
+
for jj in valid_cells: # looping over all non-masked cells.
|
|
1915
|
+
# linear interpolation for all return periods to evaluate
|
|
1916
|
+
h[:, jj] = np.interp(
|
|
1917
|
+
np.log10(return_periods),
|
|
1918
|
+
np.log10(rp_da[::-1, jj]),
|
|
1919
|
+
sorted_zs[::-1, jj],
|
|
1920
|
+
left=0,
|
|
1845
1921
|
)
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1922
|
+
|
|
1923
|
+
# Re-fill locations that had nan water level for all simulations with nans
|
|
1924
|
+
h[:, nan_cells] = np.full(h[:, nan_cells].shape, np.nan)
|
|
1925
|
+
|
|
1926
|
+
# If a cell has the same water-level as the bed elevation it should be dry (turn to nan)
|
|
1927
|
+
diff = h - np.tile(zb, (h.shape[0], 1))
|
|
1928
|
+
dry = (
|
|
1929
|
+
diff < 10e-10
|
|
1930
|
+
) # here we use a small number instead of zero for rounding errors
|
|
1931
|
+
h[dry] = np.nan
|
|
1932
|
+
|
|
1933
|
+
rp_maps = []
|
|
1934
|
+
for ii, rp in enumerate(return_periods):
|
|
1935
|
+
da = xr.DataArray(
|
|
1936
|
+
data=h[ii, :], coords={"z": zs["z"]}, attrs={"units": "meters"}
|
|
1937
|
+
)
|
|
1938
|
+
if stacking:
|
|
1939
|
+
# Ensure unstacking creates (y, x) dimensions in the correct order
|
|
1940
|
+
da = da.unstack()
|
|
1941
|
+
# Reorder dimensions if needed
|
|
1942
|
+
if set(da.dims) == {"y", "x"} and da.dims != ("y", "x"):
|
|
1943
|
+
da = da.transpose("y", "x")
|
|
1944
|
+
# #create single nc
|
|
1945
|
+
rp_maps.append(da)
|
|
1946
|
+
|
|
1947
|
+
return rp_maps
|
|
@@ -97,14 +97,8 @@ class OffshoreSfincsHandler(IOffshoreSfincsHandler, DatabaseUser):
|
|
|
97
97
|
# SfincsAdapter.write() doesnt write the bca file apparently so we need to copy the template
|
|
98
98
|
if sim_path.exists():
|
|
99
99
|
shutil.rmtree(sim_path)
|
|
100
|
-
shutil.copytree(self.template_path, sim_path)
|
|
101
100
|
|
|
102
|
-
with SfincsAdapter(model_root=
|
|
103
|
-
if _offshore_model.sfincs_completed(sim_path):
|
|
104
|
-
_offshore_model.logger.info(
|
|
105
|
-
f"Skip preprocessing offshore model as it has already been run for `{self.scenario.name}`."
|
|
106
|
-
)
|
|
107
|
-
return
|
|
101
|
+
with SfincsAdapter(model_root=self.template_path) as _offshore_model:
|
|
108
102
|
# Load objects, set root & write template model
|
|
109
103
|
_offshore_model._load_scenario_objects(self.scenario, self.event)
|
|
110
104
|
_offshore_model.write(path_out=sim_path)
|