Skip to main content

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 CLAUDE.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 CLAUDE.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 (default SmartSegmentedIMM; 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 mirroring MultiTracker.
  • 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-scan association diagnostic (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 VariantSource Protocol (competing readings of one observation).
  • Capability envelopes (CapabilityEnvelope): per-platform kinematic limits -- speed, acceleration, turn rate, climb rate, altitude -- applied as a third association test after the chi-squared gate and the clutter floor. The gate is scale-relative and the floor is about measurement density, so this is the only term that asks whether the target could physically have got there. It is not a motion model and carries no predictive power: it bounds the admissible set, the IMM still predicts inside it. Attach one per dwell (SolutionSet.envelope), per observation (a fourth route() tuple element), or per run (default_envelope). Charges are relieved by a 3-sigma slack built from BOTH endpoints' uncertainty; because implied speed is a finite difference, position error enters divided by the interval. So an envelope is nearly inert on densely sampled data (its wall sits inside the noise) and bites on sparse or coasted tracks, which is where the chi-squared gate is weakest.
  • Fragment rejoin (joinable_fragments): a batch pass proposing which completed track fragments could be one platform across a data gap. Under an envelope the question has an answer; without one it has no criterion beyond proximity, which a gap defeats. Proposes only -- never mutates.
  • Exclusive solution sets (SolutionSet): an upstream may emit several candidate solutions for ONE dwell with per-candidate prior weights, exactly one of which is true. The set occupies one scan slot under one obs_id; the MFA branches a world per candidate and resolves them across scans, and a set that fits no track births ONE track rather than one per candidate.
  • The stationary resolver (convolve as the live estimator of a locked track; the cluster answer as resolved), split/merge with TrackEvent lineage, and smoothed_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 is SmartSegmentedIMM. gri-kalman exposes one uniform interface (update(ell, t) / update_observable / predict / coast / smoothed_track / result / is_initialized) across IMM, SmartIMM, SegmentedIMM, and SmartSegmentedIMM. gri-multitrack defaults to the maneuver-segmenting, outlier-rejecting SmartSegmentedIMM the design calls for; pass tracker_factory=make_imm (or any Tracker factory) to swap it. The choice is isolated to gri_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 the CLAUDE.md "Architecture pivot". If LAP speed ever matters, add lapsolver/lap (tiny) -- not ortools. 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, SolutionSet

tracker = MultiTracker()
# each item is (payload, time_s); payload is an Ell, a gri-obs observable,
# a PresenceObs, or a SolutionSet of mutually exclusive candidates.
outputs = tracker.process([(ell0, 0.0), (tdoa1, 1.0), (presence, 2.0)])

# one dwell, three candidate geos, exactly one of them true:
dwell = SolutionSet([ell_a, ell_b, ell_c], [0.5, 0.3, 0.2])
outputs = tracker.process([(dwell, 3.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.tracksLabeledTrack records (a gri-kalman TrackEstimate plus the stationary fields). Each carries state as an EllVel (position + velocity + 6x6 covariance; .ell for the position-only Ell), mode_probabilities, existence (r_i), confirmed, hits, parent (split lineage), is_stationary / stationary_locked / resolved (the convolver's cluster answer), and a bound predict.

  • Where are the unused / outlier observations? Top-level output.dispositions, one Disposition per observation. Each has index, 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_name names it, d.variant is the measurement actually used). The committal GNN reports confidence=1.0; the MFA reports the world-agreement mass with provisional=True.

    For a SolutionSet, d.solution_id names the candidate that was used -- echoed even when the highest-weight candidate won, so upstream confidence stays scorable. It is orthogonal to variant_name: selecting a non-primary candidate is not a heal, and only variant_name bears on healed.

  • output.count_distribution / expected_count / most_likely_count — the Poisson-binomial cardinality over the existences.

  • output.events — this scan's split / merge TrackEvents.

  • output.association — the per-scan diagnostic (gates / marginals / hypotheses); None when not computed. Each WorldHypothesis carries assign and births (obs_id -> newborn label): worlds that disagree about which candidate of a dwell is real have identical assign maps and differ only in births, so for a dwell that starts a track rather than continuing one, births is the whole hypothesis surface. VariantMarginal keyed on solution_id gives the per-candidate world mass.

  • 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 a PredictedState at 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 live tracks are 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 + Associator protocol.
  • 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 -- the MultiTracker orchestrator.

Release files for gri-multitrack 0.3.3

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

Source distribution (sdist)

Source distribution for gri-multitrack 0.3.3
File Size Uploaded
gri_multitrack-0.3.3.tar.gz 166.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for gri-multitrack 0.3.3
File Interpreter ABI Platform
gri_multitrack-0.3.3-py3-none-any.whl Python 3 none any Details

Total release size: 273.5 kB

Release files / gri_multitrack-0.3.3.tar.gz

Download URL gri_multitrack-0.3.3.tar.gz
Size 166.8 kB
Tags Source
SHA-256 checksum
How to use checksums
8ecb79bf85c9704afcf07d278403bb5e224bd49d0b6c53ecc5370b196784eba0
BLAKE2b-256 checksum
How to use checksums
52a57b12301f571c0c97adccc6660423c213450597d5f09e0fbc0647df19b6b4
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.3-py3-none-any.whl

Download URL gri_multitrack-0.3.3-py3-none-any.whl
Size 106.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
41bcfe326f39832781044a6f497b83a4ea6f07876daf8b365d51bf87915f5221
BLAKE2b-256 checksum
How to use checksums
550fa882e58015cd91b3b0f115f4ec305a3115c97aecbe98106011606745219f
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 history Release notifications | RSS feed

This release

0.3.3 This release

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

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