Skip to content

[ana5] Add extBottomOption to extend the DFA path to the deposit front#1296

Open
NUForever wants to merge 2 commits into
OpenNHM:masterfrom
NUForever:dfaPathExtBottomOption
Open

[ana5] Add extBottomOption to extend the DFA path to the deposit front#1296
NUForever wants to merge 2 commits into
OpenNHM:masterfrom
NUForever:dfaPathExtBottomOption

Conversation

@NUForever

Copy link
Copy Markdown

What this adds

A new option extBottomOption in DFAPathGeneration controlling how the
mass-averaged path is extended at the bottom:

  • extBottomOption = 0 (default, unchanged): the current fixed-length
    extrapolation (extendProfileBottom), which extends the path in the
    direction of its last points by factBottomExt * sMax, clipped at the
    DEM border.
  • extBottomOption = 1 (new): extend the path to the front of the
    simulated deposit.

Motivation

The mass-averaged path ends short of the deposit once the front
decelerates. The default bottom extension extrapolates a straight line of
fixed relative length, independently of where the avalanche actually
stopped, so the path end does not coincide with the simulated runout.
Targeting the deposit front yields a path that ends at the simulated
runout, which is useful for corridor-scale and road-crossing analyses.

How it works

  1. The front of the deposit is located in the peak flow thickness field
    (pft) as the flow-thickness-weighted centroid of the flow cells in the
    lowest part of the flow elevation range (findFlowFront), with a
    lowest-cell fallback for flat deposits and a snap to the deposit when the
    centroid falls between disjoint lobes.
  2. The path end is connected to that front by a Dijkstra least-cost path
    over the DEM (leastCostPath). The cost of a step is its horizontal
    length plus a penalty on the positive elevation gain and on the distance
    from the flow footprint, so the extension descends along the deposit.
    Distances use meters (distance_transform_edt with sampling=cellsize)
    so the penalties are resolution independent.
  3. If the front cannot be located or reached, or no flow thickness field is
    available, the path falls back to the fixed-length extension, so the
    AIMEC buffer is preserved.

The option is dispatched in extendDFAPath (which gains an optional
fieldFT argument, mirroring the existing extTopOption); the pft field
is read in generatePathAndSplitpoint only when the option is active. New
parameters live in DFAPathGenerationCfg.ini (ftThreshold,
lowFrontFraction, upSlopePenalty, flowDistPenalty) and resType
gains pft.

Default behaviour

Unchanged. With extBottomOption = 0 (the default) the existing pipeline
is not affected.

Tests

Unit tests added for findFlowFront (front band, flat deposit, two-lobe
snap, no flow), leastCostPath (channel routing, unreachable goal behind
nodata), extendProfileToFront (extension to the front, both fallbacks)
and readPeakFT (lookup by name and by hash, missing field). Docs updated
in docs/moduleAna5Utils.rst.

Context

Developed for the corridor-scale thalweg extraction on Route 115-CH (Chile)
and validated there against 208 manually delineated reference thalwegs
(median endpoint distance 64 m).

…posit front

The mass-averaged path ends short of the deposit once the front
decelerates, and the existing bottom extension extrapolates a straight
line of fixed relative length (factBottomExt * sMax) clipped at the DEM
border, independently of where the avalanche actually stopped.

This adds extBottomOption = 1: the front of the deposit is located in
the peak flow thickness field (flow-thickness-weighted centroid of the
flow cells in the lowest part of the flow elevation range, with a
lowest-cell fallback for flat deposits) and the path is extended to it
along a Dijkstra least-cost path over the DEM, penalizing uphill steps
and cells away from the flow footprint. The distance from the flow is
computed in meters (distance_transform_edt with sampling=cellsize) so
the penalties are resolution independent.

Default behaviour is unchanged (extBottomOption = 0); the option is
dispatched in extendDFAPath, which gains an optional fieldFT argument,
and the pft field is read in generatePathAndSplitpoint only when the
option is active. Used for the corridor-scale thalweg extraction on
Route 115-CH (Chile), validated there against 208 manually delineated
reference thalwegs (median endpoint distance 64 m).
@fso42 fso42 self-assigned this Jun 17, 2026
@fso42 fso42 added the enhancement New feature or request label Jun 17, 2026
@fso42 fso42 self-requested a review June 17, 2026 07:10
@fso42 fso42 changed the title Add extBottomOption to extend the DFA path to the deposit front [ana5] Add extBottomOption to extend the DFA path to the deposit front Jun 17, 2026

@fso42 fso42 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you Ivan for your PR. I have 2 comments, one is only minor.

Comment thread avaframe/ana5Utils/DFAPathGeneration.py Outdated
# peak flow thickness field, only needed when extending the path to the deposit front
fieldFT = None
if cfgDFAPath['PATH'].getint('extBottomOption', fallback=0) == 1:
fieldFT = readPeakFT(avalancheDir, simName)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In your readPeakFT function, makeSimDF(inputDir, …) is used, which parses all peak files. Since we are inside the simDF.loop, this happens for each sim and is just a re-reading and re-parsing. The data frame could be build before the loop and passed as argument, if possible.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed in eb692ff. makeSimDF is now called once before the simulation loop in generatePathAndSplitpoint and the resulting dataframe is passed to readPeakFT, so the peak files are parsed a single time for the whole run instead of once per simulation.

elev = demRaster[rows, cols]
lowBand = elev <= elev.min() + lowFrontFraction * (elev.max() - elev.min())
weight = fieldFT[rows, cols] * lowBand
frontRow = int(round(np.sum(rows * weight) / weight.sum()))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understood this correctly, then weight.sum() is never zero, correct? Otherweise a divide-by-zero guard would be necessary. If my understanding is correct, then it is not needed, but maybe a quick comment would help future maintainers

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You understood it correctly, weight.sum() is always positive: the lowest flow cell is always in lowBand and, being a flow cell, has fieldFT > ftThreshold >= 0, so its weight is strictly positive. I added a comment to that effect in eb692ff instead of a guard.

Move the makeSimDF call out of the per-simulation loop in
generatePathAndSplitpoint: the peak files are parsed once and the
dataframe is passed to readPeakFT, instead of re-reading and re-parsing
all peak files for every simulation. Document why findFlowFront needs
no divide-by-zero guard on the flow-weight sum.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants