Skip to main content

DTGeoSOM — path finding on a spherical SOM with the distance transform

mt.dtgeosom finds paths between two neurons of a trained GeoSOM (a Self-Organising Map on a geodesic dome) with the method of

M. Bui and M. Takatsuka, Path finding on a spherical SOM using the distance transform and floodplain analysis, Proceedings of the 6th International Workshop on Self-Organizing Maps (WSOM 2007), Bielefeld, Germany, 2007.

A SOM is a roadmap of a high-dimensional space. Given a start state and a goal state, the neurons on a path between their best matching units are the intermediate states to pass through. The path is found as in robot motion planning: a distance wave is spread from the goal over the lattice (the distance transform), with the U-height of every neuron as the cost of entering it, and the path is read off by walking downhill from the start. A plain shortest path may still cross a "mountain range" of the U-matrix (a cluster border with no data). Floodplain analysis then keeps the path on the neurons below a U-height threshold and raises the threshold only as far as needed to join start and goal: the flattest, shortest path.

Shortest (cyan) and flattest (magenta) path from tree node 8 to 13

The paper's binary-tree experiment (examples/01_binary_tree.py): the shortest path 8 → 13 jumps across the bright mountain range (8-9-12-13, not a path in the tree); the flattest path goes round it and passes 8-4-2-1-3-6-13, the tree path — as in figures 8 and 9 of the paper. ✕ = neurons above the floodplain.

Contents — Features · Installation · Quick start · Kinds of path · Interactive path picker · Drawing paths in scripts · API overview · The algorithms · Examples · Development · Releasing · Licence and citation

Features

  • The paper's method on GeoSOM: the distance transform (Algorithm 2), reading a path off the distance map (Algorithm 1) and floodplain analysis for the flattest, shortest path (Algorithm 3) — plus PathFinder.paper(som), which runs the algorithms exactly as printed, to reproduce the published results.
  • Four kinds of path between the same two neurons: shortest (U-height cost), flattest (floodplain), hops (fewest steps on the geodesic grid) and edge (shortest walk through data space).
  • Start and goal as neurons or as samples (a sample is mapped to its best matching unit).
  • The path as data: its neurons, cost, number of hops, the weight vectors along it (the intermediate states), the classes it passes, the highest U-height it climbs, and the whole distance map.
  • An interactive path picker on GeoSOM's rotatable map: a Select start & goal button, a check box per kind of path, the floodplain, distance maps, and inspecting the attributes of any neuron along a path.
  • Path drawing in scripts (plot_paths, PathViewer): great-circle segments on any of GeoSOM's map projections, legend entries that show or hide a path, and PNG export.
  • Works with any mt.geosom map: GeoSOM (sphere), PlaneSOM (plane or torus) and LineSOM.
  • The paper's synthetic data (datasets.binary_tree) and three runnable examples.

Installation

Name
pip install … dtgeosom (from PyPI once released; until then pip install -e . in this folder)
Python import mt.dtgeosom (mt is a namespace package shared with mt.geosom, mt.geodesicdome)
Requires Python ≥ 3.10, geosom ≥ 1.2, geodesicdomes ≥ 1.3, numpy
Optional pip install "dtgeosom[interactive]" for the map windows (matplotlib); [dev] adds pytest, ruff and the build tools

For development, ./setup_env.sh builds a ready-to-use environment in .venv (see Development).

Quick start

from mt.geosom import datasets
from mt.geosom.GeoSOM import GeoSOM
from mt.dtgeosom import PathFinder

ds = datasets.load('wine')
x = datasets.standardise(ds.data)
som = GeoSOM(8).train(x, epochs=30)

finder = PathFinder(som)
p = finder.shortest_path(x[136], x[9])      # samples (mapped to their BMUs) or neuron indices
q = finder.flattest_path(x[136], x[9])      # stays on the floodplain of the U-matrix

q.nodes                 # neuron indices, start ... goal
q.hops, q.cost          # number of steps, cost on the distance map
q.states(som)           # weight vectors along the path: the intermediate states
q.threshold             # U-height of the floodplain that holds the path
q.distance              # the distance map (cost from every neuron to the goal; inf = unreachable)
q.visited_labels(som.node_labels(x, ds.labels))   # classes passed on the way

paths = finder.all_paths(x[136], x[9])      # {'shortest', 'flattest', 'hops', 'edge'}: see "Kinds of path"

from mt.dtgeosom.gui import PathPicker, plot_paths     # needs matplotlib
plot_paths(som, list(paths.values()), x, ds.labels, names=list(paths),
           floodplain=q.threshold, projection='Wagner III').show()
PathPicker(finder, x, ds.labels).show()                # pick the start and goal with the mouse

Kinds of path

Kind finder.path(a, b, kind) What it minimises Colour in the picker
'shortest' shortest_path the distance transform with the U-height of every neuron entered as its cost (the paper's shortest path) cyan
'flattest' flattest_path the same, on the floodplain: never above the lowest U-height at which start and goal connect (Algorithm 3) magenta
'hops' hop_path the number of steps on the geodesic grid, ignoring the data (ties: the straightest route) yellow
'edge' edge_path the sum of distances between neighbouring weight vectors: the shortest walk through data space lime

finder.all_paths(a, b) returns all four as a dict. Comparing them shows what the U-matrix and the floodplain add: where all four agree the map is flat; where flattest departs from the others it is going round a ridge with no data.

Four kinds of path between a cultivar-3 and a cultivar-1 wine

examples/02_paths_between_samples.py on the UCI wine data: the shortest (U-height), fewest-hops and data-space (edge) paths cut straight across a border with no data; the flattest path goes round it, through the cultivar-2 region.

Interactive path picker

PathPicker (examples/03_interactive_paths.py) is GeoSOM's rotatable map viewer with path finding added.

The path picker: three kinds of path, a neuron on the flattest path being inspected

python examples/03_interactive_paths.py                          # wine data
python examples/03_interactive_paths.py --paths all              # all four kinds from the start
python examples/03_interactive_paths.py --data tree              # the paper's binary tree on a GeoSOM(2)
python examples/03_interactive_paths.py --data penguins --paths shortest,flattest
Do How
Choose the start and goal press Select start & goal (top left) or Enter, then click the start neuron and the goal neuron; the paths are drawn when the goal is clicked. The button (or Esc) cancels, keeping the current paths. With no path yet — when the window opens, or after c — the picker is already selecting, so just click two neurons.
Inspect a neuron any other click: its attribute vector appears on the right (bar chart and values), and the line under the map says which step of which path it is, or that it is not on a path
Walk along the path n / b: inspect the next / previous neuron on the path
Show or hide a kind of path the paths check boxes on the left (each in its path's colour), keys 1–4, or click its legend entry; a shows all four
Show or hide the floodplain the floodplain ✕ check box: crosses on the neurons above the flattest path's threshold
Colour the map by a distance map the distance map button or m: each press shows the next shown path's distance map to the goal, then the U-matrix again
Swap start and goal / clear x / c
Rotate, change projection, layers, labels as in GeoSOM's SOMViewer: drag, arrow keys, the projection and layer lists, s sample dots, l labels, …

The panel under the map lists each path's number of hops, its cost, the highest U-height it climbs and the classes of the samples it passes. Example options: --data (iris, penguins, wine, clusters, a CSV file, or tree), --paths (comma-separated kinds, or all), --step (the step cost of shortest and flattest: node, mean, edge, hops), --projection, --frequency, --epochs, --start / --goal (pre-select a path), and --save FILE (write an image instead of opening a window).

In your own code:

from mt.dtgeosom.gui import PathPicker

picker = PathPicker(PathFinder(som), x, labels, kinds=('shortest', 'flattest'), projection='Wagner III')
picker.set_endpoints(10, 500)        # or let the user click
picker.result                        # {kind: SOMPath} of the paths shown
picker.show()

Drawing paths in scripts

from mt.dtgeosom.gui import PathViewer, plot_paths

viewer = plot_paths(som, [p, q], x, labels, names=['shortest', 'flattest'], floodplain=q.threshold,
                    projection='Wagner III', path='paths.png')      # centred on the paths; saved when path= is given
viewer.set_path_visible('shortest', False)    # or click its legend entry; toggle_path(...), path_visible(...)
viewer.centre_on(q)                           # rotate the sphere so the path is in the middle
viewer.show()

PathViewer(som, data, labels, **SOMViewer_options) is GeoSOM's SOMViewer with paths: add_path(path, color, label), clear_paths(), show_floodplain(threshold), centre_on(...), set_path_visible(...). Every step between neighbouring neurons is drawn along the great circle joining them and broken where it crosses the edge of the map, so paths stay right while the sphere is rotated or the projection changed.

API overview

PathFinder(som, step='node', diagonal_factor=1.0, method='wavefront', rule='exact') — everything for one trained map (GeoSOM, PlaneSOM or LineSOM).

shortest_path(a, b), flattest_path(a, b, threshold='iterative'), hop_path(a, b), edge_path(a, b) one path; a, b are neuron indices or samples
path(a, b, kind), all_paths(a, b, kinds=KINDS), paths(pairs, kind) by kind; all kinds; many pairs
distance_map(goal, allowed=None) the distance transform towards goal (a Transform; .distance is the map)
floodplain(threshold), u_height, neuron(x) neurons below a U-height; the U-heights; a sample's BMU
PathFinder.paper(som) the paper's settings: Algorithm 2 verbatim, √2 diagonals, steepest descent
refresh() re-read the map after training it further
step cost of one step: 'node' (U-height of the neuron entered, the paper), 'mean', 'edge', 'hops' or a callable f(c, n)
method 'wavefront' (the paper's FIFO distance wave), 'dijkstra' (same result) or 'paper' (printed version)
rule reading the path off the map: 'exact' (follow the wave back) or 'steepest' (Algorithm 1 as printed)

SOMPath — nodes, start, goal, kind, cost, hops, distance (the distance map), transform, threshold and thresholds (flattest paths: the final and every tried U-height threshold), states(som), max_height(u_height), visited_labels(node_labels).

Lower level — Lattice.from_som(som) (neighbours, U-heights, positions, diagonal flags of the 2D index array); distance_transform(lattice, goal, step=…, allowed=…, method=…); descend(lattice, transform, start, rule=…); lattice_flattest_path / lattice_shortest_path; minimax_threshold(lattice, a, b); path_cost(lattice, nodes, step); NoPathError. Data — datasets.binary_tree(depth), tree_path(a, b), path_agrees_with_tree(...).

Module What
lattice Lattice: the SOM's neighbour graph, U-heights, neuron positions and diagonal flags
transform distance_transform: the distance wave ('wavefront', 'dijkstra', 'paper'), step costs, allowed mask
paths descend (Algorithm 1), SOMPath, path_cost, NoPathError
floodplain flattest_path (Algorithm 3), shortest_path, minimax_threshold, floodplain
pathfinder PathFinder, KINDS
datasets the synthetic data of section 4.1
gui PathViewer, plot_paths, PathPicker, great_circle (needs matplotlib)

The algorithms

Paper Here
Algorithm 2 — distance transform on the Geodesic SOM distance_transform(lattice, goal, method='wavefront') (FIFO distance wave, re-queues every improved neuron)
Algorithm 1 — shortest path by steepest descent descend(lattice, transform, start, rule='steepest' | 'exact')
Algorithm 3 — flattest, shortest path (floodplain) flattest_path(..., threshold='iterative'); 'minimax' finds the final threshold directly
U-height ("avg diff") of the neuron entered as the step cost step='node' (default); also 'mean', 'edge', 'hops', or any f(c, n)
√2 for diagonal neighbours of the 2D index array diagonal_factor=np.sqrt(2); the diagonal flags are recovered from GeoSOM's dome (Lattice.diagonal)
Copy updates to duplicate seam neurons not needed: GeoSOM's neurons are unique
Everything exactly as printed PathFinder.paper(som) (method='paper', steepest descent)

Details, and where the implementation departs from the printed pseudocode and why: docs/algorithm.md.

Examples

Script What
examples/01_binary_tree.py The paper's section 4.1: a 15-node binary tree on a GeoSOM(2); shortest vs flattest paths for every pair of tree nodes, and the 8 → 13 path of figures 8 and 9. Options: --show, --paper, --pair A B, --seed, --frequency, --epochs, --save
examples/02_paths_between_samples.py All four kinds of path between two samples of iris / penguins / wine / clusters (by default the two of different classes furthest apart), with the attributes that change most along the way. Options: --data, --start, --goal, --show, --frequency, --epochs, --save
examples/03_interactive_paths.py The interactive path picker (above)

On the binary tree (GeoSOM(2), 42 neurons, the paper's training settings, seed 0) 46 % of the shortest paths between two tree nodes follow the tree, and 82 % of the flattest paths. The NSW library benchmarking data of the paper's section 4.2 are not included.

Development

./setup_env.sh          # creates .venv here with everything installed, and opens a shell in it
pytest
ruff check .

setup_env.sh installs DTGeoSOM (editable), geosom, geodesicdomes, matplotlib, pytest, ruff and the build tools into .venv in this folder, plus PyTorch for the GPU if this machine has one, checks that a path can be found, and adds a hook to ~/.zshrc that activates the environment in new terminals inside the project. It installs GeoSOM and GeodesicDome in editable mode from ../GeoSOM and ../GeodesicDome when those checkouts exist (or --som PATH, --dome PATH), and from PyPI otherwise. ./setup_env.sh --help lists the options (--check, --recreate, --gpu none, --python PATH, --venv PATH, --no-shell-hook, …). Without the script: pip install -e ".[dev]".

GitHub Actions (.github/workflows/tests.yml) run the lint, the tests (Linux, macOS and Windows; Python 3.10–3.14) and the three examples on every push and pull request.

Releasing

Publishing a GitHub release with a tag vX.Y.Z uploads that version to PyPI automatically (.github/workflows/publish.yml): the workflow checks the tag against __version__ and CITATION.cff, builds and tests the package, uploads it with PyPI trusted publishing, and attaches the files to the release. Pushing to main never publishes. The one-time set-up and the steps are in RELEASING.md.

Licence and citation

AGPL-3.0-or-later with an attribution term; see LICENSE and NOTICE. Please cite the WSOM 2007 paper above and the software (CITATION.cff).

Metadata

Release files for dtgeosom 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dtgeosom 1.0.0
File Size Uploaded Provenance
dtgeosom-1.0.0.tar.gz 48.4 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for dtgeosom 1.0.0
File Interpreter ABI Platform
dtgeosom-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 96.3 kB

Release files / dtgeosom-1.0.0.tar.gz

Download URL dtgeosom-1.0.0.tar.gz
Size 48.4 kB
Tags Source
SHA-256 checksum
How to use checksums
2d7f2777668f4119f83640df2aa414fad4e8e8c1f0972bea3ce2d75d12288c07
BLAKE2b-256 checksum
How to use checksums
cc736af285a7c661b110fe1648ae2b323a6470dde11f0cf147b0dfc399e5686e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release files / dtgeosom-1.0.0-py3-none-any.whl

Download URL dtgeosom-1.0.0-py3-none-any.whl
Size 47.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d7b29c787f43427da5220d551092ae763a60545b803abcf51c5a56f6b4327b01
BLAKE2b-256 checksum
How to use checksums
2eef10972fb990013df03d99f2aaac06074201d69957f13c6256568cd9e297d8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Security for dtgeosom 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for dtgeosom-1.0.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page