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.
trajectory-simulator/
├── simulator-core/ # scenarios, measurements, solver protocol, metrics, reports
├── simulator-imm-adapter/ # adapter for imm-trajectory-solver
└── simulator-app/ # runnable benchmark cases
simulator-core
- Defines the simulator-side
TrajectorySolverprotocol. - Provides trajectory sources, measurement generation, benchmark pipelines, metrics, reports, and visualization.
- Does not depend on
imm-trajectory-solver.
simulator-imm-adapter
- Wraps
imm-trajectory-solveras a simulatorTrajectorySolver. - Converts simulator measurements, predictions, updates, and metadata to/from backend types.
- Provides Cartesian and WGS84 IMM adapters. The WGS84 adapter interprets simulator
Vector3positions 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.
- 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"Run all tests:
.\gradlew.bat testRun the benchmark application:
.\gradlew.bat :simulator-app:runRun only simulator app tests:
.\gradlew.bat :simulator-app:testClean build outputs:
.\gradlew.bat cleanBenchmark 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.
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 withCV/CA/Singermotion 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 withCV=LINEAR,CA=INNOVATION_ADAPTIVE, andSinger=EXTENDED.
Simulator reports still compute errors in local ENU meters so Cartesian and WGS84 runs are directly comparable.
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,
)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.
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.
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:
initializecreates the initial state.predictmust not mutate the state.updatereturns the next state.
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.
- 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.