jejjohnson / jejjohnson/xrtoolz

V3.4: Trajectory metrics — TrajectoryRMSE, EndpointError, DispersionError, ResidenceTimeError, ConnectivityError

Open
#53 0 comments 0 reactions 0 assignees View on GitHub
area:code area:metrics enhancement validation-framework
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.