gri-multitrack
Multi-target geolocation tracking. gri-multitrack combines the gri per-target
Kalman-IMM (gri-kalman, fed by gri-obs observables) with a multi-target policy
layer: ingest/routing of the feed-in data tiers, measurement-space scoring,
per-scan association, track lifecycle, existence (Labeled Multi-Bernoulli), and
a Poisson-binomial count distribution.
The governing seam is "gri scores, gri-multitrack assigns": gri-kalman owns
per-target estimation and the measurement-space likelihoods; gri-multitrack owns
everything multi-target. The multi-target layer is DIY on numpy/scipy --
gri-multitrack is Stone-Soup-free (see the "Architecture pivot" in AGENTS.md).
The primary tracker class is MultiTracker (the "Crucible" name now belongs to
the companion 3D app).
See PLAN.md for the live program plan (state, backlog, decisions) and
AGENTS.md for repo guidance. Scenario generation, replay harnesses, scoring,
and the replay viewer live in the sibling gri-tracksim repo, which
depends on this engine and serializes its outputs (the engine itself is
serialization-free).
Status
v1 of the original build plan is complete (see PLAN.md): ingest ->
score -> associate -> gri IMM update -> lifecycle -> LMB output, on geos, raw
TDOAs, and presence events, plus the outlier stream (clutter floors,
per-observation dispositions, the extensible variant catalog), the stationary
convolve resolver, split/merge with lineage, and batch RTS retrospectives.
Implemented:
- Ingest + router for the feed-in tiers (geo
Ell, observables, presence). - Per-track adapter over any gri
Tracker(defaultSmartSegmentedIMM; CV / CoordinatedTurn / Static bank). - Measurement-space scoring seam (Gaussian innovation + chi-squared gate).
- GNN scaffold associator (local scipy Hungarian) for bring-up.
- MFA tracker (
MfaTracker): a local hypothesis-oriented MHT that defers at ambiguous crossings and resolves via accumulated kinematic likelihood -- the v1 associator (loose-coupled; not Stone Soup's MFA). Standalone engine mirroringMultiTracker. - Track lifecycle: birth from geos, M-of-N confirm, patient deletion.
- Existence r_i + Poisson-binomial count distribution.
- Presence ("is it on") ->
coast(t)+ existence bump. - LMB output: labeled tracks + count distribution + top-level per-observation
dispositions+ the per-scanassociationdiagnostic (gates / marginals / hypotheses), associator-agnostic. See Output.
Also implemented since the skeleton:
- Per-kind clutter likelihood floors and per-observation DISPOSITIONS
(assigned / birthed / clutter / healed); the user-extensible
VariantSourceProtocol (competing readings of one observation). - The stationary resolver (convolve as the live estimator of a locked track;
the cluster answer as
resolved), split/merge withTrackEventlineage, andsmoothed_tracks()batch retrospectives. - Serialization, GOSPA/OSPA metrics, and the replay viewer live in
gri-tracksim(the engine stays serialization-free).
Notable design choices
- The per-track estimator is any gri-kalman
Tracker; the default isSmartSegmentedIMM. gri-kalman exposes one uniform interface (update(ell, t)/update_observable/predict/coast/smoothed_track/result/is_initialized) acrossIMM,SmartIMM,SegmentedIMM, andSmartSegmentedIMM. gri-multitrack defaults to the maneuver-segmenting, outlier-rejectingSmartSegmentedIMMthe design calls for; passtracker_factory=make_imm(or anyTrackerfactory) to swap it. The choice is isolated togri_multitrack/track.py. - Stone-Soup-free; multi-target is DIY on numpy/scipy. Both associators are
local (GNN over scipy
linear_sum_assignment; MFA a local hypothesis-oriented MHT). Stone Soup's MFA is filter-coupled and would cost the gri IMM, and is heavy (~48 MB of deps +ortools); see theCLAUDE.md"Architecture pivot". If LAP speed ever matters, addlapsolver/lap(tiny) -- notortools. Stone Soup remains only as an optional dev-time GOSPA/OSPA cross-check.
Install
Uses uv with editable path dependencies on the sibling gri repos (in
../../foss/).
uv sync # core (Stone-Soup-free)
uv sync --extra crosscheck # optional: Stone Soup, for a dev-time GOSPA/OSPA check only
Run
uv run python examples/two_target_demo.py # end-to-end demo
uv run pytest # tests
uv run ruff check gri_multitrack test # lint
uv run ty check # type check
Quick use
from gri_multitrack import MultiTracker
tracker = MultiTracker()
# each item is (payload, time_s); payload is an Ell, a gri-obs observable,
# or a PresenceObs.
outputs = tracker.process([(ell0, 0.0), (tdoa1, 1.0), (presence, 2.0)])
final = outputs[-1]
for t in final.tracks:
print(t.label, t.existence, t.is_stationary, t.mode_probabilities)
print(final.count_distribution) # Poisson-binomial P(N=k)
Output
A TrackerOutput per scan, in the same unified surface a single-target
gri-kalman tracker reports (a single-target tracker is the degenerate
one-track case), so the two are read interchangeably.
-
output.tracks—LabeledTrackrecords (a gri-kalmanTrackEstimateplus the stationary fields). Each carriesstateas anEllVel(position + velocity + 6x6 covariance;.ellfor the position-onlyEll),mode_probabilities,existence(r_i),confirmed,hits,parent(split lineage),is_stationary/stationary_locked/resolved(the convolver's cluster answer), and a boundpredict. -
Where are the unused / outlier observations? Top-level
output.dispositions, oneDispositionper observation. Each hasindex,used,verdict,track,confidence. The outlier bucket is:outliers = [d for d in output.dispositions if not d.used]
Verdicts:
assigned(absorbed by a track),birthed(seeded a new track),clutter(explained better as clutter —used=False),healed(absorbed under an alternative READING;d.variant_namenames it,d.variantis the measurement actually used). The committal GNN reportsconfidence=1.0; the MFA reports the world-agreement mass withprovisional=True. -
output.count_distribution/expected_count/most_likely_count— the Poisson-binomial cardinality over the existences. -
output.events— this scan's split / mergeTrackEvents. -
output.association— the per-scan diagnostic (gates / marginals / hypotheses);Nonewhen not computed. -
output.worlds— the MFA's surviving global hypotheses with weights (the MHT-only confidence surface); empty for the committal GNN. -
Prediction:
track.predict(dt_s)returns aPredictedStateat any horizon (a locked-stationary track predicts its convolved fix). Smoothing is opt-in:tracker.smoothed_tracks()returns the per-label RTS retrospective (best given all data, refining the past); the livetracksare the filtered best-given-data-so-far.
Layout
gri_multitrack/ingest.py-- feed-in types, routing, scan grouping.gri_multitrack/track.py-- per-track adapter over the gri IMM.gri_multitrack/scoring.py-- measurement-space likelihood + gate ("gri scores").gri_multitrack/association.py-- GNN scaffold +Associatorprotocol.gri_multitrack/lifecycle.py-- birth / confirm / delete / existence.gri_multitrack/cardinality.py-- Poisson-binomial count distribution.gri_multitrack/output.py-- Labeled Multi-Bernoulli output records.gri_multitrack/tracker.py-- theMultiTrackerorchestrator.
Release files for gri-multitrack 0.3.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| gri_multitrack-0.3.2.tar.gz | 75.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| gri_multitrack-0.3.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 132.2 kB
Release files / gri_multitrack-0.3.2.tar.gz
| Download URL | gri_multitrack-0.3.2.tar.gz |
|---|---|
| Size | 75.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0a298c868b6827123443793a4986d7098a044c683cac4436ae25a3aa46cf86ad
|
|
BLAKE2b-256 checksum How to use checksums |
7f218e125557d93535829a809bab5121a557d6c370ad68b51259ece35e92eeb0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / gri_multitrack-0.3.2-py3-none-any.whl
| Download URL | gri_multitrack-0.3.2-py3-none-any.whl |
|---|---|
| Size | 56.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0aaad823664f13326cef0fabca0c34407f0db83c2dc88ca489ead73e86cccef7
|
|
BLAKE2b-256 checksum How to use checksums |
c3e2ce08f6c99e2b94bd101f1481d09b6af570e4f1203e9516e5dad259472069
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|