Lithology and channel steepness statistics
This page describes the statistics behind two lithology features:
- k_sn by lithologic unit in
lsdtt-chi-analysis(lithology_fname): the chi-elevation slope of each fitted channel, summarised per unit. Usage is in the chi analysis reference. - Lithology grouping of channel-link bearings and junction angles in
lsdtt-channel-network-analysis. Those statistics are on the channel-link bearing statistics page.
The code is in src/cpu/lsdtt_lithology_map_cpu.{hpp,cpp} (reading a map and
assigning units), src/cpu/lsdtt_lithology_channel_stats_cpu.{hpp,cpp} (nodes,
segments, reaches, summaries and the bootstrap) and src/utils/lsdtt_statistics
(the weighted quantile and the median). The tests and the measured tolerances are in
the developer testing notes, sections 8c.3 and 8c.4.
Units on the grid
A lithology map is read once, on the DEM grid. A polygon layer is rasterised; a
code raster is used as it is, or warped onto the grid with nearest neighbour when
its grid differs. Each pixel gets a unit code, or -1 (unmapped) when a valid
pixel is touched by no polygon or is nodata in the code raster. A map that labels no
valid DEM pixel is refused, and a partial cover is reported with its unlabelled
fraction (a warning above 10 %).
Each channel node takes the unit of its pixel. The distance from each pixel to the
nearest unit boundary is computed once, so a node's distance to its nearest contact
is known. Nodes closer than lithology_contact_buffer_metres to a contact are set
aside from the per-unit statistics; they stay in the node table, flagged.
Segments
The chi fit is piecewise linear, and each segment owns a set of nodes. A segment has a length-weighted majority unit and a purity: the share of its length (weights are each node's step length to its receiver) in that unit. Ties go to the lower code. Segments are not split at contacts, so a segment that crosses a contact gives all of its nodes the segment's k_sn. The node-level k_sn is therefore the segment value, and the reach-level k_sn (below) is the one that respects the unit boundary.
Lithologic reaches and the OLS k_sn
A reach is a run of consecutive nodes in one unit on one branch. It breaks when the unit changes or a buffer is entered. For a reach with nodes indexed by k, the elevation z_k is regressed on the chi coordinate chi_k by ordinary least squares:
slope = sum((chi_k - chi_mean)(z_k - z_mean)) / sum((chi_k - chi_mean)^2)
The slope is k_sn_ols, and it is the reach's k_sn (the chi-elevation slope, as in
the fitted segments). The standard error is k_sn_ols_se = sqrt(SSR / (n - 2) / Sxx),
where SSR is the residual sum of squares, n the node count and Sxx the chi sum of
squares. r_squared = 1 - SSR / SYY. The SE is NaN below three nodes.
A second estimate, k_sn_fit_chi_weighted, is the mean of the nodes' segment k_sn
values weighted by the chi interval each node spans (only intervals with positive
chi span count). It is written for comparison with k_sn_ols.
A reach qualifies when it has at least lithology_min_reach_pixels nodes (the
default 10; the CLI refuses fewer than 2, since a slope needs two points), a positive
chi span and a finite slope.
The reach end types are source, junction, outlet, contact and buffer. They
describe why the reach stops, and they are written so that a reach can be filtered
by how it ends (for example, reaches that end at a contact can be excluded).
Summaries
Per unit, the node statistics use the nodes not set aside by the buffer: the median,
the 25th and 75th percentiles (the code's interpolated quantiles), the mean, the
minimum and maximum, and the length-weighted mean and quartiles. The reach statistics
use the qualifying reaches: their median and quartiles (interpolated), and the
length-weighted median, weighted by reach length. low_sample is set when fewer than
five reaches qualify, and in that case no bootstrap is run.
Median convention (D2)
The unweighted median is the middle value for an odd count, and the mean of the two
middle values for an even count (median_in_place_impl in lsdtt_statistics). The
interpolated quartiles use the shared quantile_interpolated_in_place definition.
Weighted quantile
The length-weighted quantiles use compute_weighted_quantile in
src/utils/lsdtt_statistics.cpp. It is a lower weighted quantile:
- Sort the values ascending (a stable sort, so equal values keep their input order).
- Accumulate the weights in that order. The total is the sum over all values.
- Return the first value whose cumulative weight is at least
q * total.
So q = 1 always returns the largest value. A zero-weight value is returned only if it is
the first point at which the cumulative weight reaches the target (for example, at q = 0). Weights must be finite and not
negative, and a total weight of zero is an error. With equal weights the function
agrees with the floor quantile (compute_quantile_floor_in_place) at q = 0, 0.5 and 1
for sizes 1 to 12; the unit test pins this, and the fixed-vector cases at q = 0, 0.25,
0.5 and 1.
Because the quantile is a value from the data (not an interpolation), a length-weighted median is always one of the observed k_sn values.
Bootstrap interval
The interval on the per-unit median reach k_sn is a percentile bootstrap:
- The qualifying reaches of the unit are the sample. With
lithology_bootstrap_clusterreach, each reach is a cluster. Withbasin, the reaches of one basin form one cluster (clusters are ordered by basin index before the draw). - Draw as many clusters as there are, with replacement, and pool their reaches.
- Take the median of the pooled reach k_sn values.
- Repeat
lithology_bootstrap_replicatestimes (default 1000). - Report the 2.5 and 97.5 percentiles of the replicate medians (interpolated).
The random stream is a std::mt19937_64. In lsdtt-chi-analysis it is seeded per
m/n value with std::seed_seq{seed, round(m/n * 10000)} (lithology_bootstrap_engine).
A value's interval therefore depends only on the seed and that m/n value, and the
per-m/n output of a run with several m/n values is byte-identical to the run with one.
In lsdtt-channel-network-analysis the junction-angle bootstrap uses one stream per
run.
A reach-level interval treats reaches as independent. Reaches on one trunk channel are correlated, so the reach interval is a lower bound on the uncertainty. A basin cluster is the coarser check, with fewer clusters and so a wider interval.
Measured tolerances
The test set in section 8c.3 of the testing notes is a steady-state known answer. The DEM is 120 by 160 cells at 30 m, with two erodibilities K1 = 1e-5 and K2 = 4e-5, uplift 2e-4 and a contact in one of two layouts. For n = 1 (m = 0.45) the expected medians are (U/K)^(1/n), 20 and 5; for n = 2 they are sqrt(20) and sqrt(5). The measured relative errors (identical for n = 1 and 2) were:
| Statistic | Layout | Unit K1 | Unit K2 | Ratio of medians |
|---|---|---|---|---|
| node median | A (contact parallel to drainage) | +1.14 % | +1.25 % | 0.108 % |
| reach median (OLS, 10 nodes, buffer 0) | A | +1.759 % | +1.310 % | 0.443 % |
| reach median | B (contact across drainage) | +1.192 % | +1.581 % | 0.383 % |
The medians sit high because the steady state integrates chi at each cell's own area and the chi integral is evaluated trapezoidally. Their tests use a 2 % cap, and the ratio tests a 1 % cap set at about twice the measured error. Through the CLI, which re-routes flow on the steady DEM, the reach medians are 20.403 and 5.163, a ratio of 3.9515 against the true 4 (1.212 % from 4; the test is pinned at 2.42 %).
Reach counts are pinned exactly: layout A has 46 (K1) and 42 (K2) reaches (16 and 14 qualifying); layout B has 57 and 38 (18 and 20 qualifying). Layout B has 20 contact-ended reaches against 8 for A.
Two smaller checks are in the same test set: lithology_min_reach_pixels is honoured,
and the OLS on an exact line z = 3 + 7 chi returns k_sn 7 with R^2 1 and SE 0.
Field example
The West Virginia Allegheny Front run (live data, not a CTest) is the measured per-unit table in the chi reference page. It is not a validation of lithology erodibility: the units are map units, and the caveats in that page (uniform uplift, knickzones, strike-parallel sampling, alluvium, map-scale position) apply.