Ejemplo n.º 1
0
def test_mask_is_stable():
    mg = RasterModelGrid((10, 10))
    mg.add_zeros("node", "topographic__elevation")
    np.random.seed(3542)
    noise = np.random.rand(mg.size("node"))
    mg.at_node["topographic__elevation"] += noise
    fr = FlowAccumulator(mg, flow_director="D8")
    fsc = FastscapeEroder(mg, K_sp=0.01, m_sp=0.5, n_sp=1)
    for x in range(2):
        fr.run_one_step()
        fsc.run_one_step(dt=10.0)
        mg.at_node["topographic__elevation"][mg.core_nodes] += 0.01

    mask = np.zeros(len(mg.at_node["topographic__elevation"]), dtype=np.uint8)
    mask[np.where(mg.at_node["drainage_area"] > 0)] = 1

    mask0 = mask.copy()

    dd = DrainageDensity(mg, channel__mask=mask)
    mask1 = mask.copy()

    dd.calc_drainage_density()
    mask2 = mask.copy()

    assert_array_equal(mask0, mask1)
    assert_array_equal(mask0[mg.core_nodes], mask2[mg.core_nodes])
def test_sp_discharges_new():
    input_str = os.path.join(_THIS_DIR, 'test_sp_params_discharge_new.txt')
    inputs = ModelParameterDictionary(input_str, auto_type=True)
    nrows = 5
    ncols = 5
    dx = inputs.read_float('dx')
    dt = inputs.read_float('dt')

    mg = RasterModelGrid(nrows, ncols, dx)
    mg.add_zeros('topographic__elevation', at='node')
    z = np.array([5., 5., 0., 5., 5.,
                  5., 2., 1., 2., 5.,
                  5., 3., 2., 3., 5.,
                  5., 4., 4., 4., 5.,
                  5., 5., 5., 5., 5.])
    mg['node']['topographic__elevation'] = z

    fr = FlowAccumulator(mg, flow_director='D8')
    sp = StreamPowerEroder(mg, **inputs)

    # perform the loop (once!)
    for i in range(1):
        fr.run_one_step()
        sp.run_one_step(dt)

    z_tg = np.array([5.        ,  5.        ,  0.        ,  5.        ,
                     5.        ,  5.        ,  1.47759225,  0.43050087,
                     1.47759225,  5.        ,  5.        ,  2.32883687,
                     1.21525044,  2.32883687,  5.        ,  5.        ,
                     3.27261262,  3.07175015,  3.27261262,  5.        ,
                     5.        ,  5.        ,  5.        ,  5.        ,
                     5.        ])

    assert_array_almost_equal(mg.at_node['topographic__elevation'], z_tg)
Ejemplo n.º 3
0
def test_get_watershed_outlet():
    grid = RasterModelGrid((7, 7), 1)

    z = np.array([
        -9999., -9999., -9999., -9999., -9999., -9999., -9999.,
        -9999.,    26.,     0.,    30.,    32.,    34., -9999.,
        -9999.,    28.,     1.,    25.,    28.,    32., -9999.,
        -9999.,    30.,     3.,     3.,    11.,    34., -9999.,
        -9999.,    32.,    11.,    25.,    18.,    38., -9999.,
        -9999.,    34.,    32.,    34.,    36.,    40., -9999.,
        -9999., -9999., -9999., -9999., -9999., -9999., -9999.])

    grid.at_node['topographic__elevation'] = z

    imposed_outlet = 2

    grid.set_watershed_boundary_condition_outlet_id(imposed_outlet, z,
                                                    nodata_value=-9999.)

    fr = FlowAccumulator(grid, flow_director='D8')
    fr.run_one_step()

    test_node = 32

    determined_outlet = get_watershed_outlet(grid, test_node)
    np.testing.assert_equal(determined_outlet, imposed_outlet)

    # Create a pit.
    pit_node = 38
    grid.at_node['topographic__elevation'][pit_node] -= 32
    fr.run_one_step()

    pit_outlet = get_watershed_outlet(grid, test_node)
    np.testing.assert_equal(pit_outlet, pit_node)
Ejemplo n.º 4
0
def test_get_watershed_masks_with_area_threshold():
    rmg = RasterModelGrid((7, 7), 200)

    z = np.array([
            -9999., -9999., -9999., -9999., -9999., -9999., -9999.,
            -9999.,    26.,     0.,    26.,    30.,    34., -9999.,
            -9999.,    28.,     1.,    28.,     5.,    32., -9999.,
            -9999.,    30.,     3.,    30.,    10.,    34., -9999.,
            -9999.,    32.,    11.,    32.,    15.,    38., -9999.,
            -9999.,    34.,    32.,    34.,    36.,    40., -9999.,
            -9999., -9999., -9999., -9999., -9999., -9999., -9999.])

    rmg.at_node['topographic__elevation'] = z

    rmg.set_closed_boundaries_at_grid_edges(True, True, True, False)

    # Route flow.
    fr = FlowAccumulator(rmg, flow_director='D8')
    fr.run_one_step()

    # Get the masks of watersheds greater than or equal to 80,000
    # square-meters.
    critical_area = 80000
    mask = get_watershed_masks_with_area_threshold(rmg, critical_area)

    # Assert that mask null nodes have a drainage area below critical area.
    null_nodes = np.where(mask == -1)[0]
    A = rmg.at_node['drainage_area'][null_nodes]
    below_critical_area_nodes = A < critical_area
    trues = np.ones(len(A), dtype=bool)

    np.testing.assert_equal(below_critical_area_nodes, trues)
Ejemplo n.º 5
0
def test_can_run_with_hex():
    """Test that model can run with hex model grid."""

    # Set up a 5x5 grid with open boundaries and low initial elevations.
    mg = HexModelGrid(7, 7)
    z = mg.add_zeros("node", "topographic__elevation")
    z[:] = 0.01 * mg.x_of_node

    # Create a D8 flow handler
    fa = FlowAccumulator(mg, flow_director="FlowDirectorSteepest")

    # Parameter values for test 1
    U = 0.001
    dt = 10.0

    # Create the Space component...
    sp = Space(
        mg,
        K_sed=0.00001,
        K_br=0.00000000001,
        F_f=0.5,
        phi=0.1,
        H_star=1.,
        v_s=0.001,
        m_sp=0.5,
        n_sp=1.0,
        sp_crit_sed=0,
        sp_crit_br=0,
    )

    # ... and run it to steady state.
    for i in range(2000):
        fa.run_one_step()
        sp.run_one_step(dt=dt)
        z[mg.core_nodes] += U * dt
Ejemplo n.º 6
0
def test_assertion_error():
    """Test that the correct assertion error will be raised."""

    mg = RasterModelGrid(10, 10)
    z = mg.add_zeros('topographic__elevation', at='node')
    z += 200 + mg.x_of_node + mg.y_of_node + np.random.randn(mg.size('node'))

    mg.set_closed_boundaries_at_grid_edges(bottom_is_closed=True, left_is_closed=True, right_is_closed=True, top_is_closed=True)
    mg.set_watershed_boundary_condition_outlet_id(0, z, -9999)
    fa = FlowAccumulator(mg, flow_director='D8', depression_finder=DepressionFinderAndRouter)
    sp = FastscapeEroder(mg, K_sp=.0001, m_sp=.5, n_sp=1)
    ld = LinearDiffuser(mg, linear_diffusivity=0.0001)

    dt = 100
    for i in range(200):
        fa.run_one_step()
        flooded = np.where(fa.depression_finder.flood_status==3)[0]
        sp.run_one_step(dt=dt,  flooded_nodes=flooded)
        ld.run_one_step(dt=dt)
        mg.at_node['topographic__elevation'][0] -= 0.001 # Uplift


    assert_raises(AssertionError,
                  analyze_channel_network_and_plot,
                  mg,
                  threshold = 100,
                  starting_nodes = [0],
                  number_of_channels=2)
