Skip to content

Repository files navigation

Trajectory Simulator

trajectory-simulator is a Kotlin/JVM benchmark project for testing 3D trajectory prediction algorithms.

It generates synthetic trajectories, produces measurement streams with configurable noise and corruption, runs predictors step by step, computes metrics, and writes CSV/JSON/Markdown/SVG reports.

The simulator is separate from the predictor implementation. It can test the local imm-trajectory-solver (repo. link) project through an adapter, and it can also test other predictors that implement the simulator-side protocol.

Project Layout

trajectory-simulator/
├── simulator-core/         # scenarios, measurements, solver protocol, metrics, reports
├── simulator-imm-adapter/  # adapter for imm-trajectory-solver
└── simulator-app/          # runnable benchmark cases

Modules

simulator-core

  • Defines the simulator-side TrajectorySolver protocol.
  • Provides trajectory sources, measurement generation, benchmark pipelines, metrics, reports, and visualization.
  • Does not depend on imm-trajectory-solver.

simulator-imm-adapter

  • Wraps imm-trajectory-solver as a simulator TrajectorySolver.
  • Converts simulator measurements, predictions, updates, and metadata to/from backend types.
  • Provides Cartesian and WGS84 IMM adapters. The WGS84 adapter interprets simulator Vector3 positions as local ENU meters, converts them to WGS84 measurements for the backend, and converts WGS84 predictions back to ENU for simulator metrics.

simulator-app

  • Contains runnable benchmark cases.
  • Main class: dev.trajectory.simulator.app.ImmBenchmarkCaseKt.

Requirements

  • JDK 17+
  • Gradle Wrapper
  • Kotlin/JVM

The project uses Gradle Kotlin DSL and includes ../imm-trajectory-solver through Gradle composite build:

includeBuild("../imm-trajectory-solver")

If your default Java is older than 17, set JAVA_HOME before running Gradle. Example for PowerShell:

$env:JAVA_HOME='C:\Program Files\Eclipse Adoptium\jdk-17.0.19.10-hotspot'
$env:PATH="$env:JAVA_HOME\bin;$env:PATH"

Commands

Run all tests:

.\gradlew.bat test

Run the benchmark application:

.\gradlew.bat :simulator-app:run

Run only simulator app tests:

.\gradlew.bat :simulator-app:test

Clean build outputs:

.\gradlew.bat clean

Benchmark Output

Benchmark reports are written to:

build/reports/trajectory-simulator/

Each pipeline gets its own directory:

build/reports/trajectory-simulator/<pipeline-id>/
├── report.md
├── metrics.json
├── csv/
└── svg/

Output files:

  • report.md - readable Markdown summary.
  • metrics.json - aggregate and per-run metrics.
  • csv/*.csv - step-by-step simulation logs.
  • svg/*.svg - trajectory plots and error comparison plots.

Current Benchmark Pipelines

The runnable benchmark definitions are in:

simulator-app/src/main/kotlin/dev/trajectory/simulator/app/ImmBenchmarkCase.kt

Current pipelines:

  • clean-kinematics - straight motion, acceleration, smooth turn.
  • wgs84-coordinate-frame - the same metric simulator protocol routed through the WGS84 public solver API and compared with a Cartesian IMM baseline.
  • corrupted-measurements - noisy measurements with periodic outliers.
  • missing-measurements - scenarios with periodic missed measurements.
  • arbitrary-maneuvers - waypoint-based maneuvering trajectories.
  • kalman-filter-variants - IMM with CV/CA/Singer motion models and different Kalman-family filters.

The wgs84-coordinate-frame pipeline uses:

  • IMM-Cartesian-CV-CA-Singer - Cartesian reference solver.
  • IMM-WGS84-CV-CA-Singer - WGS84 solver with explicit origin, WGS84 input measurements, and WGS84 public prediction output.
  • IMM-WGS84-Mixed-KF-CV-CA-Singer - WGS84 solver using the configurable IMM filter set API with CV=LINEAR, CA=INNOVATION_ADAPTIVE, and Singer=EXTENDED.

Simulator reports still compute errors in local ENU meters so Cartesian and WGS84 runs are directly comparable.

What It Can Simulate

Trajectory sources:

  • Analytic trajectory: time -> Vector3.
  • Sampled trajectory with interpolation.
  • Waypoint trajectory with smooth Hermite interpolation.

Measurement behavior:

  • No noise.
  • Isotropic Gaussian noise.
  • Periodic missed measurements.
  • Periodic outliers.
  • Periodic delays, subject to solver capability support.

Solver capabilities are declared explicitly:

data class SolverCapabilities(
    val supportsMissingMeasurements: Boolean,
    val supportsDelayedMeasurements: Boolean,
    val supportsProbabilisticPrediction: Boolean,
)

Metrics

The default metric set includes:

  • ADE - average prediction error.
  • RMSE - root mean square prediction error.
  • FDE - final displacement error.
  • MaxError.
  • MedianError.
  • P90Error.
  • P95Error.
  • MeanPredictionToMeasurementError.
  • UpdatedEstimateADE.
  • UpdatedEstimateRMSE.
  • MeanNegativeLogLikelihood.
  • AcceptedUpdateRatio.

Prediction error is computed between the predicted position and the true trajectory position at the same time.

Visualization

SVG plots are enabled by default for the benchmark app.

Trajectory plots use:

  • Blue line: true XY trajectory.
  • Gray points: measurements.
  • Colored points: predictions.
  • Colored measurement-to-prediction segments: prediction error level.

Default error color scale:

  • Green: error <= 5 m.
  • Orange: 5 m < error <= 20 m.
  • Red: 20 m < error <= 60 m.
  • Purple: error > 60 m.

Adding a Solver

Implement TrajectorySolver or create an adapter that wraps an external predictor:

abstract class TrajectorySolver(
    val name: String,
    val capabilities: SolverCapabilities,
) {
    abstract fun initialize(history: List<TimedMeasurement>): SolverState

    abstract fun predict(
        state: SolverState,
        time: Double,
    ): SolverPrediction

    abstract fun update(
        state: SolverState,
        event: MeasurementEvent,
    ): SolverUpdate

    abstract fun reset(): SolverState
}

The simulator uses explicit state passing:

  • initialize creates the initial state.
  • predict must not mutate the state.
  • update returns the next state.

Adding a Scenario

Create a SimulationScenario:

SimulationScenario(
    id = "my-scenario",
    trajectory = AnalyticTrajectorySource("my-scenario") { time ->
        Vector3(
            x = 100.0 + 10.0 * time,
            y = 20.0,
            z = 900.0 + 2.0 * time,
        )
    },
    times = (0..40).map { it.toDouble() },
    initialHistorySize = 3,
    noiseModel = IsotropicGaussianNoise(standardDeviation = 2.0),
    randomSeed = 700L,
)

Then add it to a BenchmarkPipeline in simulator-app.

Development Notes

  • Time grids must be strictly increasing.
  • Initialization history must be strictly increasing by measurement time.
  • Predictors should report likelihood and Mahalanobis distance when available.
  • Reports are deterministic when scenario seeds are fixed.

About

Kotlin/JVM benchmark project for testing 3D trajectory prediction algorithms

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages