diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml old mode 100644 new mode 100755 diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml old mode 100644 new mode 100755 diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100644 new mode 100755 index f1ec66f..ea08aa3 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,7 @@ include(FetchContent) FetchContent_Declare( topotoolbox GIT_REPOSITORY https://github.com/TopoToolbox/libtopotoolbox.git - #GIT_TAG main - GIT_TAG 2024-W34 + GIT_TAG 2024-W40 ) FetchContent_MakeAvailable(topotoolbox) @@ -23,4 +22,7 @@ target_link_libraries(_grid PRIVATE topotoolbox) pybind11_add_module(_flow src/lib/flow.cpp) target_link_libraries(_flow PRIVATE topotoolbox) -install(TARGETS _grid _flow LIBRARY DESTINATION topotoolbox) +pybind11_add_module(_graphflood src/lib/graphflood.cpp) +target_link_libraries(_graphflood PRIVATE topotoolbox) + +install(TARGETS _grid _flow _graphflood LIBRARY DESTINATION topotoolbox) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md old mode 100644 new mode 100755 diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/LICENSES_bundled b/LICENSES_bundled old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/docs/Makefile b/docs/Makefile old mode 100644 new mode 100755 diff --git a/docs/README.md b/docs/README.md old mode 100644 new mode 100755 diff --git a/docs/_static/thumbnails/placeholder.png b/docs/_static/thumbnails/placeholder.png old mode 100644 new mode 100755 diff --git a/docs/api.rst b/docs/api.rst old mode 100644 new mode 100755 diff --git a/docs/conf.py b/docs/conf.py old mode 100644 new mode 100755 diff --git a/docs/examples.rst b/docs/examples.rst old mode 100644 new mode 100755 diff --git a/docs/index.rst b/docs/index.rst old mode 100644 new mode 100755 diff --git a/docs/installing.rst b/docs/installing.rst old mode 100644 new mode 100755 diff --git a/docs/logo.png b/docs/logo.png old mode 100644 new mode 100755 diff --git a/docs/make.bat b/docs/make.bat old mode 100644 new mode 100755 diff --git a/docs/requirements.txt b/docs/requirements.txt old mode 100644 new mode 100755 diff --git a/docs/tutorial.ipynb b/docs/tutorial.ipynb old mode 100644 new mode 100755 diff --git a/pyproject.toml b/pyproject.toml old mode 100644 new mode 100755 diff --git a/src/lib/flow.cpp b/src/lib/flow.cpp old mode 100644 new mode 100755 diff --git a/src/lib/graphflood.cpp b/src/lib/graphflood.cpp new file mode 100755 index 0000000..b13f238 --- /dev/null +++ b/src/lib/graphflood.cpp @@ -0,0 +1,171 @@ +// This file contains bindings for the fillsinks function using PyBind11. + +extern "C" { + #include +} + +#include +#include +#include +#include +#include + +namespace py = pybind11; + + +/* +Runs the full graphflood's algorithm as described in Gailleton et al., 2024 + +Z: 1D numpy array (n nodes) of topography (type np.float32) +hw: 1D numpy array (n nodes) of flow depth type np.float32) +BCs: 1D numpy array (n nodes) of boundary codes (type np.uint8) +Precipitations: 1D numpy array (n nodes) of precipitation rates in m.s-1 (type np.float32) +manning: 1D numpy array (n nodes) of friction coefficient (type np.float32) +dim: [nrows,ncolumns] for row major (e.g. python) or [ncolumns, nrows] for column major. Numpy array as np.uint64. +dt: time step (s ~ although this is not simulated time as we make the steady low assumption) +dx: spatial step (m) +D8: true to include diagonal paths +N_iterations: how many iterations to run + +B.G. (last modification: 08/2024) +*/ +void wrap_graphflood_full( + py::array_t Z, + py::array_t hw, + py::array_t BCs, + py::array_t Precipitations, + py::array_t manning, + py::array_t dim, + GF_FLOAT dt, + GF_FLOAT dx, + bool SFD, + bool D8, + GF_UINT N_iterations, + GF_FLOAT step + ){ + + // numpy arrays to pointers + GF_FLOAT* Z_ptr = Z.mutable_data() ; + GF_FLOAT* hw_ptr = hw.mutable_data() ; + uint8_t* BCs_ptr = BCs.mutable_data() ; + GF_FLOAT* Precipitations_ptr = Precipitations.mutable_data() ; + GF_FLOAT* manning_ptr = manning.mutable_data() ; + GF_UINT* dim_ptr = dim.mutable_data() ; + + // calling the C function + graphflood_full(Z_ptr, hw_ptr, BCs_ptr, Precipitations_ptr, manning_ptr, dim_ptr, dt, dx, SFD, D8, N_iterations, step); +} + + +/* +Computes a single flow graph compatible with the boundary conditions system of graphflood + +topo: 1D numpy array (n nodes) of topography (type np.float32) +Sreceivers: 1D numpy array (n nodes), flat index of steepest receiver (type np.uint64) +distToReceivers: 1D numpy array (n nodes), distance to steepest receiver (type np.float32) +Sdonors: 1D numpy array (n nodes * 4 or 8), flat index of donors (type np.uint64) +NSdonors: 1D numpy array (n nodes ), flat index of number of steepest donors (type np.uint8) +Stack: Topologically ordered nodes from downstream to upstream (stakc in Braun and Willett 2013) +BCs: 1D numpy array (n nodes) of boundary codes (type np.uint8) +dim: [nrows,ncolumns] for row major (e.g. python) or [ncolumns, nrows] for column major. Numpy array as np.uint64. +dx: spatial step (m) +D8: true to include diagonal paths +PF: true to also fill the topography using priority flood (Barnes, 2014) + +B.G. (last modification: 08/2024) +*/ +void wrap_compute_sfgraph( + py::array_t topo, + py::array_t Sreceivers, + py::array_t distToReceivers, + py::array_t Sdonors, + py::array_t NSdonors, + py::array_t Stack, + py::array_t BCs, + py::array_t dim, + GF_FLOAT dx, + bool D8, + bool PF, + GF_FLOAT step + ){ + + GF_FLOAT *topo_ptr = topo.mutable_data() ; + GF_UINT *Sreceivers_ptr = Sreceivers.mutable_data() ; + GF_FLOAT *distToReceivers_ptr = distToReceivers.mutable_data() ; + GF_UINT *Sdonors_ptr = Sdonors.mutable_data() ; + uint8_t *NSdonors_ptr = NSdonors.mutable_data() ; + GF_UINT *Stack_ptr = Stack.mutable_data() ; + uint8_t *BCs_ptr = BCs.mutable_data() ; + GF_UINT *dim_ptr = dim.mutable_data() ; + + if(PF) + compute_sfgraph_priority_flood(topo_ptr, Sreceivers_ptr, distToReceivers_ptr, Sdonors_ptr, NSdonors_ptr, Stack_ptr, BCs_ptr, dim_ptr, dx, D8, step); + else + compute_sfgraph(topo_ptr, Sreceivers_ptr, distToReceivers_ptr, Sdonors_ptr, NSdonors_ptr, Stack_ptr, BCs_ptr, dim_ptr, dx, D8); + +} + +void wrap_compute_drainage_area_single_flow( + py::array_t output, + py::array_t Sreceivers, + py::array_t Stack, + py::array_t dim, + GF_FLOAT dx + ){ + + GF_FLOAT* output_ptr = output.mutable_data(); + GF_UINT* Sreceivers_ptr = Sreceivers.mutable_data(); + GF_UINT* Stack_ptr = Stack.mutable_data(); + GF_UINT* dim_ptr = dim.mutable_data(); + + + compute_drainage_area_single_flow(output_ptr, Sreceivers_ptr, Stack_ptr, dim_ptr, dx); +} + +void wrap_compute_priority_flood_plus_stack( + py::array_t topo, + py::array_t Stack, + py::array_t BCs, + py::array_t dim, + bool D8 , + GF_FLOAT step + ){ + + GF_FLOAT* topo_ptr = topo.mutable_data(); + GF_UINT* Stack_ptr = Stack.mutable_data(); + uint8_t* BCs_ptr = BCs.mutable_data(); + GF_UINT* dim_ptr = dim.mutable_data(); + + // First priority flooding and calculating stack + compute_priority_flood_plus_topological_ordering(topo_ptr, Stack_ptr, BCs_ptr, dim_ptr, D8,step); +} + +void wrap_compute_priority_flood( + py::array_t topo, + py::array_t BCs, + py::array_t dim, + bool D8 , + GF_FLOAT step + ){ + + GF_FLOAT* topo_ptr = topo.mutable_data(); + uint8_t* BCs_ptr = BCs.mutable_data(); + GF_UINT* dim_ptr = dim.mutable_data(); + + // First priority flooding and calculating stack + compute_priority_flood(topo_ptr, BCs_ptr, dim_ptr, D8,step); +} + + +// Make wrap_funcname() function available as grid_funcname() to be used by +// by functions in the pytopotoolbox package + +PYBIND11_MODULE(_graphflood, m) { + m.def("graphflood_run_full", &wrap_graphflood_full); + m.def("graphflood_sfgraph", &wrap_compute_sfgraph); + m.def("compute_priority_flood_plus_topological_ordering", &wrap_compute_priority_flood_plus_stack); + m.def("compute_priority_flood", &wrap_compute_priority_flood); + m.def("compute_drainage_area_single_flow", &wrap_compute_drainage_area_single_flow); + + +} diff --git a/src/lib/grid.cpp b/src/lib/grid.cpp old mode 100644 new mode 100755 diff --git a/src/topotoolbox/__init__.py b/src/topotoolbox/__init__.py old mode 100644 new mode 100755 index 29a3f70..e4e4531 --- a/src/topotoolbox/__init__.py +++ b/src/topotoolbox/__init__.py @@ -1,3 +1,4 @@ from .grid_object import GridObject # noqa from .flow_object import FlowObject # noqa from .utils import * # noqa +from .graphflood import * # noqa diff --git a/src/topotoolbox/graphflood.py b/src/topotoolbox/graphflood.py new file mode 100755 index 0000000..76bf330 --- /dev/null +++ b/src/topotoolbox/graphflood.py @@ -0,0 +1,135 @@ +""" +Basic interface to libtopotoolbox implementation of graphflood + +""" + +from .grid_object import GridObject +import numpy as np +from ._graphflood import ( # type: ignore + graphflood_run_full, + graphflood_sfgraph, + compute_priority_flood_plus_topological_ordering, + compute_priority_flood, + compute_drainage_area_single_flow + ) +from copy import deepcopy + +# exposing function as string dictionary +funcdict = { + "run_full": graphflood_run_full, + "sfgraph": graphflood_sfgraph, + "priority_flood_TO": compute_priority_flood_plus_topological_ordering, + "priority_flood": compute_priority_flood, + "drainage_area_single_flow": compute_drainage_area_single_flow, +} + +__all__ = ["run_graphflood"] + + +def run_graphflood( + grid: GridObject, + initial_hw=None, + BCs=None, + dt=1e-3, + P=10 * 1e-3 / 3600, + manning=0.033, + SFD=False, + D8=True, + N_iterations=100, +): + + # Preparing the arguments + ny = grid.rows + nx = grid.columns + dx = grid.cellsize + dim = np.array([ny, nx], dtype=np.uint64) + + # Order C for vectorised topography + Z = grid.z.ravel(order="C") + + # Ingesting the flow depth + if initial_hw is None: + hw = np.zeros_like(Z) + else: + if initial_hw.shape != grid.z.shape: + raise RuntimeError( + """Feeding the model with initial flow depth + requires a 2D numpy array or a GridObject of + the same dimension of the topographic grid""" + ) + + if isinstance(grid, GridObject): + hw = initial_hw.z.ravel(order="C") + else: + hw = initial_hw.ravel(order="C") + + # Ingesting boundary condition + if BCs is None: + tBCs = np.ones((grid.rows, grid.columns), dtype=np.uint8) + tBCs[[0, -1], :] = 3 + tBCs[:, [0, -1]] = 3 + tBCs = tBCs.ravel(order="C") + else: + if BCs.shape != grid.shape: + raise RuntimeError( + """Feeding the model with boundary conditions requires + a 2D numpy array or a GridObject of the same dimension + of the topographic grid""" + ) + + if isinstance(BCs, GridObject): + tBCs = BCs.z.ravel(order="C").astype(np.uint8) + else: + tBCs = BCs.ravel(order="C").astype(np.uint8) + + # Ingesting Precipitations + if isinstance(P, np.ndarray): + if P.shape != grid.shape: + raise RuntimeError( + """Feeding the model with precipitations requires a + 2D numpy array or a GridObject of the same + dimension of the topographic grid""" + ) + Precipitations = P.ravel(order="C") + elif isinstance(P, GridObject): + if P.shape != grid.shape: + raise RuntimeError( + """Feeding the model with precipitations requires a + 2D numpy array or a GridObject of the same dimension + of the topographic grid""" + ) + Precipitations = P.z.ravel(order="C") + else: + # in case precipitation is a scalar + Precipitations = np.full_like(Z, P) + + # Ingesting manning + if isinstance(manning, np.ndarray): + if manning.shape != grid.shape: + raise RuntimeError( + """Feeding the model with precipitations requires a + 2D numpy array or a GridObject of the same dimension + of the topographic grid""" + ) + manning = manning.ravel(order="C") + elif isinstance(manning, GridObject): + if manning.shape != grid.shape: + raise RuntimeError( + """Feeding the model with precipitations requires a + 2D numpy array or a GridObject of the same dimension + of the topographic grid""" + ) + manning = manning.z.ravel(order="C") + else: + # in case precipitation is a scalar + manning = np.full_like(Z, manning) + + graphflood_run_full( + Z, hw, tBCs, Precipitations, manning, + dim, dt, dx, SFD, D8, N_iterations, 1e-3 + ) + res = deepcopy(grid) + + res.z = hw.reshape(grid.shape) + + return res diff --git a/src/topotoolbox/grid_object.py b/src/topotoolbox/grid_object.py old mode 100644 new mode 100755 diff --git a/src/topotoolbox/utils.py b/src/topotoolbox/utils.py old mode 100644 new mode 100755 diff --git a/tests/README.md b/tests/README.md old mode 100644 new mode 100755 diff --git a/tests/__init__.py b/tests/__init__.py old mode 100644 new mode 100755 diff --git a/tests/test_grid_object.py b/tests/test_grid_object.py old mode 100644 new mode 100755 diff --git a/tests/test_utils.py b/tests/test_utils.py old mode 100644 new mode 100755