Ejemplo n.º 7
0
def test_sed_dep():
    input_file = os.path.join(_THIS_DIR, "sed_dep_params.txt")
    inputs = ModelParameterDictionary(input_file, auto_type=True)
    nrows = inputs.read_int("nrows")
    ncols = inputs.read_int("ncols")
    dx = inputs.read_float("dx")
    uplift_rate = inputs.read_float("uplift_rate")

    runtime = inputs.read_float("total_time")
    dt = inputs.read_float("dt")

    nt = int(runtime // dt)
    uplift_per_step = uplift_rate * dt

    mg = RasterModelGrid((nrows, ncols), xy_spacing=(dx, dx))

    mg.add_zeros("topographic__elevation", at="node")
    z = np.loadtxt(os.path.join(_THIS_DIR, "seddepinit.txt"))
    mg["node"]["topographic__elevation"] = z

    mg.set_closed_boundaries_at_grid_edges(True, False, True, False)

    fr = FlowAccumulator(mg, flow_director="D8")
    sde = SedDepEroder(mg, **inputs)

    for i in range(nt):
        mg.at_node["topographic__elevation"][mg.core_nodes] += uplift_per_step
        mg = fr.run_one_step()
        mg, _ = sde.erode(dt)

    z_tg = np.loadtxt(os.path.join(_THIS_DIR, "seddepz_tg.txt"))

    assert_array_almost_equal(
        mg.at_node["topographic__elevation"][mg.core_nodes], z_tg[mg.core_nodes]
    )
Ejemplo n.º 8
0
def test_stupid_shaped_hole(sink_grid4):
    """Tests inclined fill into a surface with a deliberately awkward shape."""
    fr = FlowAccumulator(sink_grid4, flow_director="D8")
    hf = SinkFiller(sink_grid4, apply_slope=True)
    hf.fill_pits()
    hole1 = np.array(
        [
            4.00007692,
            4.00015385,
            4.00023077,
            4.00030769,
            4.00038462,
            4.00046154,
            4.00053846,
            4.00061538,
            4.00069231,
            4.00076923,
            4.00084615,
        ]
    )
    hole2 = np.array([7.4, 7.2, 7.6])

    assert_array_almost_equal(
        sink_grid4.at_node["topographic__elevation"][sink_grid4.lake1], hole1
    )
    assert_array_almost_equal(
        sink_grid4.at_node["topographic__elevation"][sink_grid4.lake2], hole2
    )
    fr.run_one_step()
    assert sink_grid4.at_node["flow__sink_flag"][sink_grid4.core_nodes].sum() == 0
Ejemplo n.º 9
0
def test_MFD_S_slope_w_diag():
    mg = RasterModelGrid((10, 10))
    mg.add_field("topographic__elevation", mg.node_y, at="node")
    fa = FlowAccumulator(mg, flow_director="FlowDirectorMFD", diagonals=True)
    fa.run_one_step()

    # this one should part to south west, part to south, and part to southeast
    sw_diags = mg.diagonal_adjacent_nodes_at_node[:, 2]
    se_diags = mg.diagonal_adjacent_nodes_at_node[:, 3]
    s_links = mg.adjacent_nodes_at_node[:, 3]
    node_ids = np.arange(mg.number_of_nodes)
    true_recievers = -1 * np.ones(fa.flow_director.receivers.shape)
    true_recievers[mg.core_nodes, 3] = s_links[mg.core_nodes]
    true_recievers[mg.core_nodes, 6] = sw_diags[mg.core_nodes]
    true_recievers[mg.core_nodes, 7] = se_diags[mg.core_nodes]
    true_recievers[mg.boundary_nodes, 0] = node_ids[mg.boundary_nodes]

    true_proportions = np.zeros(fa.flow_director.proportions.shape)
    true_proportions[mg.boundary_nodes, 0] = 1

    total_sum_of_slopes = 1. + 2. * (1. / 2. ** 0.5)

    true_proportions[mg.core_nodes, 3] = 1.0 / total_sum_of_slopes
    true_proportions[mg.core_nodes, 6] = (1. / 2. ** 0.5) / total_sum_of_slopes
    true_proportions[mg.core_nodes, 7] = (1. / 2. ** 0.5) / total_sum_of_slopes

    assert_array_equal(true_recievers, fa.flow_director.receivers)
    assert_array_almost_equal(true_proportions, fa.flow_director.proportions)
Ejemplo n.º 10
0
def test_D4_routing(sink_grid5):
    """
    Tests inclined fill into a surface with a deliberately awkward shape.
    This is testing D4 routing.
    """
    fr = FlowAccumulator(sink_grid5, flow_director="D4")
    hf = SinkFiller(sink_grid5, routing="D4", apply_slope=True)
    hf.fill_pits()
    hole1 = np.array(
        [
            4.00016667,
            4.00033333,
            4.0005,
            4.00008333,
            4.00025,
            4.00041667,
            4.000833,
            4.00066667,
            4.00058333,
            4.00075,
            4.334,
        ]
    )
    hole2 = np.array([7.6, 7.2, 7.4])

    assert_array_almost_equal(
        sink_grid5.at_node["topographic__elevation"][sink_grid5.lake1], hole1
    )
    assert_array_almost_equal(
        sink_grid5.at_node["topographic__elevation"][sink_grid5.lake2], hole2
    )
    fr.run_one_step()
    assert sink_grid5.at_node["flow__sink_flag"][sink_grid5.core_nodes].sum() == 0
def test_track_source():
    """Unit tests for track_source().
    """
    grid = RasterModelGrid((5, 5), spacing=(1., 1.))
    grid.at_node['topographic__elevation'] = np.array([5., 5., 5., 5., 5.,
                                                       5., 4., 5., 1., 5.,
                                                       0., 3., 5., 3., 0.,
                                                       5., 4., 5., 2., 5.,
                                                       5., 5., 5., 5., 5.])
    grid.status_at_node[10] = 0
    grid.status_at_node[14] = 0
    fr = FlowAccumulator(grid, flow_director='D8')
    fr.run_one_step()
    r = grid.at_node['flow__receiver_node']
    assert r[6] == 10
    assert r[7] == 8
    assert r[18] == 14
    hsd_ids = np.empty(grid.number_of_nodes, dtype=int)
    hsd_ids[:] = 1
    hsd_ids[2:5] = 0
    hsd_ids[7:10] = 0
    (hsd_upstr, flow_accum) = track_source(grid, hsd_ids)
    assert hsd_upstr[8] == [1, 0, 0]
    assert hsd_upstr[14] == [1, 1, 1, 1, 0, 0, 1]
    assert flow_accum[14] == 7
Ejemplo n.º 12
0
def test_with_thresh():
    K = 0.001
    U = 0.01
    m = 0.5
    n = 1.0
    threshold = 1.0
    dt = 1000

    mg = RasterModelGrid(30, 3, xy_spacing=100.)
    mg.set_closed_boundaries_at_grid_edges(True, False, True, False)
    z = mg.zeros(at="node")
    mg["node"]["topographic__elevation"] = z + np.random.rand(len(z)) / 1000.

    fa = FlowAccumulator(mg)
    sp = Spst(mg, K_sp=K, threshold_sp=threshold)
    for i in range(100):
        fa.run_one_step()
        sp.run_one_step(dt)
        mg["node"]["topographic__elevation"][mg.core_nodes] += U * dt

    actual_slopes = mg.at_node["topographic__steepest_slope"][mg.core_nodes[1:-1]]
    actual_areas = mg.at_node["drainage_area"][mg.core_nodes[1:-1]]

    predicted_slopes_upper = ((U + threshold) / (K * (actual_areas ** m))) ** (1. / n)
    predicted_slopes_lower = ((U + 0.0) / (K * (actual_areas ** m))) ** (1. / n)

    # assert actual and predicted slopes are in the correct range for the slopes.
    assert np.all(actual_slopes > predicted_slopes_lower)
    assert np.all(actual_slopes < predicted_slopes_upper)
Ejemplo n.º 13
0
def test_sed_dep_new():
    """
    This tests only the power_law version of the SDE.
    It uses a landscape run to 5000 100y iterations, then having experienced
    a 20-fold uplift acceleration for a further 30000 y. It tests the outcome
    of the next 1000 y of erosion.
    """
    mg = RasterModelGrid((25, 50), 200.)
    for edge in (mg.nodes_at_left_edge, mg.nodes_at_top_edge,
                 mg.nodes_at_right_edge):
        mg.status_at_node[edge] = CLOSED_BOUNDARY

    z = mg.add_zeros('node', 'topographic__elevation')

    fr = FlowAccumulator(mg, flow_director='D8')
    sde = SedDepEroder(mg, K_sp=1.e-4, sed_dependency_type='almost_parabolic',
                       Qc='power_law', K_t=1.e-4)

    initconds = os.path.join(os.path.dirname(__file__),
                             'perturbedcondst300.txt')
    finalconds = os.path.join(os.path.dirname(__file__),
                              'tenmorestepsfrom300.txt')
    z[:] = np.loadtxt(initconds)

    dt = 100.
    up = 0.05

    for i in range(10):
        fr.run_one_step()
        sde.run_one_step(dt)
        z[mg.core_nodes] += 20.*up

    assert_array_almost_equal(z, np.loadtxt(finalconds))
Ejemplo n.º 14
0
def test_no_thresh():
    K = 0.001
    U = 0.01
    m = 0.5
    n = 1.0
    threshold = 0.0
    dt = 1000

    mg = RasterModelGrid(30, 3, xy_spacing=100.)
    mg.set_closed_boundaries_at_grid_edges(True, False, True, False)
    z = mg.zeros(at="node")
    mg["node"]["topographic__elevation"] = z + np.random.rand(len(z)) / 1000.

    fa = FlowAccumulator(mg)
    sp = Spst(mg, K_sp=K, threshold_sp=threshold)
    for i in range(100):
        fa.run_one_step()
        sp.run_one_step(dt)
        mg["node"]["topographic__elevation"][mg.core_nodes] += U * dt

    actual_slopes = mg.at_node["topographic__steepest_slope"][mg.core_nodes[1:-1]]
    actual_areas = mg.at_node["drainage_area"][mg.core_nodes[1:-1]]

    predicted_slopes = (U / (K * (actual_areas ** m))) ** (1. / n)

    assert_array_almost_equal(actual_slopes, predicted_slopes)
Ejemplo n.º 15
0
def test_get_watershed_nodes():
    grid = RasterModelGrid((7, 7), 1)

    z = np.array([
        -9999., -9999., -9999., -9999., -9999., -9999., -9999.,
        -9999.,    26.,     0.,    30.,    32.,    34., -9999.,
        -9999.,    28.,     1.,    25.,    28.,    32., -9999.,
        -9999.,    30.,     3.,     3.,    11.,    34., -9999.,
        -9999.,    32.,    11.,    25.,    18.,    38., -9999.,
        -9999.,    34.,    32.,    34.,    36.,    40., -9999.,
        -9999., -9999., -9999., -9999., -9999., -9999., -9999.])

    grid.at_node['topographic__elevation'] = z

    outlet_id = 2

    grid.set_watershed_boundary_condition_outlet_id(outlet_id, z,
                                                    nodata_value=-9999.)

    fr = FlowAccumulator(grid, flow_director='D8')
    fr.run_one_step()

    ws_nodes = get_watershed_nodes(grid, outlet_id)

    # Given the watershed boundary conditions, the number of watershed nodes
    # should be equal to the number of core nodes plus 1 for the outlet node.
    np.testing.assert_equal(len(ws_nodes), grid.number_of_core_nodes + 1)
Ejemplo n.º 16
0
def test_no_reroute():
    mg = RasterModelGrid((5, 5), xy_spacing=2.)
    z = mg.add_zeros("node", "topographic__elevation", dtype=float)
    z[1] = -1.
    z[6] = -2.
    z[19] = -2.
    z[18] = -1.
    z[17] = -3.
    fd = FlowDirectorSteepest(mg)
    fa = FlowAccumulator(mg)
    lmb = LakeMapperBarnes(
        mg,
        method="Steepest",
        fill_flat=True,
        redirect_flow_steepest_descent=True,
        track_lakes=True,
    )

    lake_dict = {1: deque([6]), 18: deque([17])}
    fd.run_one_step()  # fill the director fields
    fa.run_one_step()  # get a drainage_area
    orig_surf = lmb._track_original_surface()
    lmb._redirect_flowdirs(orig_surf, lake_dict)

    assert mg.at_node["flow__receiver_node"][6] == 1
    assert mg.at_node["flow__receiver_node"][17] == 18
    assert mg.at_node["flow__receiver_node"][18] == 19
Ejemplo n.º 17
0
def test_filler_inclined2(sink_grid3):
    """
    Tests an inclined fill into an inclined surface, with two holes.
    """
    fr = FlowAccumulator(sink_grid3, flow_director="D8")
    hf = SinkFiller(sink_grid3, apply_slope=True)

    hf.fill_pits()
    hole1 = np.array(
        [
            4.00009091,
            4.00018182,
            4.00027273,
            4.00036364,
            4.00045455,
            4.00054545,
            4.00063636,
            4.00072727,
            4.00081818,
        ]
    )
    hole2 = np.array([7.16666667, 7.33333333, 7.5, 7.66666667])
    assert_array_almost_equal(
        sink_grid3.at_node["topographic__elevation"][sink_grid3.lake1], hole1
    )
    assert_array_almost_equal(
        sink_grid3.at_node["topographic__elevation"][sink_grid3.lake2], hole2
    )
    fr.run_one_step()
    assert sink_grid3.at_node["flow__sink_flag"][sink_grid3.core_nodes].sum() == 0
Ejemplo n.º 18
0
def test_route_to_multiple_error_raised_init():
    mg = RasterModelGrid((10, 10))
    z = mg.add_zeros("node", "topographic__elevation")
    z += mg.x_of_node + mg.y_of_node
    fa = FlowAccumulator(mg, flow_director="MFD")
    fa.run_one_step()
    with pytest.raises(NotImplementedError):
        SinkFillerBarnes(mg)
Ejemplo n.º 19
0
def test_flow__distance_raster_MFD_diagonals_true():
    """Test of flow__distance utility with a raster grid and MFD."""

    # instantiate a model grid

    mg = RasterModelGrid((5, 4), xy_spacing=(1, 1))

    # instantiate an elevation array

    z = np.array(
        [[0, 0, 0, 0], [0, 21, 10, 0], [0, 31, 20, 0], [0, 32, 30, 0], [0, 0, 0, 0]],
        dtype="float64",
    )

    # add the elevation field to the grid

    mg.add_field("node", "topographic__elevation", z)

    # instantiate the expected flow__distance array
    # considering flow directions calculated with MFD algorithm

    flow__distance_expected = np.array(
        [
            [0, 0, 0, 0],
            [0, 1, 0, 0],
            [0, math.sqrt(2), 1, 0],
            [0, 1 + math.sqrt(2), 2, 0],
            [0, 0, 0, 0],
        ],
        dtype="float64",
    )
    flow__distance_expected = np.reshape(
        flow__distance_expected, mg.number_of_node_rows * mg.number_of_node_columns
    )

    # setting boundary conditions

    mg.set_closed_boundaries_at_grid_edges(
        bottom_is_closed=True,
        left_is_closed=True,
        right_is_closed=True,
        top_is_closed=True,
    )

    # calculating flow directions with FlowAccumulator component

    fa = FlowAccumulator(
        mg, "topographic__elevation", flow_director="MFD", diagonals=True
    )
    fa.run_one_step()

    # calculating flow distance map

    flow__distance = calculate_flow__distance(mg, add_to_grid=True, noclobber=False)

    # test that the flow__distance utility works as expected

    assert_array_equal(flow__distance_expected, flow__distance)
Ejemplo n.º 20
0
def test_flow__distance_regular_grid_d8():
    """Test to demonstrate that flow__distance utility works as expected with
    regular grids"""

    # instantiate a model grid

    mg = RasterModelGrid((5, 4), spacing=(1, 1))

    # instantiate an elevation array

    z = np.array([[0, 0, 0, 0], [0, 21, 10, 0], [0, 31, 20, 0], [0, 32, 30, 0],
                  [0, 0, 0, 0]], dtype='float64')

    # add the elevation field to the grid

    mg.add_field('node', 'topographic__elevation', z)

    # instantiate the expected flow__distance array
    # considering flow directions calculated with D8 algorithm

    flow__distance_expected = np.array([[0, 0, 0, 0], [0, 1, 0, 0],
                                       [0, math.sqrt(2), 1, 0],
                                       [0, 1+math.sqrt(2), 2, 0], [0, 0, 0, 0]],
                                       dtype='float64')
    flow__distance_expected = np.reshape(flow__distance_expected,
                                        mg.number_of_node_rows *
                                        mg.number_of_node_columns)

    #setting boundary conditions

    mg.set_closed_boundaries_at_grid_edges(bottom_is_closed=True,
                                           left_is_closed=True,
                                           right_is_closed=True,
                                           top_is_closed=True)

    # calculating flow directions with FlowAccumulator component

    fr = FlowAccumulator(mg, flow_director='D8')
    fr.run_one_step()

    # calculating flow distance map

    flow__distance = calculate_flow__distance(mg, add_to_grid=True,
                                             noclobber=False)
    flow__distance = np.reshape(flow__distance, mg.number_of_node_rows *
                               mg.number_of_node_columns)

    # modifying the flow distance map because boundary and outlet nodes should
    # not have flow__distance value different from 0

    flow__distance[mg.boundary_nodes] = 0
    outlet_id = 6
    flow__distance[outlet_id] = 0


    # test that the flow distance utility works as expected

    assert_array_equal(flow__distance_expected, flow__distance)
Ejemplo n.º 21
0
def test_route_to_multiple_error_raised():
    mg = RasterModelGrid((10, 10))
    z = mg.add_zeros("node", "topographic__elevation")
    z += mg.x_of_node + mg.y_of_node
    fa = FlowAccumulator(mg, flow_director="MFD")
    fa.run_one_step()

    with pytest.raises(NotImplementedError):
        ChiFinder(mg, min_drainage_area=1., reference_concavity=1.)
Ejemplo n.º 22
0
def test_bad_reference_area():
    mg = RasterModelGrid((10, 10))
    z = mg.add_zeros("node", "topographic__elevation")
    z += mg.x_of_node + mg.y_of_node
    fa = FlowAccumulator(mg)
    fa.run_one_step()

    with pytest.raises(ValueError):
        ChiFinder(mg, min_drainage_area=1.0, reference_area=-1)
Ejemplo n.º 23
0
def test_route_to_multiple_error_raised():
    mg = RasterModelGrid((10, 10))
    z = mg.add_zeros('node', 'topographic__elevation')
    z += mg.x_of_node + mg.y_of_node
    fa = FlowAccumulator(mg, flow_director='MFD')
    fa.run_one_step()

    with pytest.raises(NotImplementedError):
        DepressionFinderAndRouter(mg)
Ejemplo n.º 24
0
def test_route_to_multiple_error_raised():
    mg = RasterModelGrid((10, 10))
    z = mg.add_zeros("node", "topographic__elevation")
    z += mg.x_of_node + mg.y_of_node
    fa = FlowAccumulator(mg, flow_director="MFD")
    fa.run_one_step()

    with pytest.raises(NotImplementedError):
        ErosionDeposition(mg, K=0.01, phi=0.0, v_s=0.001, m_sp=0.5, n_sp=1.0, sp_crit=0)
Ejemplo n.º 25
0
def test_route_to_multiple_error_raised():
    mg = RasterModelGrid((10, 10))
    z = mg.add_zeros("node", "topographic__elevation")
    z += mg.x_of_node + mg.y_of_node
    fa = FlowAccumulator(mg, flow_director="MFD")
    fa.run_one_step()

    with pytest.raises(NotImplementedError):
        TransportLengthHillslopeDiffuser(mg, erodibility=1.0, slope_crit=0.5)
Ejemplo n.º 26
0
def test_functions_with_Hex():
    mg = HexModelGrid(10, 10)
    z = mg.add_zeros("node", "topographic__elevation")
    z += mg.x_of_node + mg.y_of_node
    fa = FlowAccumulator(mg)
    fa.run_one_step()

    ch = ChiFinder(mg, min_drainage_area=1.0, reference_concavity=1.0)
    ch.calculate_chi()
Ejemplo n.º 27
0
def test_flow__distance_irregular_grid_d4():
    """Test to demonstrate that flow__distance utility works as expected with irregular grids"""

    # instantiate a model grid

    dx = 1.0
    hmg = HexModelGrid(5, 3, dx)

    # instantiate and add the elevation field

    hmg.add_field(
        "topographic__elevation", hmg.node_x + np.round(hmg.node_y), at="node"
    )

    # instantiate the expected flow__distance array

    flow__distance_expected = np.array(
        [
            0.0,
            0.0,
            0.0,
            0.0,
            0.0,
            dx,
            0.0,
            0.0,
            dx,
            dx,
            2.0 * dx,
            0.0,
            0.0,
            2.0 * dx,
            2.0 * dx,
            0.0,
            0.0,
            0.0,
            0.0,
        ]
    )

    # setting boundary conditions

    hmg.set_closed_nodes(hmg.boundary_nodes)

    # calculating flow directions with FlowAccumulator component: D4 algorithm

    fr = FlowAccumulator(hmg, flow_director="D4")
    fr.run_one_step()

    # calculating flow distance map

    flow__distance = calculate_flow__distance(hmg, add_to_grid=True, noclobber=False)

    # test that the flow__distance utility works as expected

    assert_almost_equal(flow__distance_expected, flow__distance, decimal=10)
Ejemplo n.º 28
0
def test_route_to_multiple_error_raised():
    mg = RasterModelGrid((10, 10))
    z = mg.add_zeros('node', 'topographic__elevation')
    z += mg.x_of_node + mg.y_of_node
    fa = FlowAccumulator(mg, flow_director='MFD')
    fa.run_one_step()

    with pytest.raises(NotImplementedError):
        Space(mg, K_sed=0.1, K_br=0.1, F_f=0.5, phi=0.1, H_star=1., v_s=0.001,
              m_sp=1.0, n_sp=0.5, sp_crit_sed=0, sp_crit_br=0)
Ejemplo n.º 29
0
def test_route_to_multiple_error_raised():
    mg = RasterModelGrid((10, 10))
    z = mg.add_zeros("node", "topographic__elevation")
    z += mg.x_of_node + mg.y_of_node
    fa = FlowAccumulator(mg, flow_director="MFD")
    fa.run_one_step()

    channel__mask = mg.zeros(at="node")

    with pytest.raises(NotImplementedError):
        DrainageDensity(mg, channel__mask=channel__mask)
Ejemplo n.º 30
0
def test_route_to_multiple_error_raised_run_StreamPowerEroder():
    mg = RasterModelGrid((10, 10))
    z = mg.add_zeros("node", "topographic__elevation")
    z += mg.x_of_node + mg.y_of_node
    sp = StreamPowerEroder(mg)

    fa = FlowAccumulator(mg, flow_director="MFD")
    fa.run_one_step()

    with pytest.raises(NotImplementedError):
        sp.run_one_step(10)
Ejemplo n.º 31
0
    def __init__(self, grid, runoff_rate=1.0, roughness=0.01,
                 changing_topo=False, depth_exp=1.5, weight=1.0, **kwds):
        """Initialize the KinwaveOverlandFlowModel.

        Parameters
        ----------
        grid : ModelGrid
            Landlab ModelGrid object
        runoff_rate : float, optional (defaults to 1 mm/hr)
            Precipitation rate, mm/hr
        roughnes : float, defaults to 0.01
            Manning roughness coefficient, s/m^1/3
        changing_topo : boolean, optional (defaults to False)
            Flag indicating whether topography changes between time steps
        depth_exp : float (defaults to 1.5)
            Exponent on water depth in velocity equation (3/2 for Darcy/Chezy,
            5/3 for Manning)
        weight : float (defaults to 1.0)
            Weighting on depth at new time step versus old time step (1 = all
            implicit; 0 = explicit)
        """

        # Store grid and parameters and do unit conversion
        self._grid = grid
        self.runoff_rate = runoff_rate / 3600000.0  # convert to m/s
        self.vel_coef = 1.0 / roughness  # do division now to save time
        self.changing_topo = changing_topo
        self.depth_exp = depth_exp
        self.weight = weight

        # Get elevation field
        try:
            self.elev = grid.at_node['topographic__elevation']
        except:
            raise

        # Create fields...
        #  Water depth
        if 'surface_water__depth' in grid.at_node:
            self.depth = grid.at_node['surface_water__depth']
        else:
            self.depth = grid.add_zeros('node', 'surface_water__depth')
        #   Slope
        if 'topographic__gradient' in grid.at_link:
            self.slope = grid.at_link['topographic__gradient']
        else:
            self.slope = grid.add_zeros('link', 'topographic__gradient')
        #  Velocity
#        if 'water__velocity' in grid.at_link:
#            self.vel = grid.at_link['water__velocity']
#        else:
#            self.vel = grid.add_zeros('link', 'water__velocity')
        #  Discharge
#        if 'surface_water__specific_discharge' in grid.at_link:
#            self.disch = grid.at_link['surface_water__specific_discharge']
#        else:
#            self.disch = grid.add_zeros('link',
#                                        'surface_water__specific_discharge')
        #  Inflow discharge at nodes
        if 'surface_water_inflow__discharge' in grid.at_node:
            self.disch_in = grid.at_node['surface_water_inflow__discharge']
        else:
            self.disch_in = grid.add_zeros('node',
                                           'surface_water_inflow__discharge')

        # This array holds, for each node, the sum of sqrt(slope) x face width
        # for each link/face.
        self.grad_width_sum = grid.zeros('node')

        # This array holds the prefactor in the algebraic equation that we
        # will find a solution for.
        self.alpha = grid.zeros('node')

        # Instantiate flow router
        self.flow_accum = FlowAccumulator(grid, 'topographic__elevation',
                                    flow_director='MFD',
                                    partition_method='square_root_of_slope')

        # Flag to let us know whether this is our first iteration
        self.first_iteration = True
Ejemplo n.º 32
0
def test_fastscape():
    input_str = os.path.join(_THIS_DIR, "drive_sp_params.txt")
    inputs = ModelParameterDictionary(input_str)
    nrows = inputs.read_int("nrows")
    ncols = inputs.read_int("ncols")
    dx = inputs.read_float("dx")
    dt = inputs.read_float("dt")
    time_to_run = inputs.read_float("run_time")
    uplift = inputs.read_float("uplift_rate")
    init_elev = inputs.read_float("init_elev")

    mg = RasterModelGrid(nrows, ncols, dx)
    mg.set_closed_boundaries_at_grid_edges(False, False, True, True)

    mg.add_zeros("topographic__elevation", at="node")
    z = mg.zeros(at="node") + init_elev
    numpy.random.seed(0)
    mg["node"]["topographic__elevation"] = z + numpy.random.rand(len(z)) / 1000.

    fr = FlowAccumulator(mg, flow_director="D8")
    fsp = Fsc(mg, input_str, method="D8")
    elapsed_time = 0.
    while elapsed_time < time_to_run:
        if elapsed_time + dt > time_to_run:
            dt = time_to_run - elapsed_time
        mg = fr.run_one_step()
        mg = fsp.erode(mg, dt=dt)
        mg.at_node["topographic__elevation"][mg.core_nodes] += uplift * dt
        elapsed_time += dt

    z_trg = numpy.array(
        [
            5.48813504e-04,
            7.15189366e-04,
            6.02763376e-04,
            5.44883183e-04,
            4.23654799e-04,
            6.45894113e-04,
            1.01830760e-02,
            9.58036770e-03,
            6.55865452e-03,
            3.83441519e-04,
            7.91725038e-04,
            1.00142749e-02,
            8.80798884e-03,
            5.78387585e-03,
            7.10360582e-05,
            8.71292997e-05,
            9.81911417e-03,
            9.52243406e-03,
            7.55093226e-03,
            8.70012148e-04,
            9.78618342e-04,
            1.00629755e-02,
            8.49253798e-03,
            5.33216680e-03,
            1.18274426e-04,
            6.39921021e-04,
            9.88956320e-03,
            9.47119567e-03,
            6.43790696e-03,
            4.14661940e-04,
            2.64555612e-04,
            1.00450743e-02,
            8.37262908e-03,
            5.21540904e-03,
            1.87898004e-05,
            6.17635497e-04,
            9.21286940e-03,
            9.34022513e-03,
            7.51114450e-03,
            6.81820299e-04,
            3.59507901e-04,
            6.19166921e-03,
            7.10456176e-03,
            6.62585507e-03,
            6.66766715e-04,
            6.70637870e-04,
            2.10382561e-04,
            1.28926298e-04,
            3.15428351e-04,
            3.63710771e-04,
        ]
    )

    assert_array_almost_equal(mg.at_node["topographic__elevation"], z_trg)
Ejemplo n.º 33
0
    def __init__(self,
                 input_file=None,
                 params=None,
                 BaselevelHandlerClass=None):
        """Initialize the BasicDd."""

        # Call ErosionModel's init
        super(BasicDd,
              self).__init__(input_file=input_file,
                             params=params,
                             BaselevelHandlerClass=BaselevelHandlerClass)

        # Get Parameters and convert units if necessary:
        K_sp = self.get_parameter_from_exponent('K_sp', raise_error=False)
        K_ss = self.get_parameter_from_exponent('K_ss', raise_error=False)
        linear_diffusivity = (
            self._length_factor**2.) * self.get_parameter_from_exponent(
                'linear_diffusivity')  # has units length^2/time

        #  threshold has units of  Length per Time which is what
        # StreamPowerSmoothThresholdEroder expects
        self.threshold_value = self._length_factor * self.get_parameter_from_exponent(
            'erosion__threshold')  # has units length/time

        # check that a stream power and a shear stress parameter have not both been given
        if K_sp != None and K_ss != None:
            raise ValueError('A parameter for both K_sp and K_ss has been'
                             'provided. Only one of these may be provided')
        elif K_sp != None or K_ss != None:
            if K_sp != None:
                self.K = K_sp
            else:
                self.K = (self._length_factor**(
                    1. / 3.)) * K_ss  # K_ss has units Lengtg^(1/3) per Time
        else:
            raise ValueError('A value for K_sp or K_ss  must be provided.')

        # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method
        self.flow_router = FlowAccumulator(
            self.grid,
            flow_director='D8',
            depression_finder=DepressionFinderAndRouter)

        # Create a field for the (initial) erosion threshold
        self.threshold = self.grid.add_zeros('node', 'erosion__threshold')
        self.threshold[:] = self.threshold_value

        # Instantiate a FastscapeEroder component
        self.eroder = StreamPowerSmoothThresholdEroder(
            self.grid,
            m_sp=self.params['m_sp'],
            n_sp=self.params['n_sp'],
            K_sp=self.K,
            threshold_sp=self.threshold)

        # Get the parameter for rate of threshold increase with erosion depth
        self.thresh_change_per_depth = self.params['thresh_change_per_depth']

        # Instantiate a LinearDiffuser component
        self.diffuser = LinearDiffuser(self.grid,
                                       linear_diffusivity=linear_diffusivity)
Ejemplo n.º 34
0
class BasicRtSa(ErosionModel):
    """
    A BasicSaRt computes erosion using linear diffusion, basic
    stream power with rock and till layers, and Q~A.

    It creates soil through weathering and consideres soil thickness
    in calculating hillslope diffusion.
    """
    def __init__(self,
                 input_file=None,
                 params=None,
                 BaselevelHandlerClass=None):
        """Initialize the BasicSaRt."""

        # Call ErosionModel's init
        super(BasicSaRt,
              self).__init__(input_file=input_file,
                             params=params,
                             BaselevelHandlerClass=BaselevelHandlerClass)
        contact_zone__width = (self._length_factor) * self.params[
            'contact_zone__width']  # has units length
        self.K_rock_sp = self.get_parameter_from_exponent('K_rock_sp')
        self.K_till_sp = self.get_parameter_from_exponent('K_till_sp')
        linear_diffusivity = (
            self._length_factor**
            2.) * self.get_parameter_from_exponent('linear_diffusivity')

        # Set up rock-till
        self.setup_rock_and_till(self.params['rock_till_file__name'],
                                 self.K_rock_sp, self.K_till_sp,
                                 contact_zone__width)

        # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method
        self.flow_router = FlowAccumulator(
            self.grid,
            flow_director='D8',
            depression_finder=DepressionFinderAndRouter)

        # Instantiate a FastscapeEroder component
        self.eroder = FastscapeEroder(self.grid,
                                      K_sp=self.erody,
                                      m_sp=self.params['m_sp'],
                                      n_sp=self.params['n_sp'])

        # Create soil thickness (a.k.a. depth) field
        if 'soil__depth' in self.grid.at_node:
            soil_thickness = self.grid.at_node['soil__depth']
        else:
            soil_thickness = self.grid.add_zeros('node', 'soil__depth')

        # Create bedrock elevation field
        if 'bedrock__elevation' in self.grid.at_node:
            bedrock_elev = self.grid.at_node['bedrock__elevation']
        else:
            bedrock_elev = self.grid.add_zeros('node', 'bedrock__elevation')

        # Set soil thickness and bedrock elevation
        try:
            initial_soil_thickness = (self._length_factor) * self.params[
                'initial_soil_thickness']  # has units length
        except KeyError:
            initial_soil_thickness = 1.0  # default value

        soil_transport_decay_depth = (self._length_factor) * self.params[
            'soil_transport_decay_depth']  # has units length
        max_soil_production_rate = (self._length_factor) * self.params[
            'max_soil_production_rate']  # has units length per time
        soil_production_decay_depth = (self._length_factor) * self.params[
            'soil_production_decay_depth']  # has units length

        soil_thickness[:] = initial_soil_thickness
        bedrock_elev[:] = self.z - initial_soil_thickness

        # Instantiate diffusion and weathering components
        self.diffuser = DepthDependentDiffuser(
            self.grid,
            linear_diffusivity=linear_diffusivity,
            soil_transport_decay_depth=soil_transport_decay_depth)

        self.weatherer = ExponentialWeatherer(
            self.grid,
            max_soil_production_rate=max_soil_production_rate,
            soil_production_decay_depth=soil_production_decay_depth)

    def setup_rock_and_till(self, file_name, rock_erody, till_erody,
                            contact_width):
        """Set up lithology handling for two layers with different erodibility.

        Parameters
        ----------
        file_name : string
            Name of arc-ascii format file containing elevation of contact
            position at each grid node (or NODATA)

        Read elevation of rock-till contact from an esri-ascii format file
        containing the basal elevation value at each node, create a field for
        erodibility.

        Some considerations here:
            1. We could represent the contact between two layers either as a
               depth below present land surface, or as an altitude. Using a
               depth would allow for vertical motion, because for a fixed
               surface, the depth remains constant while the altitude changes.
               But the depth must be updated every time the surface is eroded
               or aggrades. Using an altitude avoids having to update the
               contact position every time the surface erodes or aggrades, but
               any tectonic motion would need to be applied to the contact
               position as well. Here we'll use the altitude approach because
               this model was originally written for an application with lots
               of erosion expected but no tectonics.
        """
        from landlab.io import read_esri_ascii

        # Read input data on rock-till contact elevation
        read_esri_ascii(file_name,
                        grid=self.grid,
                        name='rock_till_contact__elevation',
                        halo=1)

        # Get a reference to the rock-till field
        self.rock_till_contact = self.grid.at_node[
            'rock_till_contact__elevation']

        # Create field for erodibility
        if 'substrate__erodibility' in self.grid.at_node:
            self.erody = self.grid.at_node['substrate__erodibility']
        else:
            self.erody = self.grid.add_zeros('node', 'substrate__erodibility')

        # Create array for erodibility weighting function
        self.erody_wt = np.zeros(self.grid.number_of_nodes)

        # Read the erodibility value of rock and till
        self.rock_erody = rock_erody
        self.till_erody = till_erody

        # Read and remember the contact zone characteristic width
        self.contact_width = contact_width

    def update_erodibility_field(self):
        """Update erodibility at each node based on elevation relative to
        contact elevation.

        To promote smoothness in the solution, the erodibility at a given point
        (x,y) is set as follows:

            1. Take the difference between elevation, z(x,y), and contact
               elevation, b(x,y): D(x,y) = z(x,y) - b(x,y). This number could
               be positive (if land surface is above the contact), negative
               (if we're well within the rock), or zero (meaning the rock-till
               contact is right at the surface).
            2. Define a smoothing function as:
                $F(D) = 1 / (1 + exp(-D/D*))$
               This sigmoidal function has the property that F(0) = 0.5,
               F(D >> D*) = 1, and F(-D << -D*) = 0.
                   Here, D* describes the characteristic width of the "contact
               zone", where the effective erodibility is a mixture of the two.
               If the surface is well above this contact zone, then F = 1. If
               it's well below the contact zone, then F = 0.
            3. Set the erodibility using F:
                $K = F K_till + (1-F) K_rock$
               So, as F => 1, K => K_till, and as F => 0, K => K_rock. In
               between, we have a weighted average.

        Translating these symbols into variable names:

            z = self.elev
            b = self.rock_till_contact
            D* = self.contact_width
            F = self.erody_wt
            K_till = self.till_erody
            K_rock = self.rock_erody
        """

        # Update the erodibility weighting function (this is "F")
        self.erody_wt[self.data_nodes] = (1.0 / (1.0 + np.exp(
            -(self.z[self.data_nodes] -
              self.rock_till_contact[self.data_nodes]) / self.contact_width)))

        # (if we're varying K through time, update that first)
        if self.opt_var_precip:
            erode_factor = self.pc.get_erodibility_adjustment_factor(
                self.model_time)
            self.till_erody = self.K_till_sp * erode_factor
            self.rock_erody = self.K_rock_sp * erode_factor

        # Calculate the effective erodibilities using weighted averaging
        self.erody[:] = (self.erody_wt * self.till_erody +
                         (1.0 - self.erody_wt) * self.rock_erody)

    def run_one_step(self, dt):
        """
        Advance model for one time-step of duration dt.
        """

        # Route flow
        self.flow_router.run_one_step()

        # Get IDs of flooded nodes, if any
        flooded = np.where(
            self.flow_router.depression_finder.flood_status == 3)[0]

        # Update the erodibility field
        self.update_erodibility_field()

        # Do some erosion (but not on the flooded nodes)
        self.eroder.run_one_step(dt,
                                 flooded_nodes=flooded,
                                 K_if_used=self.erody)

        # We must also now erode the bedrock where relevant. If water erosion
        # into bedrock has occurred, the bedrock elevation will be higher than
        # the actual elevation, so we simply re-set bedrock elevation to the
        # lower of itself or the current elevation.
        b = self.grid.at_node['bedrock__elevation']
        b[:] = np.minimum(b, self.grid.at_node['topographic__elevation'])

        # Calculate regolith-production rate
        self.weatherer.calc_soil_prod_rate()

        # Generate and move soil around
        self.diffuser.run_one_step(dt)

        # calculate model time
        self.model_time += dt

        # Lower outlet
        self.update_outlet(dt)

        # Check walltime
        self.check_walltime()
class BasicHySt(_StochasticErosionModel):
    """
    A BasicHySt computes erosion using (1) hybrid alluvium river erosion,
    (2) linear nhillslope diffusion, and 
    (3) generation of a random sequence of runoff events across a topographic
    surface.
    
    Examples
    --------
    >>> from erosion_model import StochasticRainDepthDepThresholdModel
    >>> my_pars = {}
    >>> my_pars['dt'] = 1.0 
    >>> my_pars['run_duration'] = 1.0
    >>> my_pars['infiltration_capacity'] = 1.0
    >>> my_pars['K_sp'] = 1.0
    >>> my_pars['threshold_sp'] = 1.0
    >>> my_pars['linear_diffusivity'] = 0.01
    >>> my_pars['mean_storm_duration'] = 0.002
    >>> my_pars['mean_interstorm_duration'] = 0.008
    >>> my_pars['mean_storm_depth'] = 0.025
    >>> srt = StochasticRainDepthDepThresholdModel(params=my_pars)
    Warning: no DEM specified; creating 4x5 raster grid
    """
    def __init__(self,
                 input_file=None,
                 params=None,
                 BaselevelHandlerClass=None):
        """Initialize the BasicHySt."""

        # Call ErosionModel's init
        super(BasicHySt,
              self).__init__(input_file=input_file,
                             params=params,
                             BaselevelHandlerClass=BaselevelHandlerClass)

        # Get Parameters:
        K = ((self._length_factor**0.5)  # K_stochastic [=] L^(1/2)  T^-(1/2)
             * self.get_parameter_from_exponent('K_stochastic_sp'))

        linear_diffusivity = (
            (self._length_factor**2) *
            self.get_parameter_from_exponent('linear_diffusivity'))  # L^2/T

        v_s = (self._length_factor) * self.get_parameter_from_exponent(
            'v_s')  # has units length per time

        # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method
        self.flow_router = FlowAccumulator(
            self.grid,
            flow_director='D8',
            depression_finder=DepressionFinderAndRouter)

        #set methods and fields.
        method = 'simple_stream_power'
        discharge_method = 'discharge_field'
        area_field = None
        discharge_field = 'surface_water__discharge'

        # instantiate rain generator
        self.instantiate_rain_generator()

        # Add a field for discharge
        if 'surface_water__discharge' not in self.grid.at_node:
            self.grid.add_zeros('node', 'surface_water__discharge')
        self.discharge = self.grid.at_node['surface_water__discharge']

        # Get the infiltration-capacity parameter
        infiltration_capacity = (self._length_factor *
                                 self.params['infiltration_capacity'])  # L/T
        self.infilt = infiltration_capacity

        # Run flow routing and lake filler
        self.flow_router.run_one_step()

        # Keep a reference to drainage area
        self.area = self.grid.at_node['drainage_area']

        # Handle solver option
        try:
            solver = self.params['solver']
        except:
            solver = 'original'

        # Instantiate an ErosionDeposition component
        self.eroder = ErosionDeposition(self.grid,
                                        K=K,
                                        F_f=self.params['F_f'],
                                        phi=self.params['phi'],
                                        v_s=v_s,
                                        m_sp=self.params['m_sp'],
                                        n_sp=self.params['n_sp'],
                                        method=method,
                                        discharge_method=discharge_method,
                                        area_field=area_field,
                                        discharge_field=discharge_field,
                                        solver=solver)

        # Instantiate a LinearDiffuser component
        self.diffuser = LinearDiffuser(self.grid,
                                       linear_diffusivity=linear_diffusivity)

    def calc_runoff_and_discharge(self):
        """Calculate runoff rate and discharge; return runoff."""
        if self.rain_rate > 0.0 and self.infilt > 0.0:
            runoff = self.rain_rate - (
                self.infilt * (1.0 - np.exp(-self.rain_rate / self.infilt)))
            if runoff < 0:
                runoff = 0
        else:
            runoff = self.rain_rate
        self.discharge[:] = runoff * self.area
        return runoff

    def run_one_step(self, dt):
        """
        Advance model for one time-step of duration dt.
        """
        # Route flow
        self.flow_router.run_one_step()

        # Get IDs of flooded nodes, if any
        flooded = np.where(
            self.flow_router.depression_finder.flood_status == 3)[0]

        # Handle water erosion
        self.handle_water_erosion(dt, flooded)

        # Do some soil creep
        self.diffuser.run_one_step(dt)

        # calculate model time
        self.model_time += dt

        # Lower outlet
        self.update_outlet(dt)

        # Check walltime
        self.check_walltime()
Ejemplo n.º 36
0
def test_three_pits():
    """
    A test to ensure the component correctly handles cases where there are
    multiple pits.
    """
    mg = RasterModelGrid((10, 10))
    z = mg.add_field("node", "topographic__elevation", mg.node_x.copy())
    # a sloping plane
    # np.random.seed(seed=0)
    # z += np.random.rand(100)/10000.
    # punch some holes
    z[33] = 1.0
    z[43] = 1.0
    z[37] = 4.0
    z[74:76] = 1.0
    fr = FlowAccumulator(mg, flow_director="D8")
    lf = DepressionFinderAndRouter(mg)
    fr.run_one_step()
    lf.map_depressions()

    flow_sinks_target = np.zeros(100, dtype=bool)
    flow_sinks_target[mg.boundary_nodes] = True
    # no internal sinks now:
    assert_array_equal(mg.at_node["flow__sink_flag"], flow_sinks_target)

    # test conservation of mass:
    assert mg.at_node["drainage_area"].reshape(
        (10, 10))[1:-1, 1].sum() == approx(8.0**2)
    # ^all the core nodes

    # test the actual flow field:
    nA = np.array([
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        8.0,
        8.0,
        7.0,
        6.0,
        5.0,
        4.0,
        3.0,
        2.0,
        1.0,
        0.0,
        2.0,
        2.0,
        1.0,
        1.0,
        2.0,
        1.0,
        1.0,
        1.0,
        1.0,
        0.0,
        26.0,
        26.0,
        25.0,
        15.0,
        11.0,
        10.0,
        9.0,
        8.0,
        1.0,
        0.0,
        2.0,
        2.0,
        1.0,
        9.0,
        2.0,
        1.0,
        1.0,
        1.0,
        1.0,
        0.0,
        2.0,
        2.0,
        1.0,
        1.0,
        5.0,
        4.0,
        3.0,
        2.0,
        1.0,
        0.0,
        2.0,
        2.0,
        1.0,
        1.0,
        1.0,
        1.0,
        3.0,
        2.0,
        1.0,
        0.0,
        20.0,
        20.0,
        19.0,
        18.0,
        17.0,
        12.0,
        3.0,
        2.0,
        1.0,
        0.0,
        2.0,
        2.0,
        1.0,
        1.0,
        1.0,
        1.0,
        3.0,
        2.0,
        1.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
    ])
    assert_array_equal(mg.at_node["drainage_area"], nA)

    # test a couple more properties:
    lc = np.empty(100, dtype=int)
    lc.fill(XX)
    lc[33] = 33
    lc[43] = 33
    lc[37] = 37
    lc[74:76] = 74
    assert_array_equal(lf.lake_map, lc)
    assert_array_equal(lf.lake_codes, [33, 37, 74])
    assert lf.number_of_lakes == 3
    assert lf.lake_areas == approx([2.0, 1.0, 2.0])
    assert lf.lake_volumes == approx([2.0, 2.0, 4.0])
Ejemplo n.º 37
0
def test_flow__distance_raster_MFD_diagonals_true():
    """Test of flow__distance utility with a raster grid and MFD."""

    # instantiate a model grid

    mg = RasterModelGrid((5, 4), xy_spacing=(1, 1))

    # instantiate an elevation array

    z = np.array(
        [[0, 0, 0, 0], [0, 21, 10, 0], [0, 31, 20, 0], [0, 32, 30, 0],
         [0, 0, 0, 0]],
        dtype="float64",
    )

    # add the elevation field to the grid

    mg.add_field("node", "topographic__elevation", z)

    # instantiate the expected flow__distance array
    # considering flow directions calculated with MFD algorithm

    flow__distance_expected = np.array(
        [
            [0, 0, 0, 0],
            [0, 1, 0, 0],
            [0, math.sqrt(2), 1, 0],
            [0, 1 + math.sqrt(2), 2, 0],
            [0, 0, 0, 0],
        ],
        dtype="float64",
    )
    flow__distance_expected = np.reshape(
        flow__distance_expected,
        mg.number_of_node_rows * mg.number_of_node_columns)

    # setting boundary conditions

    mg.set_closed_boundaries_at_grid_edges(
        bottom_is_closed=True,
        left_is_closed=True,
        right_is_closed=True,
        top_is_closed=True,
    )

    # calculating flow directions with FlowAccumulator component

    fa = FlowAccumulator(mg,
                         "topographic__elevation",
                         flow_director="MFD",
                         diagonals=True)
    fa.run_one_step()

    # calculating flow distance map

    flow__distance = calculate_flow__distance(mg,
                                              add_to_grid=True,
                                              noclobber=False)

    # test that the flow__distance utility works as expected

    assert_array_equal(flow__distance_expected, flow__distance)
Ejemplo n.º 38
0
catagory_weight_file = os.path.join(os.path.abspath(os.sep), *['work', 'WVDP_EWG_STUDY3', 'study3py','auxillary_inputs', 'weights', 'sew.chi_elev_weight.20.txt'])
cat_wt = np.loadtxt(catagory_weight_file)

#%%
input_file = glob.glob(os.path.join(os.sep, *(work_dir+['model**', '**', 'inputs_template.txt'])))[0]
with open(input_file, 'r') as f:
    inputs = yaml.load(f)

outlet_id = inputs['outlet_id']
# observed_topography
observed_topo_file_name = inputs['modern_dem_name']
(grid, z) = read_esri_ascii(observed_topo_file_name, name='topographic__elevation', halo=1)
grid.set_watershed_boundary_condition_outlet_id(inputs['outlet_id'], z, nodata_value=-9999)
fa = FlowAccumulator(grid,
                     flow_director='D8',
                     depression_finder = 'DepressionFinderAndRouter')
fa.run_one_step()
#hs = grid.calc_hillshade_at_node()
ch = ChiFinder(grid, min_drainage_area=10*grid.dx**2)
ch.calculate_chi()

# initial condition topography
initial_topo_file_name = inputs['DEM_filename']
(igrid, iz) = read_esri_ascii(initial_topo_file_name, name='topographic__elevation', halo=1)
igrid.set_watershed_boundary_condition_outlet_id(inputs['outlet_id'], iz, nodata_value=-9999)
ifa = FlowAccumulator(igrid,
                      flow_director='D8',
                      depression_finder = 'DepressionFinderAndRouter')
ifa.run_one_step()
#ihs = grid.calc_hillshade_at_node()
Ejemplo n.º 39
0
class SinkFiller(Component):
    """
    This component identifies depressions in a topographic surface, then fills
    them in in the topography.  No attempt is made to conserve sediment mass.
    User may specify whether the holes should be filled to flat, or with a
    gradient downwards towards the depression outlet. The gradient can be
    spatially variable, and is chosen to not reverse any drainage directions
    at the perimeter of each lake.

    The primary method of this class is 'run_one_step'. 'fill_pits' is a
    synonym.

    Constructor assigns a copy of the grid, and calls the initialize
    method.

    Examples
    --------
    >>> from landlab import RasterModelGrid
    >>> from landlab import BAD_INDEX_VALUE as XX
    >>> from landlab.components import FlowAccumulator, SinkFiller
    >>> import numpy as np
    >>> lake1 = np.array([34, 35, 36, 44, 45, 46, 54, 55, 56, 65, 74])
    >>> lake2 = np.array([78, 87, 88])
    >>> guard_nodes = np.array([23, 33, 53, 63, 73, 83])
    >>> lake = np.concatenate((lake1, lake2))
    >>> mg = RasterModelGrid((10, 10))
    >>> z = np.ones(100, dtype=float)
    >>> z += mg.node_x  # add a slope
    >>> z[guard_nodes] += 0.001  # forces the flow out of a particular node
    >>> z[lake] = 0.
    >>> field = mg.add_field('node', 'topographic__elevation', z,
    ...                      units='-', copy=True)
    >>> fr = FlowAccumulator(mg, flow_director='D8')
    >>> fr.run_one_step()
    >>> mg.at_node['flow__sink_flag'][mg.core_nodes].sum()
    14
    >>> hf = SinkFiller(mg, apply_slope=False)
    >>> hf.run_one_step()
    >>> np.allclose(mg.at_node['topographic__elevation'][lake1], 4.)
    True
    >>> np.allclose(mg.at_node['topographic__elevation'][lake2], 7.)
    True

    Now reset and demonstrate the adding of an inclined surface:

    >>> field[:] = z
    >>> hf = SinkFiller(mg, apply_slope=True)
    >>> hf.run_one_step()
    >>> hole1 = np.array([4.00007692, 4.00015385, 4.00023077, 4.00030769,
    ...                   4.00038462, 4.00046154, 4.00053846, 4.00061538,
    ...                   4.00069231, 4.00076923, 4.00084615])
    >>> hole2 = np.array([7.4, 7.2, 7.6])
    >>> np.allclose(mg.at_node['topographic__elevation'][lake1], hole1)
    True
    >>> np.allclose(mg.at_node['topographic__elevation'][lake2], hole2)
    True
    >>> fr.run_one_step()
    >>> mg.at_node['flow__sink_flag'][mg.core_nodes].sum()
    0
    """

    _name = "SinkFiller"

    _input_var_names = ("topographic__elevation", )

    _output_var_names = ("topographic__elevation", "sediment_fill__depth")

    _var_units = {"topographic__elevation": "m", "sediment_fill__depth": "m"}

    _var_mapping = {
        "topographic__elevation": "node",
        "sediment_fill__depth": "node"
    }

    _var_doc = {
        "topographic__elevation": "Surface topographic elevation",
        "sediment_fill__depth": "Depth of sediment added at each" + "node",
    }

    @use_file_name_or_kwds
    def __init__(self,
                 grid,
                 routing="D8",
                 apply_slope=False,
                 fill_slope=1.0e-5,
                 **kwds):
        """
        Parameters
        ----------
        grid : ModelGrid
            A landlab grid.
        routing : {'D8', 'D4'} (optional)
            If grid is a raster type, controls whether fill connectivity can
            occur on diagonals ('D8', default), or only orthogonally ('D4').
            Has no effect if grid is not a raster.
        apply_slope : bool
            If False (default), leave the top of the filled sink flat. If True,
            apply the slope fill_slope to the top surface to allow subsequent flow
            routing. A test is performed to ensure applying this slope will not
            alter the drainage structure at the edge of the filled region
            (i.e., that we are not accidentally reversing the flow direction
            far from the outlet.)
        fill_slope : float (m/m)
            The slope added to the top surface of filled pits to allow flow
            routing across them, if apply_slope.
        """
        if "flow__receiver_node" in grid.at_node:
            if grid.at_node["flow__receiver_node"].size != grid.size("node"):
                msg = (
                    "A route-to-multiple flow director has been "
                    "run on this grid. The landlab development team has not "
                    "verified that SinkFiller is compatible with "
                    "route-to-multiple methods. Please open a GitHub Issue "
                    "to start this process.")
                raise NotImplementedError(msg)

        self._grid = grid
        if routing != "D8":
            assert routing == "D4"
        self._routing = routing
        if (type(self._grid) is
                landlab.grid.raster.RasterModelGrid) and (routing == "D8"):
            self._D8 = True
            self.num_nbrs = 8
        else:
            self._D8 = False  # useful shorthand for thia test we do a lot
            if type(self._grid) is landlab.grid.raster.RasterModelGrid:
                self.num_nbrs = 4
        self._fill_slope = fill_slope
        self._apply_slope = apply_slope
        self.initialize()

    def initialize(self, input_stream=None):
        """
        The BMI-style initialize method takes an optional input_stream
        parameter, which may be either a ModelParameterDictionary object or
        an input stream from which a ModelParameterDictionary can read values.
        """
        # Create a ModelParameterDictionary for the inputs
        if input_stream is None:
            inputs = None
        elif type(input_stream) == ModelParameterDictionary:
            inputs = input_stream
        else:
            inputs = ModelParameterDictionary(input_stream)

        # Make sure the grid includes elevation data. This means either:
        #  1. The grid has a node field called 'topographic__elevation', or
        #  2. The input file has an item called 'ELEVATION_FIELD_NAME' *and*
        #     a field by this name exists in the grid.
        try:
            self._elev = self._grid.at_node["topographic__elevation"]
        except FieldError:
            try:
                self.topo_field_name = inputs.read_string("ELEVATION_" +
                                                          "FIELD_NAME")
            except AttributeError:
                print("Error: Because your grid does not have a node field")
                print('called "topographic__elevation", you need to pass the')
                print("name of a text input file or ModelParameterDictionary,")
                print("and this file or dictionary needs to include the name")
                print("of another field in your grid that contains your")
                print("elevation data.")
                raise AttributeError
            except MissingKeyError:
                print("Error: Because your grid does not have a node field")
                print('called "topographic__elevation", your input file (or')
                print("ModelParameterDictionary) must include an entry with")
                print('the key "ELEVATION_FIELD_NAME", which gives the name')
                print("of a field in your grid that contains your elevation")
                print("data.")
                raise MissingKeyError("ELEVATION_FIELD_NAME")
            try:
                self._elev = self._grid.at_node[self.topo_field_name]
            except AttributeError:
                print(
                    "Your grid does not seem to have a node field called",
                    self.topo_field_name,
                )
        else:
            self.topo_field_name = "topographic__elevation"
        # create the only new output field:
        self.sed_fill_depth = self._grid.add_zeros("node",
                                                   "sediment_fill__depth",
                                                   noclobber=False)

        self._lf = DepressionFinderAndRouter(self._grid, routing=self._routing)
        self._fr = FlowAccumulator(self._grid, flow_director=self._routing)

    def fill_pits(self, **kwds):
        """
        This is a synonym for the main method :func:`run_one_step`.
        """
        self.run_one_step(**kwds)

    def run_one_step(self, **kwds):
        """
        This is the main method. Call it to fill depressions in a starting
        topography.
        """
        # added for back-compatibility with old formats
        try:
            self._apply_slope = kwds["apply_slope"]
        except KeyError:
            pass
        self.original_elev = self._elev.copy()
        # We need this, as we'll have to do ALL this again if we manage
        # to jack the elevs too high in one of the "subsidiary" lakes.
        # We're going to implement the lake_mapper component to do the heavy
        # lifting here, then delete its fields. This means we first need to
        # test if these fields already exist, in which case, we should *not*
        # delete them!
        existing_fields = {}
        spurious_fields = set()
        set_of_outputs = set(self._lf.output_var_names) | set(
            self._fr.output_var_names)
        try:
            set_of_outputs.remove(self.topo_field_name)
        except KeyError:
            pass
        for field in set_of_outputs:
            try:
                existing_fields[field] = self._grid.at_node[field].copy()
            except FieldError:  # not there; good!
                spurious_fields.add(field)

        self._fr.run_one_step()
        self._lf.map_depressions(pits=self._grid.at_node["flow__sink_flag"],
                                 reroute_flow=True)
        # add the depression depths to get up to flat:
        self._elev += self._grid.at_node["depression__depth"]
        # if apply_slope is none, we're now done! But if not...
        if self._apply_slope:
            # new way of doing this - use the upstream structure! Should be
            # both more general and more efficient
            for (outlet_node, lake_code) in zip(self._lf.lake_outlets,
                                                self._lf.lake_codes):
                lake_nodes = np.where(self._lf.lake_map == lake_code)[0]
                lake_perim = self._get_lake_ext_margin(lake_nodes)
                perim_elevs = self._elev[lake_perim]
                out_elev = self._elev[outlet_node]
                lowest_elev_perim = perim_elevs[perim_elevs != out_elev].min()
                # note we exclude the outlet node
                elev_increment = (lowest_elev_perim - self._elev[outlet_node]
                                  ) / (lake_nodes.size + 2.0)
                assert elev_increment > 0.0
                all_ordering = self._grid.at_node["flow__upstream_node_order"]
                upstream_order_bool = np.in1d(all_ordering,
                                              lake_nodes,
                                              assume_unique=True)
                lake_upstream_order = all_ordering[upstream_order_bool]
                argsort_lake = np.argsort(lake_upstream_order)
                elevs_to_add = (np.arange(lake_nodes.size, dtype=float) +
                                1.0) * elev_increment
                sorted_elevs_to_add = elevs_to_add[argsort_lake]
                self._elev[lake_nodes] += sorted_elevs_to_add
        # now put back any fields that were present initially, and wipe the
        # rest:
        for delete_me in spurious_fields:
            if delete_me in self._grid.at_node:
                self._grid.delete_field("node", delete_me)
        for update_me in existing_fields.keys():
            self.grid.at_node[update_me][:] = existing_fields[update_me]
        # fill the output field
        self.sed_fill_depth[:] = self._elev - self.original_elev

    @deprecated(use="fill_pits", version=1.0)
    def _fill_pits_old(self, apply_slope=None):
        """

        .. deprecated:: 0.1.38
            Use :func:`fill_pits` instead.

        This is the main method. Call it to fill depressions in a starting
        topography.

        **Output fields**

        *  `topographic__elevation` : the updated elevations
        *  `sediment_fill__depth` : the depth of sediment added at each node

        Parameters
        ----------
        apply_slope : None, bool, or float
            If a float is provided this is the slope of the surface down
            towards the lake outlet. Supply a small positive number, e.g.,
            1.e-5 (or True, to use this default value).
            A test is performed to ensure applying this slope will not alter
            the drainage structure at the edge of the filled region (i.e.,
            that we are not accidentally reversing the flow direction far
            from the outlet.) The component will automatically decrease the
            (supplied or default) gradient a number of times to try to
            accommodate this, but will eventually raise an OverflowError
            if it can't deal with it. If you pass True, the method will use
            the default value of 1.e-5.
        """
        self.original_elev = self._elev.copy()
        # We need this, as we'll have to do ALL this again if we manage
        # to jack the elevs too high in one of the "subsidiary" lakes.
        # We're going to implement the lake_mapper component to do the heavy
        # lifting here, then delete its fields. This means we first need to
        # test if these fields already exist, in which case, we should *not*
        # delete them!
        existing_fields = {}
        spurious_fields = set()
        set_of_outputs = self._lf.output_var_names | self._fr.output_var_names
        try:
            set_of_outputs.remove(self.topo_field_name)
        except KeyError:
            pass
        for field in set_of_outputs:
            try:
                existing_fields[field] = self._grid.at_node[field].copy()
            except FieldError:  # not there; good!
                spurious_fields.add(field)

        self._fr.run_one_step()
        self._lf.map_depressions(pits=self._grid.at_node["flow__sink_flag"],
                                 reroute_flow=False)
        # add the depression depths to get up to flat:
        self._elev += self._grid.at_node["depression__depth"]
        # if apply_slope is none, we're now done! But if not...
        if apply_slope is True:
            apply_slope = self._fill_slope
        elif type(apply_slope) in (float, int):
            assert apply_slope >= 0.0
        if apply_slope:
            # this isn't very efficient, but OK as we're only running this
            # code ONCE in almost all use cases
            sublake = False
            unstable = True
            stability_increment = 0
            self.lake_nodes_treated = np.array([], dtype=int)
            while unstable:
                while 1:
                    for (outlet_node,
                         lake_code) in zip(self._lf.lake_outlets,
                                           self._lf.lake_codes):
                        self._apply_slope_current_lake(apply_slope,
                                                       outlet_node, lake_code,
                                                       sublake)
                    # Call the mapper again here. Bail out if no core pits are
                    # found.
                    # This is necessary as there are some configs where adding
                    # the slope could create subsidiary pits in the topo
                    self._lf.map_depressions(pits=None, reroute_flow=False)
                    if len(self._lf.lake_outlets) == 0.0:
                        break
                    self._elev += self._grid.at_node["depression__depth"]
                    sublake = True
                    self.lake_nodes_treated = np.array([], dtype=int)
                # final test that all lakes are not reversing flow dirs
                all_lakes = np.where(
                    self._lf.flood_status < BAD_INDEX_VALUE)[0]
                unstable = self.drainage_directions_change(
                    all_lakes, self.original_elev, self._elev)
                if unstable:
                    apply_slope *= 0.1
                    sublake = False
                    self.lake_nodes_treated = np.array([], dtype=int)
                    self._elev[:] = self.original_elev  # put back init conds
                    stability_increment += 1
                    if stability_increment == 10:
                        raise OverflowError("Filler could not find a stable " +
                                            "condition with a sloping " +
                                            "surface!")
        # now put back any fields that were present initially, and wipe the
        # rest:
        for delete_me in spurious_fields:
            if delete_me in self.grid.at_node:
                self._grid.delete_field("node", delete_me)
        for update_me in existing_fields.keys():
            self.grid.at_node[update_me] = existing_fields[update_me]
        # fill the output field
        self.sed_fill_depth[:] = self._elev - self.original_elev

    def _add_slopes(self, slope, outlet_node, lake_code):
        """
        Assuming you have already run the lake_mapper, adds an incline towards
        the outlet to the nodes in the lake.
        """
        new_elevs = self._elev.copy()
        outlet_coord = (self._grid.node_x[outlet_node],
                        self._grid.node_y[outlet_node])
        lake_nodes = np.where(self._lf.lake_map == lake_code)[0]
        lake_nodes = np.setdiff1d(lake_nodes, self.lake_nodes_treated)
        # lake_ext_margin = self._get_lake_ext_margin(lake_nodes)
        d = self._grid.calc_distances_of_nodes_to_point(outlet_coord,
                                                        node_subset=lake_nodes)
        add_vals = slope * d
        new_elevs[lake_nodes] += add_vals
        self.lake_nodes_treated = np.union1d(self.lake_nodes_treated,
                                             lake_nodes)
        return new_elevs, lake_nodes

    def _get_lake_ext_margin(self, lake_nodes):
        """
        Returns the nodes forming the external margin of the lake, honoring
        the *routing* method (D4/D8) if applicable.
        """
        if self._D8 is True:
            all_poss = np.union1d(
                self.grid.active_adjacent_nodes_at_node[lake_nodes],
                self.grid.diagonal_adjacent_nodes_at_node[lake_nodes],
            )
        else:
            all_poss = np.unique(
                self.grid.active_adjacent_nodes_at_node[lake_nodes])
        lake_ext_edge = np.setdiff1d(all_poss, lake_nodes)
        return lake_ext_edge[lake_ext_edge != BAD_INDEX_VALUE]

    def _get_lake_int_margin(self, lake_nodes, lake_ext_edge):
        """
        Returns the nodes forming the internal margin of the lake, honoring
        the *routing* method (D4/D8) if applicable.
        """
        lee = lake_ext_edge
        if self._D8 is True:
            all_poss_int = np.union1d(
                self._grid.active_adjacent_nodes_at_node[lee],
                self._grid.diagonal_adjacent_nodes_at_node[lee],
            )
        else:
            all_poss_int = np.unique(
                self._grid.active_adjacent_nodes_at_node[lee])
        lake_int_edge = np.intersect1d(all_poss_int, lake_nodes)
        return lake_int_edge[lake_int_edge != BAD_INDEX_VALUE]

    def _apply_slope_current_lake(self, apply_slope, outlet_node, lake_code,
                                  sublake):
        """
        Wraps the _add_slopes method to allow handling of conditions where the
        drainage structure would be changed or we're dealing with a sublake.
        """
        while 1:
            starting_elevs = self._elev.copy()
            self._elev[:], lake_nodes = self._add_slopes(
                apply_slope, outlet_node, lake_code)
            # ext_edge = self._get_lake_ext_margin(lake_nodes)
            if sublake:
                break
            else:
                if not self.drainage_directions_change(
                        lake_nodes, starting_elevs, self._elev):
                    break
                else:
                    # put the elevs back...
                    self._elev[lake_nodes] = starting_elevs[lake_nodes]
                    # the slope was too big. Reduce it.
                    apply_slope *= 0.1
        # if we get here, either sublake, or drainage dirs are stable

    def drainage_directions_change(self, lake_nodes, old_elevs, new_elevs):
        """
        True if the drainage structure at lake margin changes, False otherwise.
        """
        ext_edge = self._get_lake_ext_margin(lake_nodes)
        if self._D8:
            edge_neighbors = np.hstack((
                self.grid.active_adjacent_nodes_at_node[ext_edge],
                self.grid.diagonal_adjacent_nodes_at_node[ext_edge],
            ))
        else:
            edge_neighbors = self.grid.active_adjacent_nodes_at_node[
                ext_edge].copy()
        edge_neighbors[edge_neighbors == BAD_INDEX_VALUE] = -1
        # ^value irrelevant
        old_neighbor_elevs = old_elevs[edge_neighbors]
        new_neighbor_elevs = new_elevs[edge_neighbors]
        # enforce the "don't change drainage direction" condition:
        edge_elevs = old_elevs[ext_edge].reshape((ext_edge.size, 1))
        cond = np.allclose((edge_elevs >= old_neighbor_elevs),
                           (edge_elevs >= new_neighbor_elevs))
        # if True, we're good, the tilting didn't mess with the fr
        return not cond
Ejemplo n.º 40
0
# create the fields in the grid
mg.add_zeros("topographic__elevation", at="node")
z = mg.zeros(at="node") + init_elev
mg["node"]["topographic__elevation"] = z + numpy.random.rand(len(z)) / 1000.
mg.add_zeros("node", "water__unit_flux_in")

# make some K values in a field to test
# mg.at_node['K_values'] = 0.1+numpy.random.rand(nrows*ncols)/10.
mg.at_node["K_values"] = numpy.empty(nrows * ncols, dtype=float)
# mg.at_node['K_values'].fill(0.1+numpy.random.rand()/10.)
mg.at_node["K_values"].fill(0.001)

print("Running ...")

# instantiate the components:
fr = FlowAccumulator(mg, flow_director="D8")
sp = StreamPowerEroder(mg, input_file_string)
# fsp = FastscapeEroder(mg, input_file_string)
precip = PrecipitationDistribution(input_file=input_file_string)

# load the Fastscape module too, to allow direct comparison
fsp = FastscapeEroder(mg, input_file_string)

try:
    # raise NameError
    mg = copy.deepcopy(mg_mature)
except NameError:
    print("building a new grid...")
    out_interval = 50000.
    last_trunc = time_to_run  # we use this to trigger taking an output plot
    # run to a steady state:
Ejemplo n.º 41
0
class SinkFiller(Component):
    """This component identifies depressions in a topographic surface, then
    fills them in in the topography.  No attempt is made to conserve sediment
    mass. User may specify whether the holes should be filled to flat, or with
    a gradient downwards towards the depression outlet. The gradient can be
    spatially variable, and is chosen to not reverse any drainage directions at
    the perimeter of each lake.

    The primary method of this class is 'run_one_step'. 'fill_pits' is a
    synonym.

    Constructor assigns a copy of the grid, and calls the initialize
    method.

    Examples
    --------
    >>> from landlab import RasterModelGrid
    >>> from landlab.components import FlowAccumulator, SinkFiller
    >>> import numpy as np
    >>> lake1 = np.array([34, 35, 36, 44, 45, 46, 54, 55, 56, 65, 74])
    >>> lake2 = np.array([78, 87, 88])
    >>> guard_nodes = np.array([23, 33, 53, 63, 73, 83])
    >>> lake = np.concatenate((lake1, lake2))
    >>> mg = RasterModelGrid((10, 10))
    >>> z = np.ones(100, dtype=float)
    >>> z += mg.node_x  # add a slope
    >>> z[guard_nodes] += 0.001  # forces the flow out of a particular node
    >>> z[lake] = 0.
    >>> field = mg.add_field(
    ...     "topographic__elevation",
    ...     z,
    ...     at="node",
    ...     units="-",
    ...     copy=True,
    ... )
    >>> fr = FlowAccumulator(mg, flow_director='D8')
    >>> fr.run_one_step()
    >>> mg.at_node['flow__sink_flag'][mg.core_nodes].sum()
    14
    >>> hf = SinkFiller(mg, apply_slope=False)
    >>> hf.run_one_step()
    >>> np.allclose(mg.at_node['topographic__elevation'][lake1], 4.)
    True
    >>> np.allclose(mg.at_node['topographic__elevation'][lake2], 7.)
    True

    Now reset and demonstrate the adding of an inclined surface:

    >>> field[:] = z
    >>> hf = SinkFiller(mg, apply_slope=True)
    >>> hf.run_one_step()
    >>> hole1 = np.array([4.00007692, 4.00015385, 4.00023077, 4.00030769,
    ...                   4.00038462, 4.00046154, 4.00053846, 4.00061538,
    ...                   4.00069231, 4.00076923, 4.00084615])
    >>> hole2 = np.array([7.4, 7.2, 7.6])
    >>> np.allclose(mg.at_node['topographic__elevation'][lake1], hole1)
    True
    >>> np.allclose(mg.at_node['topographic__elevation'][lake2], hole2)
    True
    >>> fr.run_one_step()
    >>> mg.at_node['flow__sink_flag'][mg.core_nodes].sum()
    0

    References
    ----------
    **Required Software Citation(s) Specific to this Component**

    None Listed

    **Additional References**

    Tucker, G., Lancaster, S., Gasparini, N., Bras, R., Rybarczyk, S. (2001).
    An object-oriented framework for distributed hydrologic and geomorphic
    modeling using triangulated irregular networks. Computers & Geosciences
    27(8), 959-973. https://dx.doi.org/10.1016/s0098-3004(00)00134-5

    """

    _name = "SinkFiller"

    _unit_agnostic = True

    _info = {
        "sediment_fill__depth": {
            "dtype": float,
            "intent": "out",
            "optional": False,
            "units": "m",
            "mapping": "node",
            "doc": "Depth of sediment added at eachnode",
        },
        "topographic__elevation": {
            "dtype": float,
            "intent": "inout",
            "optional": False,
            "units": "m",
            "mapping": "node",
            "doc": "Land surface topographic elevation",
        },
    }

    def __init__(self,
                 grid,
                 routing="D8",
                 apply_slope=False,
                 fill_slope=1.0e-5):
        """
        Parameters
        ----------
        grid : ModelGrid
            A landlab grid.
        routing : {'D8', 'D4'} (optional)
            If grid is a raster type, controls whether fill connectivity can
            occur on diagonals ('D8', default), or only orthogonally ('D4').
            Has no effect if grid is not a raster.
        apply_slope : bool
            If False (default), leave the top of the filled sink flat. If True,
            apply the slope fill_slope to the top surface to allow subsequent flow
            routing. A test is performed to ensure applying this slope will not
            alter the drainage structure at the edge of the filled region
            (i.e., that we are not accidentally reversing the flow direction
            far from the outlet.)
        fill_slope : float (m/m)
            The slope added to the top surface of filled pits to allow flow
            routing across them, if apply_slope.
        """
        super().__init__(grid)

        if "flow__receiver_node" in grid.at_node:
            if grid.at_node["flow__receiver_node"].size != grid.size("node"):
                msg = (
                    "A route-to-multiple flow director has been "
                    "run on this grid. The landlab development team has not "
                    "verified that SinkFiller is compatible with "
                    "route-to-multiple methods. Please open a GitHub Issue "
                    "to start this process.")
                raise NotImplementedError(msg)

        if routing != "D8":
            assert routing == "D4"
        self._routing = routing
        if isinstance(self._grid, RasterModelGrid) and (routing == "D8"):
            self._D8 = True
            self._num_nbrs = 8
        else:
            self._D8 = False  # useful shorthand for thia test we do a lot
            if isinstance(self._grid, RasterModelGrid):
                self._num_nbrs = 4
        self._fill_slope = fill_slope
        self._apply_slope = apply_slope

        self._elev = self._grid.at_node["topographic__elevation"]
        self._topo_field_name = "topographic__elevation"

        # create the only new output field:
        self._sed_fill_depth = self._grid.add_zeros("node",
                                                    "sediment_fill__depth",
                                                    clobber=True)

        self._lf = DepressionFinderAndRouter(self._grid,
                                             routing=self._routing,
                                             reroute_flow=True)
        self._fr = FlowAccumulator(self._grid, flow_director=self._routing)

    def fill_pits(self):
        """This is a synonym for the main method :func:`run_one_step`."""
        self.run_one_step()

    def run_one_step(self):
        """This is the main method.

        Call it to fill depressions in a starting topography.
        """
        self._original_elev = self._elev.copy()
        # We need this, as we'll have to do ALL this again if we manage
        # to jack the elevs too high in one of the "subsidiary" lakes.
        # We're going to implement the lake_mapper component to do the heavy
        # lifting here, then delete its fields. This means we first need to
        # test if these fields already exist, in which case, we should *not*
        # delete them!
        existing_fields = {}
        spurious_fields = set()
        set_of_outputs = set(self._lf.output_var_names) | set(
            self._fr.output_var_names)
        try:
            set_of_outputs.remove(self._topo_field_name)
        except KeyError:
            pass
        for field in set_of_outputs:
            try:
                existing_fields[field] = self._grid.at_node[field].copy()
            except FieldError:  # not there; good!
                spurious_fields.add(field)

        self._fr.run_one_step()
        self._lf.map_depressions()
        # add the depression depths to get up to flat:
        self._elev += self._grid.at_node["depression__depth"]
        # if apply_slope is none, we're now done! But if not...
        if self._apply_slope:
            # new way of doing this - use the upstream structure! Should be
            # both more general and more efficient
            for (outlet_node, lake_code) in zip(self._lf.lake_outlets,
                                                self._lf.lake_codes):
                lake_nodes = np.where(self._lf.lake_map == lake_code)[0]
                lake_perim = self._get_lake_ext_margin(lake_nodes)
                perim_elevs = self._elev[lake_perim]
                out_elev = self._elev[outlet_node]
                lowest_elev_perim = perim_elevs[perim_elevs != out_elev].min()
                # note we exclude the outlet node
                elev_increment = (lowest_elev_perim - self._elev[outlet_node]
                                  ) / (lake_nodes.size + 2.0)
                assert elev_increment > 0.0
                all_ordering = self._grid.at_node["flow__upstream_node_order"]
                upstream_order_bool = np.in1d(all_ordering,
                                              lake_nodes,
                                              assume_unique=True)
                lake_upstream_order = all_ordering[upstream_order_bool]
                argsort_lake = np.argsort(lake_upstream_order)
                elevs_to_add = (np.arange(lake_nodes.size, dtype=float) +
                                1.0) * elev_increment
                sorted_elevs_to_add = elevs_to_add[argsort_lake]
                self._elev[lake_nodes] += sorted_elevs_to_add
        # now put back any fields that were present initially, and wipe the
        # rest:
        for delete_me in spurious_fields:
            if delete_me in self._grid.at_node:
                self._grid.delete_field("node", delete_me)
        for update_me in existing_fields.keys():
            self._grid.at_node[update_me][:] = existing_fields[update_me]
        # fill the output field
        self._sed_fill_depth[:] = self._elev - self._original_elev

    def _add_slopes(self, slope, outlet_node, lake_code):
        """Assuming you have already run the lake_mapper, adds an incline
        towards the outlet to the nodes in the lake."""
        new_elevs = self._elev.copy()
        outlet_coord = (self._grid.node_x[outlet_node],
                        self._grid.node_y[outlet_node])
        lake_nodes = np.where(self._lf.lake_map == lake_code)[0]
        lake_nodes = np.setdiff1d(lake_nodes, self._lake_nodes_treated)
        # lake_ext_margin = self._get_lake_ext_margin(lake_nodes)
        d = self._grid.calc_distances_of_nodes_to_point(outlet_coord,
                                                        node_subset=lake_nodes)
        add_vals = slope * d
        new_elevs[lake_nodes] += add_vals
        self._lake_nodes_treated = np.union1d(self._lake_nodes_treated,
                                              lake_nodes)
        return new_elevs, lake_nodes

    def _get_lake_ext_margin(self, lake_nodes):
        """Returns the nodes forming the external margin of the lake, honoring
        the *routing* method (D4/D8) if applicable."""
        if self._D8 is True:
            all_poss = np.union1d(
                self._grid.active_adjacent_nodes_at_node[lake_nodes],
                self._grid.diagonal_adjacent_nodes_at_node[lake_nodes],
            )
        else:
            all_poss = np.unique(
                self._grid.active_adjacent_nodes_at_node[lake_nodes])
        lake_ext_edge = np.setdiff1d(all_poss, lake_nodes)
        return lake_ext_edge[lake_ext_edge != self._grid.BAD_INDEX]

    def _get_lake_int_margin(self, lake_nodes, lake_ext_edge):
        """Returns the nodes forming the internal margin of the lake, honoring
        the *routing* method (D4/D8) if applicable."""
        lee = lake_ext_edge
        if self._D8 is True:
            all_poss_int = np.union1d(
                self._grid.active_adjacent_nodes_at_node[lee],
                self._grid.diagonal_adjacent_nodes_at_node[lee],
            )
        else:
            all_poss_int = np.unique(
                self._grid.active_adjacent_nodes_at_node[lee])
        lake_int_edge = np.intersect1d(all_poss_int, lake_nodes)
        return lake_int_edge[lake_int_edge != self._grid.BAD_INDEX]

    def _apply_slope_current_lake(self, apply_slope, outlet_node, lake_code,
                                  sublake):
        """Wraps the _add_slopes method to allow handling of conditions where
        the drainage structure would be changed or we're dealing with a
        sublake."""
        while 1:
            starting_elevs = self._elev.copy()
            self._elev[:], lake_nodes = self._add_slopes(
                apply_slope, outlet_node, lake_code)
            # ext_edge = self._get_lake_ext_margin(lake_nodes)
            if sublake:
                break
            else:
                if not self.drainage_directions_change(
                        lake_nodes, starting_elevs, self._elev):
                    break
                else:
                    # put the elevs back...
                    self._elev[lake_nodes] = starting_elevs[lake_nodes]
                    # the slope was too big. Reduce it.
                    apply_slope *= 0.1
        # if we get here, either sublake, or drainage dirs are stable

    def drainage_directions_change(self, lake_nodes, old_elevs, new_elevs):
        """True if the drainage structure at lake margin changes, False
        otherwise."""
        ext_edge = self._get_lake_ext_margin(lake_nodes)
        if self._D8:
            edge_neighbors = np.hstack((
                self._grid.active_adjacent_nodes_at_node[ext_edge],
                self._grid.diagonal_adjacent_nodes_at_node[ext_edge],
            ))
        else:
            edge_neighbors = self._grid.active_adjacent_nodes_at_node[
                ext_edge].copy()
        edge_neighbors[edge_neighbors == self._grid.BAD_INDEX] = -1
        # ^value irrelevant
        old_neighbor_elevs = old_elevs[edge_neighbors]
        new_neighbor_elevs = new_elevs[edge_neighbors]
        # enforce the "don't change drainage direction" condition:
        edge_elevs = old_elevs[ext_edge].reshape((ext_edge.size, 1))
        cond = np.allclose((edge_elevs >= old_neighbor_elevs),
                           (edge_elevs >= new_neighbor_elevs))
        # if True, we're good, the tilting didn't mess with the fr
        return not cond
Ejemplo n.º 42
0
    def __init__(self,
                 grid,
                 routing="D8",
                 apply_slope=False,
                 fill_slope=1.0e-5):
        """
        Parameters
        ----------
        grid : ModelGrid
            A landlab grid.
        routing : {'D8', 'D4'} (optional)
            If grid is a raster type, controls whether fill connectivity can
            occur on diagonals ('D8', default), or only orthogonally ('D4').
            Has no effect if grid is not a raster.
        apply_slope : bool
            If False (default), leave the top of the filled sink flat. If True,
            apply the slope fill_slope to the top surface to allow subsequent flow
            routing. A test is performed to ensure applying this slope will not
            alter the drainage structure at the edge of the filled region
            (i.e., that we are not accidentally reversing the flow direction
            far from the outlet.)
        fill_slope : float (m/m)
            The slope added to the top surface of filled pits to allow flow
            routing across them, if apply_slope.
        """
        super().__init__(grid)

        if "flow__receiver_node" in grid.at_node:
            if grid.at_node["flow__receiver_node"].size != grid.size("node"):
                msg = (
                    "A route-to-multiple flow director has been "
                    "run on this grid. The landlab development team has not "
                    "verified that SinkFiller is compatible with "
                    "route-to-multiple methods. Please open a GitHub Issue "
                    "to start this process.")
                raise NotImplementedError(msg)

        if routing != "D8":
            assert routing == "D4"
        self._routing = routing
        if isinstance(self._grid, RasterModelGrid) and (routing == "D8"):
            self._D8 = True
            self._num_nbrs = 8
        else:
            self._D8 = False  # useful shorthand for thia test we do a lot
            if isinstance(self._grid, RasterModelGrid):
                self._num_nbrs = 4
        self._fill_slope = fill_slope
        self._apply_slope = apply_slope

        self._elev = self._grid.at_node["topographic__elevation"]
        self._topo_field_name = "topographic__elevation"

        # create the only new output field:
        self._sed_fill_depth = self._grid.add_zeros("node",
                                                    "sediment_fill__depth",
                                                    clobber=True)

        self._lf = DepressionFinderAndRouter(self._grid,
                                             routing=self._routing,
                                             reroute_flow=True)
        self._fr = FlowAccumulator(self._grid, flow_director=self._routing)
Ejemplo n.º 43
0
    def __init__(self,
                 input_file=None,
                 params=None,
                 BaselevelHandlerClass=None):
        """Initialize the BasicSa."""

        # Call ErosionModel's init
        super(BasicSa,
              self).__init__(input_file=input_file,
                             params=params,
                             BaselevelHandlerClass=BaselevelHandlerClass)

        # Get Parameters and convert units if necessary:
        self.K_sp = self.get_parameter_from_exponent('K_sp')
        linear_diffusivity = (
            self._length_factor**2.) * self.get_parameter_from_exponent(
                'linear_diffusivity')  # has units length^2/time
        try:
            initial_soil_thickness = (self._length_factor) * self.params[
                'initial_soil_thickness']  # has units length
        except KeyError:
            initial_soil_thickness = 1.0  # default value
        soil_transport_decay_depth = (self._length_factor) * self.params[
            'soil_transport_decay_depth']  # has units length
        max_soil_production_rate = (self._length_factor) * self.params[
            'max_soil_production_rate']  # has units length per time
        soil_production_decay_depth = (self._length_factor) * self.params[
            'soil_production_decay_depth']  # has units length

        # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method
        self.flow_router = FlowAccumulator(
            self.grid,
            flow_director='D8',
            depression_finder=DepressionFinderAndRouter)

        # Instantiate a FastscapeEroder component
        self.eroder = FastscapeEroder(self.grid,
                                      K_sp=self.K_sp,
                                      m_sp=self.params['m_sp'],
                                      n_sp=self.params['n_sp'])

        # Create soil thickness (a.k.a. depth) field
        if 'soil__depth' in self.grid.at_node:
            soil_thickness = self.grid.at_node['soil__depth']
        else:
            soil_thickness = self.grid.add_zeros('node', 'soil__depth')

        # Create bedrock elevation field
        if 'bedrock__elevation' in self.grid.at_node:
            bedrock_elev = self.grid.at_node['bedrock__elevation']
        else:
            bedrock_elev = self.grid.add_zeros('node', 'bedrock__elevation')

        # Set soil thickness and bedrock elevation
        soil_thickness[:] = initial_soil_thickness
        bedrock_elev[:] = self.z - initial_soil_thickness

        # Instantiate diffusion and weathering components
        self.diffuser = DepthDependentDiffuser(
            self.grid,
            linear_diffusivity=linear_diffusivity,
            soil_transport_decay_depth=soil_transport_decay_depth)

        self.weatherer = ExponentialWeatherer(
            self.grid,
            max_soil_production_rate=max_soil_production_rate,
            soil_production_decay_depth=soil_production_decay_depth)
Ejemplo n.º 44
0
rmg.set_nodata_nodes_to_closed(rmg.at_node['topographic__elevation'], -9999)

imshow_grid(rmg, 'topographic__elevation')

sfb = SinkFillerBarnes(rmg, fill_flat=False)

sfb.run_one_step()

# Set watershed boundary condition - use lowest elevation if basic call fails

rmg.set_watershed_boundary_condition(z)

fa = FlowAccumulator(
    rmg,
    #                      surface='topographic__elevation',
    flow_director='FlowDirectorD8',
    #                      runoff_rate=None,
    #                      depression_finder='DepressionFinderAndRouter',
    #                      routing='D4'
)

fa.run_one_step()
(da, q) = fa.accumulate_flow()

outlet_node_to_sample = np.argmax(rmg.at_node['drainage_area'])
print('Outlet Node = ' + str(outlet_node_to_sample) + '; Drainage Area= ' +
      str(da[outlet_node_to_sample] / 1000000) + ' km^2; Elev = ' +
      str(round(z[outlet_node_to_sample], 1)) + ' m')

# For each of the links, there is a tail and head and node.
# Look at tail nodes of all links, and look at node area, find link that connects
Ejemplo n.º 45
0
def test_degenerate_drainage():
    """
    This "hourglass" configuration should be one of the hardest to correctly
    re-route.
    """
    mg = RasterModelGrid((9, 5))
    z_init = mg.node_x.copy() * 0.0001 + 1.0
    lake_pits = np.array([7, 11, 12, 13, 17, 27, 31, 32, 33, 37])
    z_init[lake_pits] = -1.0
    z_init[22] = 0.0  # the common spill pt for both lakes
    z_init[21] = 0.1  # an adverse bump in the spillway
    z_init[20] = -0.2  # the spillway
    mg.add_field("node", "topographic__elevation", z_init)

    fr = FlowAccumulator(mg, flow_director="D8")
    lf = DepressionFinderAndRouter(mg)
    fr.run_one_step()
    lf.map_depressions()

    #    correct_A = np.array([ 0.,   0.,   0.,   0.,   0.,
    #                           0.,   1.,   3.,   1.,   0.,
    #                           0.,   5.,   1.,   2.,   0.,
    #                           0.,   1.,  10.,   1.,   0.,
    #                          21.,  21.,   1.,   1.,   0.,
    #                           0.,   1.,   9.,   1.,   0.,
    #                           0.,   3.,   1.,   2.,   0.,
    #                           0.,   1.,   1.,   1.,   0.,
    #                           0.,   0.,   0.,   0.,   0.])

    correct_A = np.array([
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        1.0,
        3.0,
        1.0,
        0.0,
        0.0,
        2.0,
        4.0,
        2.0,
        0.0,
        0.0,
        1.0,
        10.0,
        1.0,
        0.0,
        21.0,
        21.0,
        1.0,
        1.0,
        0.0,
        0.0,
        1.0,
        9.0,
        1.0,
        0.0,
        0.0,
        2.0,
        2.0,
        2.0,
        0.0,
        0.0,
        1.0,
        1.0,
        1.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
    ])

    assert mg.at_node["drainage_area"] == approx(correct_A)
Ejemplo n.º 46
0
def test_flow__distance_regular_grid_d8():
    """Test to demonstrate that flow__distance utility works as expected with
    regular grids"""

    # instantiate a model grid

    mg = RasterModelGrid((5, 4), xy_spacing=(1, 1))

    # instantiate an elevation array

    z = np.array(
        [[0, 0, 0, 0], [0, 21, 10, 0], [0, 31, 20, 0], [0, 32, 30, 0],
         [0, 0, 0, 0]],
        dtype="float64",
    )

    # add the elevation field to the grid

    mg.add_field("node", "topographic__elevation", z)

    # instantiate the expected flow__distance array
    # considering flow directions calculated with D8 algorithm

    flow__distance_expected = np.array(
        [
            [0, 0, 0, 0],
            [0, 1, 0, 0],
            [0, math.sqrt(2), 1, 0],
            [0, 1 + math.sqrt(2), 2, 0],
            [0, 0, 0, 0],
        ],
        dtype="float64",
    )
    flow__distance_expected = np.reshape(
        flow__distance_expected,
        mg.number_of_node_rows * mg.number_of_node_columns)

    # setting boundary conditions

    mg.set_closed_boundaries_at_grid_edges(
        bottom_is_closed=True,
        left_is_closed=True,
        right_is_closed=True,
        top_is_closed=True,
    )

    # calculating flow directions with FlowAccumulator component

    fr = FlowAccumulator(mg, flow_director="D8")
    fr.run_one_step()

    # calculating flow distance map

    flow__distance = calculate_flow__distance(mg,
                                              add_to_grid=True,
                                              noclobber=False)
    flow__distance = np.reshape(
        flow__distance, mg.number_of_node_rows * mg.number_of_node_columns)

    # modifying the flow distance map because boundary and outlet nodes should
    # not have flow__distance value different from 0

    flow__distance[mg.boundary_nodes] = 0
    outlet_id = 6
    flow__distance[outlet_id] = 0

    # test that the flow distance utility works as expected

    assert_array_equal(flow__distance_expected, flow__distance)
Ejemplo n.º 47
0
def test_composite_pits():
    """
    A test to ensure the component correctly handles cases where there are
    multiple pits, inset into each other.
    """
    mg = RasterModelGrid((10, 10))
    z = mg.add_field("node", "topographic__elevation", mg.node_x.copy())
    # a sloping plane
    # np.random.seed(seed=0)
    # z += np.random.rand(100)/10000.
    # punch one big hole
    z.reshape((10, 10))[3:8, 3:8] = 0.0
    # dig a couple of inset holes
    z[57] = -1.0
    z[44] = -2.0
    z[54] = -10.0

    # make an outlet
    z[71] = 0.9

    fr = FlowAccumulator(mg, flow_director="D8")
    lf = DepressionFinderAndRouter(mg)
    fr.run_one_step()
    lf.map_depressions()

    flow_sinks_target = np.zeros(100, dtype=bool)
    flow_sinks_target[mg.boundary_nodes] = True
    # no internal sinks now:
    assert_array_equal(mg.at_node["flow__sink_flag"], flow_sinks_target)

    # test conservation of mass:
    assert mg.at_node["drainage_area"].reshape(
        (10, 10))[1:-1, 1].sum() == approx(8.0**2)
    # ^all the core nodes

    # test the actual flow field:
    #    nA = np.array([  0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
    #                     8.,   8.,   7.,   6.,   5.,   4.,   3.,   2.,   1.,   0.,
    #                     1.,   1.,   1.,   1.,   1.,   1.,   1.,   1.,   1.,   0.,
    #                     1.,   1.,   1.,   4.,   2.,   2.,   8.,   4.,   1.,   0.,
    #                     1.,   1.,   1.,   8.,   3.,  15.,   3.,   2.,   1.,   0.,
    #                     1.,   1.,   1.,  13.,  25.,   6.,   3.,   2.,   1.,   0.,
    #                     1.,   1.,   1.,  45.,   3.,   3.,   5.,   2.,   1.,   0.,
    #                    50.,  50.,  49.,   3.,   2.,   2.,   2.,   4.,   1.,   0.,
    #                     1.,   1.,   1.,   1.,   1.,   1.,   1.,   1.,   1.,   0.,
    #                     0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.])
    nA = np.array([
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        8.0,
        8.0,
        7.0,
        6.0,
        5.0,
        4.0,
        3.0,
        2.0,
        1.0,
        0.0,
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
        0.0,
        1.0,
        1.0,
        1.0,
        4.0,
        2.0,
        2.0,
        6.0,
        4.0,
        1.0,
        0.0,
        1.0,
        1.0,
        1.0,
        6.0,
        3.0,
        12.0,
        3.0,
        2.0,
        1.0,
        0.0,
        1.0,
        1.0,
        1.0,
        8.0,
        20.0,
        4.0,
        3.0,
        2.0,
        1.0,
        0.0,
        1.0,
        1.0,
        1.0,
        35.0,
        5.0,
        4.0,
        3.0,
        2.0,
        1.0,
        0.0,
        50.0,
        50.0,
        49.0,
        13.0,
        10.0,
        8.0,
        6.0,
        4.0,
        1.0,
        0.0,
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
    ])
    assert_array_equal(mg.at_node["drainage_area"], nA)

    # the lake code map:
    lc = np.array([
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        57,
        57,
        57,
        57,
        57,
        XX,
        XX,
        XX,
        XX,
        XX,
        57,
        57,
        57,
        57,
        57,
        XX,
        XX,
        XX,
        XX,
        XX,
        57,
        57,
        57,
        57,
        57,
        XX,
        XX,
        XX,
        XX,
        XX,
        57,
        57,
        57,
        57,
        57,
        XX,
        XX,
        XX,
        XX,
        XX,
        57,
        57,
        57,
        57,
        57,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
    ])

    # test the remaining properties:
    assert lf.lake_outlets.size == 1
    assert lf.lake_outlets[0] == 72
    outlets_in_map = np.unique(lf.depression_outlet_map)
    assert outlets_in_map.size == 2
    assert outlets_in_map[1] == 72
    assert lf.number_of_lakes == 1
    assert lf.lake_codes[0] == 57
    assert_array_equal(lf.lake_map, lc)
    assert lf.lake_areas[0] == approx(25.0)
    assert lf.lake_volumes[0] == approx(63.0)
Ejemplo n.º 48
0
init_elev = inputs.read_float('init_elev')

mg = RasterModelGrid(nrows, ncols, dx)

#create the fields in the grid
mg.add_zeros('topographic__elevation', at='node')
z = mg.zeros(at='node') + init_elev
mg['node']['topographic__elevation'] = z + numpy.random.rand(len(z)) / 1000.

#make some K values in a field to test
mg.at_node['K_values'] = 0.1 + numpy.random.rand(nrows * ncols) / 10.

print('Running ...')

#instantiate the components:
fr = FlowAccumulator(mg, flow_director='D8')
sp = StreamPowerEroder(mg, './drive_sp_params.txt')
#load the Fastscape module too, to allow direct comparison
fsp = FastscapeEroder(mg, './drive_sp_params.txt')

#perform the loop:
elapsed_time = 0.  #total time in simulation
while elapsed_time < time_to_run:
    print(elapsed_time)
    if elapsed_time + dt > time_to_run:
        print("Short step!")
        dt = time_to_run - elapsed_time
    mg = fr.run_one_step()
    #print 'Area: ', numpy.max(mg.at_node['drainage_area'])
    #mg = fsp.erode(mg)
    mg = fsp.erode(mg, K_if_used='K_values')
Ejemplo n.º 49
0
import numpy as np
from matplotlib.pyplot import figure, show
from six.moves import range

from landlab import VoronoiDelaunayGrid
from landlab.components import FlowAccumulator, StreamPowerEroder
from landlab.plot.imshow import imshow_node_grid

nnodes = 10000

x, y = np.random.rand(nnodes), np.random.rand(nnodes)
mg = VoronoiDelaunayGrid(x, y)

z = mg.add_field(
    "node", "topographic__elevation", np.random.rand(nnodes) / 10000., copy=False
)

fr = FlowAccumulator(mg)
spe = StreamPowerEroder(mg, "drive_sp_params_voronoi.txt")

for i in range(100):
    z[mg.core_nodes] += 0.01
    fr.run_one_step()
    spe.erode(mg, 1.)

imshow_node_grid(mg, "topographic__elevation")

show()
Ejemplo n.º 50
0
class BasicChSa(ErosionModel):
    """
    A BasicChSa model computes erosion using depth-dependent cubic diffusion
    with a soil layer, basic stream power, and Q~A.
    """

    def __init__(self, input_file=None, params=None,
                 BaselevelHandlerClass=None):
        """Initialize the BasicChSa model."""

        # Call ErosionModel's init
        super(BasicChSa, self).__init__(input_file=input_file,
                                        params=params,
                                        BaselevelHandlerClass=BaselevelHandlerClass)

        self.K_sp = self.get_parameter_from_exponent('K_sp')
        linear_diffusivity = (self._length_factor**2.)*self.get_parameter_from_exponent('linear_diffusivity') # has units length^2/time
        try:
            initial_soil_thickness = (self._length_factor)*self.params['initial_soil_thickness'] # has units length
        except KeyError:
            initial_soil_thickness = 1.0  # default value
        soil_transport_decay_depth = (self._length_factor)*self.params['soil_transport_decay_depth']  # has units length
        max_soil_production_rate = (self._length_factor)*self.params['max_soil_production_rate'] # has units length per time
        soil_production_decay_depth = (self._length_factor)*self.params['soil_production_decay_depth']   # has units length

        # Create soil thickness (a.k.a. depth) field
        if 'soil__depth' in self.grid.at_node:
            soil_thickness = self.grid.at_node['soil__depth']
        else:
            soil_thickness = self.grid.add_zeros('node', 'soil__depth')

        # Create bedrock elevation field
        if 'bedrock__elevation' in self.grid.at_node:
            bedrock_elev = self.grid.at_node['bedrock__elevation']
        else:
            bedrock_elev = self.grid.add_zeros('node', 'bedrock__elevation')

        soil_thickness[:] = initial_soil_thickness
        bedrock_elev[:] = self.z - initial_soil_thickness

        # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method
        self.flow_router = FlowAccumulator(self.grid,
                                           flow_director='D8',
                                           depression_finder = DepressionFinderAndRouter)

        # Instantiate a FastscapeEroder component
        self.eroder = FastscapeEroder(self.grid,
                                      K_sp=self.K_sp,
                                      m_sp=self.params['m_sp'],
                                      n_sp=self.params['n_sp'])

        # Instantiate a weathering component
        self.weatherer = ExponentialWeatherer(self.grid,
                                              max_soil_production_rate=max_soil_production_rate,
                                              soil_production_decay_depth=soil_production_decay_depth)

        # Instantiate a soil-transport component
        self.diffuser = DepthDependentTaylorDiffuser(self.grid,
                                                    linear_diffusivity=linear_diffusivity,
                                                    slope_crit=self.params['slope_crit'],
                                                    soil_transport_decay_depth=soil_transport_decay_depth,
                                                    nterms=11)

    def run_one_step(self, dt):
        """
        Advance model for one time-step of duration dt.
        """

        # Route flow
        self.flow_router.run_one_step()

        # Get IDs of flooded nodes, if any
        flooded = np.where(self.flow_router.depression_finder.flood_status==3)[0]

        # Do some erosion (but not on the flooded nodes)
        # (if we're varying K through time, update that first)
        if self.opt_var_precip:
            self.eroder.K = (self.K_sp
                             * self.pc.get_erodibility_adjustment_factor(self.model_time))

        self.eroder.run_one_step(dt, flooded_nodes=flooded)

        # We must also now erode the bedrock where relevant. If water erosion
        # into bedrock has occurred, the bedrock elevation will be higher than
        # the actual elevation, so we simply re-set bedrock elevation to the
        # lower of itself or the current elevation.
        b = self.grid.at_node['bedrock__elevation']
        b[:] = np.minimum(b, self.grid.at_node['topographic__elevation'])

        # Calculate regolith-production rate
        self.weatherer.calc_soil_prod_rate()

        # Do some soil creep
        self.diffuser.run_one_step(dt,
                                   dynamic_dt=True,
                                   if_unstable='raise',
                                   courant_factor=0.1)

        # calculate model time
        self.model_time += dt

        # Lower outlet
        self.update_outlet(dt)

        # Check walltime
        self.check_walltime()
Ejemplo n.º 51
0
from landlab.components import FlowAccumulator, SedDepEroder
from landlab import RasterModelGrid
import landlab
import numpy as np
from pylab import show, figure
from landlab.io import read_esri_ascii


(mg, z) = read_esri_ascii("bacia_piratini_90m.asc", name="topographic__elevation")

fda = FlowAccumulator(mg, 'topographic__elevation')
sde1 = SedDepEroder(mg, Qc="MPM")

fda.run_one_step()
sde1.run_one_step(dt=30) #dt em anos

print(sde1.characteristic_grainsize)
Ejemplo n.º 52
0
def test_MassBalance():
    # %%
    # set up a 15x15 grid with one open outlet node and low initial elevations.
    nr = 15
    nc = 15
    mg = RasterModelGrid((nr, nc), xy_spacing=10.0)

    z = mg.add_zeros("topographic__elevation", at="node")
    br = mg.add_zeros("bedrock__elevation", at="node")
    soil = mg.add_zeros("soil__depth", at="node")

    mg["node"]["topographic__elevation"] += (
        mg.node_y / 100000 + mg.node_x / 100000 +
        np.random.rand(len(mg.node_y)) / 10000)
    mg.set_closed_boundaries_at_grid_edges(
        bottom_is_closed=True,
        left_is_closed=True,
        right_is_closed=True,
        top_is_closed=True,
    )
    mg.set_watershed_boundary_condition_outlet_id(
        0, mg["node"]["topographic__elevation"], -9999.0)
    soil[:] += 0.0  # initial condition of no soil depth.
    br[:] = z[:]
    z[:] += soil[:]

    # Create a D8 flow handler
    # fa = PriorityFloodFlowRouter(mg, surface="topographic__elevation", flow_metric = 'D8',suppress_out=True)
    # fa.run_one_step()

    # Create a D8 flow handler
    fa = FlowAccumulator(mg,
                         flow_director="D8",
                         depression_finder="DepressionFinderAndRouter")

    # Parameter values for detachment-limited test
    K_br = 0.002
    K_sed = 0.002
    U = 0.0001
    dt = 10.0
    F_f = 0.2  # all detached rock disappears; detachment-ltd end-member
    m_sp = 0.5
    n_sp = 1.0
    v_s = 0.25
    H_star = 0.1

    # Instantiate the Space component...
    sp = SpaceLargeScaleEroder(
        mg,
        K_sed=K_sed,
        K_br=K_br,
        F_f=F_f,
        phi=0.0,
        H_star=H_star,
        v_s=v_s,
        m_sp=m_sp,
        n_sp=n_sp,
        sp_crit_sed=0,
        sp_crit_br=0,
    )
    # Get values before run
    z = mg.at_node["topographic__elevation"]
    br = mg.at_node["bedrock__elevation"]
    H = mg.at_node["soil__depth"]
    cores = mg.core_nodes
    area = mg.cell_area_at_node
    # ... and run it to steady state (10000x1-year timesteps).
    for _ in range(10000):
        fa.run_one_step()
        soil_B = cp.deepcopy(H)
        bed_B = cp.deepcopy(br)
        vol_SSY_riv, V_leaving_riv = sp.run_one_step(dt=dt)
        diff_MB = (np.sum((bed_B[cores] - br[cores]) * area[cores]) + np.sum(
            (soil_B[cores] - H[cores]) * area[cores]) * (1 - sp._phi) -
                   vol_SSY_riv * dt - V_leaving_riv)

        br[mg.core_nodes] += U * dt  # m
        soil[
            0] = 0.0  # enforce 0 soil depth at boundary to keep lowering steady
        z[:] = br[:] + soil[:]

        # Test Every iteration
        testing.assert_array_almost_equal(
            z[cores],
            br[cores] + H[cores],
            decimal=5,
            err_msg=
            "Topography does not equal sum of bedrock and soil! Decrease timestep",
            verbose=True,
        )
        testing.assert_array_less(
            abs(diff_MB),
            1e-8 * mg.number_of_nodes,
            err_msg=
            "Mass balance error SpaceLargeScaleEroder! Try to resolve by becreasing timestep",
            verbose=True,
        )
Ejemplo n.º 53
0
class BasicDd(ErosionModel):
    """
    A BasicDd computes erosion using linear diffusion, stream
    power with a smoothed threshold that is proportional to
    depth, and Q~A.
    """
    def __init__(self,
                 input_file=None,
                 params=None,
                 BaselevelHandlerClass=None):
        """Initialize the BasicDd."""

        # Call ErosionModel's init
        super(BasicDd,
              self).__init__(input_file=input_file,
                             params=params,
                             BaselevelHandlerClass=BaselevelHandlerClass)

        # Get Parameters and convert units if necessary:
        K_sp = self.get_parameter_from_exponent('K_sp', raise_error=False)
        K_ss = self.get_parameter_from_exponent('K_ss', raise_error=False)
        linear_diffusivity = (
            self._length_factor**2.) * self.get_parameter_from_exponent(
                'linear_diffusivity')  # has units length^2/time

        #  threshold has units of  Length per Time which is what
        # StreamPowerSmoothThresholdEroder expects
        self.threshold_value = self._length_factor * self.get_parameter_from_exponent(
            'erosion__threshold')  # has units length/time

        # check that a stream power and a shear stress parameter have not both been given
        if K_sp != None and K_ss != None:
            raise ValueError('A parameter for both K_sp and K_ss has been'
                             'provided. Only one of these may be provided')
        elif K_sp != None or K_ss != None:
            if K_sp != None:
                self.K = K_sp
            else:
                self.K = (self._length_factor**(
                    1. / 3.)) * K_ss  # K_ss has units Lengtg^(1/3) per Time
        else:
            raise ValueError('A value for K_sp or K_ss  must be provided.')

        # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method
        self.flow_router = FlowAccumulator(
            self.grid,
            flow_director='D8',
            depression_finder=DepressionFinderAndRouter)

        # Create a field for the (initial) erosion threshold
        self.threshold = self.grid.add_zeros('node', 'erosion__threshold')
        self.threshold[:] = self.threshold_value

        # Instantiate a FastscapeEroder component
        self.eroder = StreamPowerSmoothThresholdEroder(
            self.grid,
            m_sp=self.params['m_sp'],
            n_sp=self.params['n_sp'],
            K_sp=self.K,
            threshold_sp=self.threshold)

        # Get the parameter for rate of threshold increase with erosion depth
        self.thresh_change_per_depth = self.params['thresh_change_per_depth']

        # Instantiate a LinearDiffuser component
        self.diffuser = LinearDiffuser(self.grid,
                                       linear_diffusivity=linear_diffusivity)

    def update_erosion_threshold_values(self):
        """Updates the erosion threshold at each node based on cumulative
        erosion so far."""

        # Set the erosion threshold.
        #
        # Note that a minus sign is used because cum ero depth is negative for
        # erosion, positive for deposition.
        # The second line handles the case where there is growth, in which case
        # we want the threshold to stay at its initial value rather than
        # getting smaller.
        cum_ero = self.grid.at_node['cumulative_erosion__depth']
        cum_ero[:] = (self.z -
                      self.grid.at_node['initial_topographic__elevation'])
        self.threshold[:] = (self.threshold_value -
                             (self.thresh_change_per_depth * cum_ero))
        self.threshold[self.threshold < self.threshold_value] = \
            self.threshold_value

    def run_one_step(self, dt):
        """
        Advance model for one time-step of duration dt.
        """

        # Route flow
        self.flow_router.run_one_step()

        # Get IDs of flooded nodes, if any
        flooded = np.where(
            self.flow_router.depression_finder.flood_status == 3)[0]

        # Calculate the new threshold values given cumulative erosion
        self.update_erosion_threshold_values()

        # Do some erosion (but not on the flooded nodes)
        # (if we're varying K through time, update that first)
        if self.opt_var_precip:
            self.eroder.K = (
                self.K *
                self.pc.get_erodibility_adjustment_factor(self.model_time))
        self.eroder.run_one_step(dt, flooded_nodes=flooded)

        # Do some soil creep
        self.diffuser.run_one_step(dt)

        # calculate model time
        self.model_time += dt

        # Lower outlet
        self.update_outlet(dt)

        # Check walltime
        self.check_walltime()
Ejemplo n.º 54
0
def test_soil_field_already_on_grid():
    # %%
    """
    Test that an existing soil grid field is not changed by instantiating
    SpaceLargeScaleEroder.
    """

    # set up a 5x5 grid with one open outlet node and low initial elevations.
    nr = 5
    nc = 5
    mg = RasterModelGrid((nr, nc), xy_spacing=10.0)

    z = mg.add_zeros("topographic__elevation", at="node")
    br = mg.add_zeros("bedrock__elevation", at="node")
    soil = mg.add_zeros("soil__depth", at="node")
    soil += 1.0  # add 1m of soil everywehre

    mg["node"]["topographic__elevation"] += (
        mg.node_y / 10000 + mg.node_x / 10000 +
        np.random.rand(len(mg.node_y)) / 10000)
    mg.set_closed_boundaries_at_grid_edges(
        bottom_is_closed=True,
        left_is_closed=True,
        right_is_closed=True,
        top_is_closed=True,
    )
    mg.set_watershed_boundary_condition_outlet_id(
        0, mg["node"]["topographic__elevation"], -9999.0)
    br[:] = z - soil

    # Create a D8 flow handler
    FlowAccumulator(mg, flow_director="D8")

    # Instantiate SpaceLargeScaleEroder
    sp = SpaceLargeScaleEroder(
        mg,
        K_sed=0.01,
        K_br=0.01,
        F_f=0.0,
        phi=0.0,
        v_s=0.001,
        m_sp=0.5,
        n_sp=1.0,
        sp_crit_sed=0,
        sp_crit_br=0,
    )

    # ensure that 'soil__depth' field is everywhere equal to 1.0 m.
    testing.assert_array_equal(
        np.ones(mg.number_of_nodes),
        sp._soil__depth,
        err_msg="SpaceLargeScaleEroder soil depth field test failed",
        verbose=True,
    )

    # %% Check getters
    testing.assert_array_equal(
        0.01,
        sp.K_br,
        err_msg="Parameter value issue",
        verbose=True,
    )
    testing.assert_array_equal(
        0.01,
        sp.K_sed,
        err_msg="Parameter value issue",
        verbose=True,
    )
    # sediment erosion is zero before running the component
    testing.assert_array_equal(
        np.zeros(mg.number_of_nodes),
        sp.Es,
        err_msg="Parameter value issue",
        verbose=True,
    )
    # rock erosion is zero before running the component
    testing.assert_array_equal(
        np.zeros(mg.number_of_nodes),
        sp.Er,
        err_msg="Parameter value issue",
        verbose=True,
    )
    # %% Check setters
    sp.K_br = 0.02
    testing.assert_array_equal(
        0.02,
        sp.K_br,
        err_msg="Parameter value issue",
        verbose=True,
    )
    sp.K_sed = 0.02
    testing.assert_array_equal(
        0.02,
        sp.K_sed,
        err_msg="Parameter value issue",
        verbose=True,
    )

    with pytest.raises(AttributeError):
        sp.Es = np.zeros(mg.number_of_nodes)

    with pytest.raises(AttributeError):
        sp.Er = np.zeros(mg.number_of_nodes)
Ejemplo n.º 55
0
class KinwaveImplicitOverlandFlow(Component):
    """
    Calculate shallow water flow over topography.

    Landlab component that implements a two-dimensional kinematic wave model.
    This is a form of the 2D shallow-water equations in which energy slope is
    assumed to equal bed slope. The solution method is locally implicit, and
    works as follows. At each time step, we iterate from upstream to downstream
    over the topography. Because we are working downstream, we can assume that
    we know the total water inflow to a given cell. We solve the following mass 
    conservation equation at each cell:
        
        $(H^{t+1} - H^t)/\Delta t = Q_{in}/A - Q_{out}/A + R$
        
    where $H$ is water depth, $t$ indicates time step number, $\Delta t$ is
    time step duration, $Q_{in}$ is total inflow discharge, $Q_{out}$ is total
    outflow discharge, $A$ is cell area, and $R$ is local runoff rate 
    (precipitation minus infiltration; could be negative if runon infiltration
    is occurring).
    
    The specific outflow discharge leaving a cell along one of its faces is:
        
        $q = (1/C_r) H^\alpha S^{1/2}$
    
    where $C_r$ is a roughness coefficient (such as Manning's n), $\alpha$ is
    an exponent equal to 5/3 for the Manning equation and 3/2 for the Chezy
    family, and $S$ is the downhill-positive gradient of the link that crosses
    this particular face. Outflow discharge is zero for links that are flat or
    "uphill" from the given node. Total discharge out of a cell is then the
    sum of (specific discharge x face width) over all outflow faces
        
        $Q_{out} = \sum_{i=1}^N (1/C_r) H^\alpha S_i^{1/2} W_i$
        
    where $N$ is the number of outflow faces (i.e., faces where the ground
    slopes downhill away from the cell's node), and $W_i$ is the width of face
    $i$.
    
    We use the depth at the cell's node, so this simplifies to:

        $Q_{out} = (1/C_r) H'^\alpha \sum_{i=1}^N S_i^{1/2} W_i$
        
    We define $H$ in the above as a weighted sum of the "old" (time step $t$)
    and "new" (time step $t+1$) depth values:
        
        $H' = w H^{t+1} + (1-w) H^t$
    
    If $w=1$, the method is fully implicit. If $w=0$, it is a simple forward
    explicit method.
    
    When we combine these equations, we have an equation that includes the
    unknown $H^{t+1}$ and a bunch of terms that are known. If $w\ne 0$, it is
    a nonlinear equation in $H^{t+1}$, and must be solved iteratively. We do
    this using a root-finding method in the scipy.optimize library.

    Construction:

        KinwaveImplicitOverlandFlow(grid, precip_rate=1.0,
                                    precip_duration=1.0,
                                    infilt_rate=0.0,
                                    roughness=0.01, **kwds)

    Parameters
    ----------
    grid : ModelGrid
        A Landlab grid object.
    precip_rate : float, optional (defaults to 1 mm/hr)
        Precipitation rate, mm/hr
    precip_duration : float, optional (defaults to 1 hour)
        Duration of precipitation, hours
    infilt_rate : float, optional (defaults to 0)
        Maximum rate of infiltration, mm/hr
    roughnes : float, defaults to 0.01
        Manning roughness coefficient, s/m^1/3

    Examples
    --------
    >>> from landlab import RasterModelGrid
    >>> rg = RasterModelGrid((4, 5), 10.0)
    >>> z = rg.add_zeros('node', 'topographic__elevation')
    >>> kw = KinwaveImplicitOverlandFlow(rg)
    >>> round(kw.runoff_rate * 1.0e7, 2)
    2.78
    >>> kw.vel_coef  # default value
    100.0
    >>> rg.at_node['surface_water__depth'][6:9]
    array([ 0.,  0.,  0.])
    """

    _name = 'KinwaveImplicitOverlandFlow'

    _input_var_names = (
        'topographic__elevation',
    )

    _output_var_names = (
        'topographic__gradient',
        'surface_water__depth',
        #'water__velocity',
        #'water__specific_discharge',
        'surface_water_inflow__discharge',
    )

    _var_units = {
        'topographic__elevation': 'm',
        'topographic__slope': 'm/m',
        'surface_water__depth': 'm',
        'water__velocity': 'm/s',
        'water__specific_discharge': 'm2/s',
    }

    _var_mapping = {
        'topographic__elevation': 'node',
        'topographic__gradient': 'link',
        'surface_water__depth': 'node',
        #'water__velocity': 'link',
        #'water__specific_discharge': 'link',
        'surface_water_inflow__discharge' : 'node',
    }

    _var_doc = {
        'topographic__elevation':
            'elevation of the ground surface relative to some datum',
        'topographic__gradient':
            'gradient of the ground surface',
        'surface_water__depth':
            'depth of water',
#        'water__velocity':
#            'flow velocity component in the direction of the link',
#        'water__specific_discharge':
#            'flow discharge component in the direction of the link',
        'surface_water_inflow__discharge':
            'water volume inflow rate to the cell around each node'
    }

    def __init__(self, grid, runoff_rate=1.0, roughness=0.01,
                 changing_topo=False, depth_exp=1.5, weight=1.0, **kwds):
        """Initialize the KinwaveOverlandFlowModel.

        Parameters
        ----------
        grid : ModelGrid
            Landlab ModelGrid object
        runoff_rate : float, optional (defaults to 1 mm/hr)
            Precipitation rate, mm/hr
        roughnes : float, defaults to 0.01
            Manning roughness coefficient, s/m^1/3
        changing_topo : boolean, optional (defaults to False)
            Flag indicating whether topography changes between time steps
        depth_exp : float (defaults to 1.5)
            Exponent on water depth in velocity equation (3/2 for Darcy/Chezy,
            5/3 for Manning)
        weight : float (defaults to 1.0)
            Weighting on depth at new time step versus old time step (1 = all
            implicit; 0 = explicit)
        """

        # Store grid and parameters and do unit conversion
        self._grid = grid
        self.runoff_rate = runoff_rate / 3600000.0  # convert to m/s
        self.vel_coef = 1.0 / roughness  # do division now to save time
        self.changing_topo = changing_topo
        self.depth_exp = depth_exp
        self.weight = weight

        # Get elevation field
        try:
            self.elev = grid.at_node['topographic__elevation']
        except:
            raise

        # Create fields...
        #  Water depth
        if 'surface_water__depth' in grid.at_node:
            self.depth = grid.at_node['surface_water__depth']
        else:
            self.depth = grid.add_zeros('node', 'surface_water__depth')
        #   Slope
        if 'topographic__gradient' in grid.at_link:
            self.slope = grid.at_link['topographic__gradient']
        else:
            self.slope = grid.add_zeros('link', 'topographic__gradient')
        #  Velocity
#        if 'water__velocity' in grid.at_link:
#            self.vel = grid.at_link['water__velocity']
#        else:
#            self.vel = grid.add_zeros('link', 'water__velocity')
        #  Discharge
#        if 'surface_water__specific_discharge' in grid.at_link:
#            self.disch = grid.at_link['surface_water__specific_discharge']
#        else:
#            self.disch = grid.add_zeros('link',
#                                        'surface_water__specific_discharge')
        #  Inflow discharge at nodes
        if 'surface_water_inflow__discharge' in grid.at_node:
            self.disch_in = grid.at_node['surface_water_inflow__discharge']
        else:
            self.disch_in = grid.add_zeros('node',
                                           'surface_water_inflow__discharge')

        # This array holds, for each node, the sum of sqrt(slope) x face width
        # for each link/face.
        self.grad_width_sum = grid.zeros('node')

        # This array holds the prefactor in the algebraic equation that we
        # will find a solution for.
        self.alpha = grid.zeros('node')

        # Instantiate flow router
        self.flow_accum = FlowAccumulator(grid, 'topographic__elevation',
                                    flow_director='MFD',
                                    partition_method='square_root_of_slope')

        # Flag to let us know whether this is our first iteration
        self.first_iteration = True

    def run_one_step(self, dt, current_time=0.0, runoff_rate=None, **kwds):
        """Calculate water flow for a time period `dt`.
        """
        # Handle runoff rate
        if runoff_rate is None:
            runoff_rate = self.runoff_rate

        # If it's our first iteration, or if the topography may be changing,
        # do flow routing and calculate square root of slopes at links     
        if self.changing_topo or self.first_iteration:

            # Calculate the ground-surface slope
            self.slope[self.grid.active_links] = \
                self._grid.calc_grad_at_link(self.elev)[self._grid.active_links]
                
            # Take square root of slope magnitude for use in velocity eqn
            self.sqrt_slope = np.sqrt(np.abs(self.slope))

            # Re-route flow, which gives us the downstream-to-upstream
            # ordering
            self.flow_accum.run_one_step()
            self.nodes_ordered = self.grid.at_node['flow__upstream_node_order']
            self.flow_lnks = self.grid.at_node['flow__links_to_receiver_nodes']

            # (Re)calculate, for each node, sum of sqrt(gradient) x width
            self.grad_width_sum[:] = 0.0
            for i in range(self.flow_lnks.shape[1]):
                self.grad_width_sum[:] += (self.sqrt_slope[self.flow_lnks[:,i]]
                    * self._grid.width_of_face[
                            self.grid.face_at_link[self.flow_lnks[:,i]]])

            # Calculate values of alpha, which is defined as
            #
            #   $\alpha = \frac{\Sigma W S^{1/2} \Delta t}{A C_r}$
            cores = self.grid.core_nodes
            self.alpha[cores] = (
                self.vel_coef * self.grad_width_sum[cores] * dt 
                / (self.grid.area_of_cell[self.grid.cell_at_node[cores]]))

        # Zero out inflow discharge
        self.disch_in[:] = 0.0

        # Upstream-to-downstream loop
        for i in range(len(self.nodes_ordered) - 1, -1, -1):
            n = self.nodes_ordered[i]
            if self.grid.status_at_node[n] == 0:

                # Solve for new water depth
                aa = self.alpha[n]
                cc = self.depth[n]
                ee = ((dt * runoff_rate) 
                       + (dt * self.disch_in[n] 
                          / self.grid.area_of_cell[self.grid.cell_at_node[n]]))
                self.depth[n] = newton(water_fn, self.depth[n], 
                                       args=(aa, self.weight, cc, 
                                             self.depth_exp, ee))

                # Calc outflow
                Heff = (self.weight * self.depth[n]
                        + (1.0 - self.weight) * cc)
                outflow = (self.vel_coef * (Heff ** self.depth_exp)
                           * self.grad_width_sum[n])  # this is manning/chezy/darcy

                # Send flow downstream. Here we take total inflow discharge
                # and partition it among the node's neighbors. For this, we use
                # the flow director's "proportions" array, which contains, for
                # each node, the proportion of flow that heads out toward each
                # of its N neighbors. The proportion is zero if the neighbor is
                # uphill; otherwise, it is S^1/2 / sum(S^1/2). If for example
                # we have a raster grid, there will be four neighbors and four
                # proportions, some of which may be zero and some between 0 and
                # 1.
                self.disch_in[self.grid.adjacent_nodes_at_node[n]] += (outflow
                    * self.flow_accum.flow_director.proportions[n])
Ejemplo n.º 56
0
def test_matches_detachment_solution_n_gr_1():
    # %%
    """
    Test that model matches the detachment-limited analytical solution
    for slope/area relationship at steady state: S=(U/K_br)^(1/n)*A^(-m/n).
    """

    # %% set up a 5x5 grid with one open outlet node and low initial elevations.
    nr = 5
    nc = 5
    mg = RasterModelGrid((nr, nc), xy_spacing=10.0)

    z = mg.add_zeros("topographic__elevation", at="node")
    br = mg.add_zeros("bedrock__elevation", at="node")
    soil = mg.add_zeros("soil__depth", at="node")

    mg["node"]["topographic__elevation"] += (
        mg.node_y / 10000 + mg.node_x / 10000 +
        np.random.rand(len(mg.node_y)) / 10000)
    mg.set_closed_boundaries_at_grid_edges(
        bottom_is_closed=True,
        left_is_closed=True,
        right_is_closed=True,
        top_is_closed=True,
    )
    mg.set_watershed_boundary_condition_outlet_id(
        0, mg["node"]["topographic__elevation"], -9999.0)
    br[:] = z[:] - soil[:]

    # Create a D8 flow handler
    fa = FlowAccumulator(mg, flow_director="D8")

    # Parameter values for detachment-limited test
    K_br = 0.01
    U = 0.0001
    dt = 1.0
    F_f = 1.0  # all detached rock disappears; detachment-ltd end-member
    m_sp = 0.5
    n_sp = 1.1

    # Instantiate the SpaceLargeScaleEroder component...
    sp = SpaceLargeScaleEroder(
        mg,
        K_sed=0.00001,
        K_br=K_br,
        F_f=F_f,
        phi=0.1,
        H_star=1.0,
        v_s=0.001,
        m_sp=m_sp,
        n_sp=n_sp,
        sp_crit_sed=0,
        sp_crit_br=0,
    )

    # ... and run it to steady state (2000x1-year timesteps).
    for _ in range(4000):
        fa.run_one_step()
        sp.run_one_step(dt=dt)
        z[mg.core_nodes] += U * dt  # m
        br[mg.core_nodes] = z[mg.core_nodes] - soil[mg.core_nodes]

    # compare numerical and analytical slope solutions
    num_slope = mg.at_node["topographic__steepest_slope"][mg.core_nodes]
    analytical_slope = np.power(U / K_br, 1.0 / n_sp) * np.power(
        mg.at_node["drainage_area"][mg.core_nodes], -m_sp / n_sp)

    # test for match with analytical slope-area relationship
    testing.assert_array_almost_equal(
        num_slope,
        analytical_slope,
        decimal=8,
        err_msg="SpaceLargeScaleEroder detachment-limited test failed",
        verbose=True,
    )
Ejemplo n.º 57
0
def test_matches_bedrock_alluvial_solution():
    # %%
    """
    Test that model matches the bedrock-alluvial analytical solution
    for slope/area relationship at steady state:
    S=((U * v_s * (1 - F_f)) / (K_sed * A^m) + U / (K_br * A^m))^(1/n).

    Also test that the soil depth everywhere matches the bedrock-alluvial
    analytical solution at steady state:
    H = -H_star * ln(1 - (v_s / (K_sed / (K_br * (1 - F_f)) + v_s))).
    """

    # set up a 5x5 grid with one open outlet node and low initial elevations.
    nr = 5
    nc = 5
    mg = RasterModelGrid((nr, nc), xy_spacing=10.0)

    z = mg.add_zeros("topographic__elevation", at="node")
    br = mg.add_zeros("bedrock__elevation", at="node")
    soil = mg.add_zeros("soil__depth", at="node")

    mg["node"]["topographic__elevation"] += (
        mg.node_y / 100000 + mg.node_x / 100000 +
        np.random.rand(len(mg.node_y)) / 10000)
    mg.set_closed_boundaries_at_grid_edges(
        bottom_is_closed=True,
        left_is_closed=True,
        right_is_closed=True,
        top_is_closed=True,
    )
    mg.set_watershed_boundary_condition_outlet_id(
        0, mg["node"]["topographic__elevation"], -9999.0)
    soil[:] += 0.0  # initial condition of no soil depth.
    br[:] = z[:]
    z[:] += soil[:]

    # Create a D8 flow handler
    fa = FlowAccumulator(mg,
                         flow_director="D8",
                         depression_finder="DepressionFinderAndRouter")

    # Parameter values for detachment-limited test
    K_br = 0.002
    K_sed = 0.002
    U = 0.0001
    dt = 10.0
    F_f = 0.2  # all detached rock disappears; detachment-ltd end-member
    m_sp = 0.5
    n_sp = 1.0
    v_s = 0.25
    H_star = 0.1

    # Instantiate the SpaceLargeScaleEroder component...
    sp = SpaceLargeScaleEroder(
        mg,
        K_sed=K_sed,
        K_br=K_br,
        F_f=F_f,
        phi=0.0,
        H_star=H_star,
        v_s=v_s,
        m_sp=m_sp,
        n_sp=n_sp,
        sp_crit_sed=0,
        sp_crit_br=0,
    )

    # ... and run it to steady state (10000x1-year timesteps).
    for _ in range(10000):
        fa.run_one_step()
        sp.run_one_step(dt=dt)
        br[mg.core_nodes] += U * dt  # m
        soil[
            0] = 0.0  # enforce 0 soil depth at boundary to keep lowering steady
        z[:] = br[:] + soil[:]

    # compare numerical and analytical slope solutions
    num_slope = mg.at_node["topographic__steepest_slope"][mg.core_nodes]
    analytical_slope = np.power(
        ((U * v_s * (1 - F_f)) /
         (K_sed * np.power(mg.at_node["drainage_area"][mg.core_nodes], m_sp)))
        +
        (U /
         (K_br * np.power(mg.at_node["drainage_area"][mg.core_nodes], m_sp))),
        1.0 / n_sp,
    )

    # test for match with analytical slope-area relationship
    testing.assert_array_almost_equal(
        num_slope,
        analytical_slope,
        decimal=8,
        err_msg="SpaceLargeScaleEroder bedrock-alluvial slope-area test failed",
        verbose=True,
    )

    # compare numerical and analytical soil depth solutions
    num_h = mg.at_node["soil__depth"][mg.core_nodes]
    analytical_h = -H_star * np.log(1 - (v_s / (K_sed / (K_br *
                                                         (1 - F_f)) + v_s)))

    # test for match with analytical sediment depth
    testing.assert_array_almost_equal(
        num_h,
        analytical_h,
        decimal=5,
        err_msg=
        "SpaceLargeScaleEroder bedrock-alluvial soil thickness test failed",
        verbose=True,
    )
Ejemplo n.º 58
0
def test_edge_draining():
    """
    This tests when the lake attempts to drain from an edge, where an issue
    is suspected.
    """
    # Create a 7x7 test grid with a well defined hole in it, AT THE EDGE.
    mg = RasterModelGrid((7, 7))

    z = mg.node_x.copy()
    guard_sides = np.concatenate((np.arange(7, 14), np.arange(35, 42)))
    edges = np.concatenate((np.arange(7), np.arange(42, 49)))
    hole_here = np.array(([15, 16, 22, 23, 29, 30]))
    z[guard_sides] = z[13]
    z[edges] = -2.0  # force flow outwards from the tops of the guards
    z[hole_here] = -1.0

    A_new = np.array([[[
        0.0,
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
        0.0,
        0.0,
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
        0.0,
        15.0,
        5.0,
        4.0,
        3.0,
        2.0,
        1.0,
        0.0,
        0.0,
        10.0,
        4.0,
        3.0,
        2.0,
        1.0,
        0.0,
        0.0,
        1.0,
        4.0,
        3.0,
        2.0,
        1.0,
        0.0,
        0.0,
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
        0.0,
        0.0,
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
        0.0,
    ]]]).flatten()

    depr_outlet_target = np.array([
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        14,
        14,
        XX,
        XX,
        XX,
        XX,
        XX,
        14,
        14,
        XX,
        XX,
        XX,
        XX,
        XX,
        14,
        14,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
        XX,
    ]).flatten()

    mg.add_field("node", "topographic__elevation", z, units="-")

    fr = FlowAccumulator(mg, flow_director="D8")
    lf = DepressionFinderAndRouter(mg)

    fr.run_one_step()
    lf.map_depressions()
    assert mg.at_node["drainage_area"] == approx(A_new)
    assert lf.depression_outlet_map == approx(depr_outlet_target)
    rmg,
    rmg.status_at_node,
    color_for_closed="blue",
    plot_name=
    'Carbon Glacier Raster Model Grid\nClosed boundaries in blue, Outlet in white',
    var_name='elevation',
    var_units='m',
    grid_units=('m', 'm'))

