Flow router/depression finder incorrectly directs flow in landlab version 2.1.1
See original GitHub issueI’ve become stuck on an issue with the flow router and depression finder. I am attempting to write a simple model using linear diffusion and detachment-limited fluvial erosion on an arbitrary topography loaded from a png. However, something about this model seems to be confusing the flow router such that lakes are not handled properly and large chunks of the drainage network flash on and off (see animation_island_r320.mp4). Although I’m using topography and erosion coefficients that probably aren’t realistic, the flow router shouldn’t be impacted. It should only care about the current topography when run_one_step is called, correct?
A minimum working example with only basic plotting code is shown below. However, the attached mwe_routing_issue.py runs the same code with more useful plotting. I recommend using that file. The following description is based on those plots.
The initial topography (top left subplot)) is a ring of high topography with a small gap surrounding low topography in the interior. The router correctly identifies the lake in the interior and correctly directs the drainage network (center left and bottom left) through the lake area. The first step shifts things a bit, but overall remains similar. The only unusual thing to note is that the node at (7, 15) increases slightly despite being part of the main flow route because of diffusion.
Step 2 sees the lake shrink significantly (bottom right) and does not reflect the current topography (top center). The drainage network is cut off (center) and flow becomes trapped in one of the lake nodes (bottom center). Drainage area upstream of the lake are not accumulated and consequently the fluvial component cannot erode anywhere in the interior. Step 3 continues the trend.
Step 4 through 38 are mostly the same. Backward erosion of the original channel in the ring gap slowly reduces the elevation of what should be the outlet node.
Between steps 39 and 41, the backward erosion recaptures much of the lower left corner of the interior. The proper drainage network is restored on step 42 when the lake is eliminated.
The issue still occurs with different ordering for calling each component’s run_one_step and occurs for both D4 or D8 routing. The timing is different though and you would have to modify the script to see other scenarios.
I found this issue using landlab version 2.1.1 acquired via conda. However, when I run the same script in version 2.0.0-dev+36.g39fee962, this issue does not occur. I’m not sure about other landlab versions.
Here is the model code for minimum working example (with only one basic plot):
import numpy as np
import matplotlib.pyplot as plt
from landlab import RasterModelGrid
import landlab.components as lab_components
print("Initializing Model")
## Model parameters
n_steps = 42
dt = 100 # years
node_spacing = 100 # m
## Make initial topography
input_topo = np.array([
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 0],
[0, 35, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 35, 0],
[0, 35, 140, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 140, 35, 0],
[0, 35, 140, 255, 211, 212, 211, 211, 212, 211, 212, 212, 211, 211, 211, 212, 255, 140, 35, 0],
[0, 35, 140, 255, 211, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 212, 255, 140, 35, 0],
[0, 35, 140, 255, 211, 124, 36, 36, 36, 36, 36, 36, 36, 36, 124, 212, 255, 140, 35, 0],
[0, 35, 140, 255, 211, 124, 36, 36, 36, 36, 36, 36, 36, 36, 124, 211, 255, 140, 35, 0],
[0, 35, 140, 255, 211, 124, 36, 36, 36, 36, 36, 36, 36, 36, 124, 211, 255, 140, 35, 0],
[0, 35, 140, 255, 211, 124, 36, 36, 36, 36, 36, 36, 36, 36, 124, 211, 255, 140, 35, 0],
[0, 35, 140, 255, 211, 124, 36, 36, 36, 36, 36, 36, 36, 36, 124, 211, 255, 140, 35, 0],
[0, 35, 140, 255, 212, 124, 36, 36, 36, 36, 36, 36, 36, 36, 124, 211, 255, 140, 35, 0],
[0, 35, 140, 255, 211, 124, 36, 36, 36, 36, 36, 36, 36, 36, 124, 211, 255, 140, 35, 0],
[0, 35, 140, 255, 211, 124, 36, 36, 36, 36, 36, 36, 36, 36, 124, 211, 255, 140, 35, 0],
[0, 35, 140, 255, 211, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 212, 255, 140, 35, 0],
[0, 35, 140, 255, 211, 211, 211, 124, 212, 211, 211, 211, 212, 211, 211, 212, 255, 140, 35, 0],
[0, 35, 140, 255, 255, 255, 255, 124, 255, 255, 255, 255, 255, 255, 255, 255, 255, 140, 35, 0],
[0, 35, 140, 140, 140, 140, 140, 124, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 35, 0],
[0, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
])
np_2D_init_topo = np.flipud(input_topo.astype(np.float))
map_shape = np_2D_init_topo.shape
np_1D_init_topo = np_2D_init_topo.ravel()
## Set up Landlab
# Set up landlab grid
print(f"Setting up Landlab grid")
mg = RasterModelGrid(map_shape, node_spacing)
# Add topography and slope
mg.add_field('node', 'topographic__elevation',
np_1D_init_topo, units='m')
mg.add_field('node', 'topographic__init_elevation',
np_1D_init_topo.copy(), units='m')
g = mg.calc_grad_at_link('topographic__elevation')
mg.add_field('link', 'topographic__slope', g)
# Set up boundaries
mg.set_closed_boundaries_at_grid_edges(False, False, False, False)
# Set up flow router component and run it once
flow_router = lab_components.FlowAccumulator(mg,
flow_director='FlowDirectorD8',
depression_finder=lab_components.DepressionFinderAndRouter,
# Or
#flow_director='FlowDirectorSteepest',
#depression_finder=lab_components.DepressionFinderAndRouter,
#routing='D4',
)
flow_router.run_one_step()
mg['node']['init_drainage_area'] = mg['node']['drainage_area'].copy()
#mg.add_field('node', 'init_flow_direct', _get_flow_dir(mg))
# Set up fluvial component
fluvial = lab_components.FastscapeEroder(mg)
# Set up diffusion component
diffusion = lab_components.LinearDiffuser(mg)
print(f"Finished setting up")
print()
## Run model
print()
print(f"Running model...")
for step in range(1, n_steps + 1):
diffusion.run_one_step(dt)
flow_router.run_one_step()
fluvial.run_one_step(dt)
if n_steps >= 10 and step%(n_steps//10) == 0 and step != n_steps:
print(f"{step/n_steps:2.0%} complete ({step}/{n_steps})")
elif step == n_steps:
# Get the final drainage network
flow_router.run_one_step()
print(f"100% complete ({n_steps}/{n_steps})")
print()
# Plotting code goes here
mg_da = mg['node']['drainage_area']
np_da = mg.node_vector_to_raster(mg_da, flip_vertically=True)
contrib_nodes = np_da / (mg.dx * mg.dy)
plt.figure()
ax = plt.gca()
ax.imshow(np.log10(contrib_nodes + 1))
ax.set_title("log10(contributing_nodes + 1)")
plt.show()
Issue Analytics
- State:
- Created 3 years ago
- Comments:15 (9 by maintainers)
Top GitHub Comments
This is associated with the refactoring I did in Cython between 2.06 and 2.07: https://github.com/landlab/landlab/compare/v2.0.0b6...v2.0.0b7
Taking a look to see if there’s a quick fix.
I think I might have it. I’m struggling to pick apart the every detail of the debug process, so you should check this, but try running this branch: https://github.com/landlab/landlab/tree/SiccarPoint/fix_1219. (Because I’m messing with the cfuncs, make sure to rerun
python setup.py develop
first).I think the problem was in declaring the type a little lower down wrongly, so we actively compared a python float with a c double within an array. My understanding of Cython said this should have been OK (these types are equivalent, according to the docs??), but realistically this was the only place the problem could have been. By switching in the ctype, I seem to have got an answer that floods the necessary nodes. Do you agree, as you know your debug code better than I do?