jejjohnson / jejjohnson/xrtoolz
V3.4: Trajectory metrics — TrajectoryRMSE, EndpointError, DispersionError, ResidenceTimeError, ConnectivityError
- Dominant language
- Python
- Stars
- 1
- Forks
- 0
- Avg merge
- 13d 21h
- Merged PRs (30d)
- 3
Description
## Summary
Skill scores over trajectory Datasets. Live in `xr_toolz.metrics.lagrangian` (per D12) so they sit alongside other metrics rather than inside the physics module.
## API target
```python
class TrajectoryRMSE(Operator): ... # mean sqrt((Δlon)² + (Δlat)²) over time/particles
class EndpointError(Operator): ... # great-circle distance at final time
class EndpointErrorMap(Operator): ... # binned (horizon, lat, lon) deviation map
class DispersionError(Operator): ... # bias of pair_dispersion(pred) vs pair_dispersion(ref)
class ResidenceTimeError(Operator): ... # per-region residence-time MAE
class ConnectivityError(Operator): ... # Frobenius norm of connectivity-matrix difference
```
### `EndpointErrorMap` — binned deviation diagnostic
Geographic map of paired-track distances at one or more elapsed-time horizons. Sits between scalar `EndpointError` and a generic `bin_residuals_2d` reduction. Matches the canonical drifter-eval visualisation from [`ocean-data-challenges/2024c_DC_4DMedSea-ESA/src/mod_traj.py:compute_deviation`](https://github.com/ocean-data-challenges/2024c_DC_4DMedSea-ESA/blob/main/src/mod_traj.py).
```python
class EndpointErrorMap(Operator):
def __init__(
self, *,
lon_bins: ArrayLike,
lat_bins: ArrayLike,
horizons: Sequence[pd.Timedelta] | None = None,
statistic: Literal["mean", "median", "rmse"] = "mean",
traj_dim: str = "particle", time_dim: str = "time",
lon: str = "lon", lat: str = "lat",
): ...
def __call__(self, traj_pred: xr.Dataset, traj_ref: xr.Dataset) -> xr.Dataset:
# Returns Dataset with dims (horizon, lat_bin, lon_bin)
...
```
Implementation: paired great-circle distance per `(particle, time)`, time-slice by `horizons`, then `scipy.stats.binned_statistic_2d` (reuses ODC-1.4 `bin_residuals_2d` pattern) with statistic-by-name dispatch.
## Acceptance criteria
- [ ] All six implemented in `metrics/_src/lagrangian.py` with Layer 0 + Layer 1.
- [ ] All accept two trajectory Datasets that conform to V3.1 schema.
- [ ] `EndpointError`: identical trajectories → 0; documented haversine-vs-flat-earth choice.
- [ ] `EndpointErrorMap`: identical trajectories → all-zero map; positive everywhere on a divergent twin; respects `horizons=` selection; `statistic="rmse"` matches `sqrt(mean(d²))`.
- [ ] `DispersionError`: zero on identical trajectories; positive on a divergent twin run.
- [ ] `TrajectoryRMSE` reduces over `particle` and `time` per the constructor `dims=` arg.
- [ ] All metrics compose inside a `Graph` (multi-input pattern from architecture.md).
Contributor guide
Research direction
Start with metrics/_src/lagrangian.py, the V3.1 trajectory schema, and the multi-input Graph pattern in architecture.md. Review the ODC-1.4 bin_residuals_2d pattern before implementing the six Layer 0 and Layer 1 operators. Done means both trajectory Datasets are accepted, the listed identical and divergent cases pass, horizon and statistic options work, and the metrics compose in a Graph.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- numpy, pandas, python
- Domain
- data
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100