# ### Flow routing

# #### Without depression finder/filler

flow_accum = FlowAccumulator(rmg,
                             surface='topographic__elevation',
                             flow_director='FlowDirectorD8',
                             depression_finder=None,
                             runoff_rate=None)

da, q = flow_accum.accumulate_flow()

imshow_grid(rmg,
            'drainage_area',
            plot_name='Contributing Area',
            var_name='Contributing Area',
            var_units='m^2',
            grid_units=('m', 'm'))
fig = plt.gcf().set_size_inches(18.5, 10.5)

# #### With depression finder/filler
Ejemplo n.º 60
0
def test_steady_state_with_basic_solver_option():
    """
    Test that model matches the transport-limited analytical solution
    for slope/area relationship at steady state: S=((U * v_s) / (K * A^m)
    + U / (K * A^m))^(1/n).

    Also test that model matches the analytical solution for steady-state
    sediment flux: Qs = U * A * (1 - phi).
    """

    # set up a 5x5 grid with one open outlet node and low initial elevations.
    nr = 5
    nc = 5
    mg = RasterModelGrid((nr, nc), xy_spacing=10.0)

    z = mg.add_zeros("topographic__elevation", at="node")

    mg["node"]["topographic__elevation"] += (
        mg.node_y / 100000 + mg.node_x / 100000 +
        np.random.rand(len(mg.node_y)) / 10000)
    mg.set_closed_boundaries_at_grid_edges(
        bottom_is_closed=True,
        left_is_closed=True,
        right_is_closed=True,
        top_is_closed=True,
    )
    mg.set_watershed_boundary_condition_outlet_id(
        0, mg["node"]["topographic__elevation"], -9999.0)

    # Create a D8 flow handler
    fa = FlowAccumulator(mg,
                         flow_director="D8",
                         depression_finder="DepressionFinderAndRouter")

    # Parameter values for detachment-limited test
    K = 0.01
    U = 0.0001
    dt = 1.0
    F_f = 0.0  # all sediment is considered coarse bedload
    m_sp = 0.5
    n_sp = 1.0
    v_s = 0.5

    # Instantiate the ErosionDeposition component...
    ed = ErosionDeposition(
        mg,
        K=K,
        F_f=F_f,
        v_s=v_s,
        m_sp=m_sp,
        n_sp=n_sp,
        sp_crit=0,
        solver="basic",
    )

    # ... and run it to steady state (5000x1-year timesteps).
    for _ in range(5000):
        fa.run_one_step()
        ed.run_one_step(dt=dt)
        z[mg.core_nodes] += U * dt  # m

    # compare numerical and analytical slope solutions
    num_slope = mg.at_node["topographic__steepest_slope"][mg.core_nodes]
    analytical_slope = np.power(
        ((U * v_s) /
         (K * np.power(mg.at_node["drainage_area"][mg.core_nodes], m_sp))) +
        ((U) /
         (K * np.power(mg.at_node["drainage_area"][mg.core_nodes], m_sp))),
        1.0 / n_sp,
    )

    # test for match with analytical slope-area relationship
    testing.assert_array_almost_equal(
        num_slope,
        analytical_slope,
        decimal=8,
        err_msg="E/D slope-area test failed",
        verbose=True,
    )

    # compare numerical and analytical sediment flux solutions
    num_sedflux = mg.at_node["sediment__flux"][mg.core_nodes]
    analytical_sedflux = U * mg.at_node["drainage_area"][mg.core_nodes]

    # test for match with anakytical sediment flux
    testing.assert_array_almost_equal(
        num_sedflux,
        analytical_sedflux,
        decimal=8,
        err_msg="E/D sediment flux test failed",
        verbose=True,
    )