tslearn's 1d-SAX represents each segment by an average symbol and a slope symbol and reconstructs
the segment as the line avg + slope * (t - pivot). The 1d-SAX distance is defined as the Euclidean
distance between the two reconstructed piecewise-linear series, which the estimator produces with
inverse_transform. The reconstruction and the slope fit use the segment centroid t0 + (seg_sz-1)/2
as the pivot, but the distance uses t0 + seg_sz/2, half a timestep off. So
OneD_SymbolicAggregateApproximation.distance disagrees with the Euclidean distance between the
model's own reconstructions whenever two segments differ in slope, by a bias proportional to the
slope difference.
tslearn, tslearn/metrics/cysax.py, 0.9.0 and current main (byte-identical):
# cyslopes (line 107-121): the slope is fit over the indices t0 .. t0+sz-1, centroid t0 + (sz-1)/2
vec_t = np.arange(t0, t0 + sz).reshape((-1, 1))
# cydist_1d_sax (line 174): the distance pivot is t0 + seg_sz/2
t_middle = float(t0) + 0.5 * seg_sz
... s += (avg1 + slope1 * (tt - t_middle) - (avg2 + slope2 * (tt - t_middle))) ** 2
# inv_transform_1d_sax (line 217): the reconstruction pivot is t0 + (seg_sz-1)/2
t_middle = float(t0) + 0.5 * (seg_sz - 1)The slope is measured about the centroid t0 + (seg_sz-1)/2, and the reconstruction places it there,
but the distance evaluates the line about t0 + seg_sz/2, half a step later. The difference of the
two reconstructions at sample t is (avg1-avg2) + (slope1-slope2)*(t - pivot), so the half-step
pivot shift changes it by (slope1-slope2)/2 at every sample. The distance is therefore
self-inconsistent with the reconstruction that defines it; the term vanishes only when the two slopes
are equal.
Driving the real estimator (fp64):
distance_1d_sax 4.83954 vs reconstruction L2 4.34056 (11.5% error)
buggy pivot reproduces est.distance: True
corrected pivot matches reconstruction L2: True (4.34056 vs 4.34056)
The 1d-SAX distance between two words is 4.83954, but the Euclidean distance between the estimator's
own inverse_transform reconstructions of the same words is 4.34056, an 11.5 percent discrepancy.
Recomputing the distance sum with the buggy pivot t0 + seg_sz/2 reproduces the shipped distance
exactly, and recomputing it with the reconstruction pivot t0 + (seg_sz-1)/2 matches the
reconstruction distance to machine precision. Only the pivot offset changes between the two, so the
tree, the breakpoints, and the averaging are exonerated and the half-step pivot is the sole cause;
when the segment slopes are equal the offset cancels and the distance is correct.
OneD_SymbolicAggregateApproximation.distance and .distance_1d_sax are the documented public 1d-SAX
distances, and they run on default parameters over ordinary time series, silently: no exception, just
a distance that is not the Euclidean distance between the model's own reconstructions whenever segment
slopes differ, which is the generic case for real data. The error is a structural half-timestep bias,
not floating-point round-off. The fix is t_middle = t0 + 0.5*(seg_sz - 1) at line 174, matching the
reconstruction and the slope fit.
excerpt.py: the slope fit, the distance pivot, and the reconstruction pivot quoted with the flags that name the fault (BSD-2).sax.py: the segment distance under both pivots, so the two agree when the slopes are equal and differ by the slope-difference bias otherwise.consequence.py: the realdistance_1d_saxagainst the Euclidean distance of the model's own reconstructions, and the distance recomputed with the buggy and the corrected pivot.test_saxpivot.py: the pivot matters only when slopes differ and the offsets differ by half a step; the real distance disagrees with its reconstruction, the buggy pivot reproduces the estimator, and the corrected pivot matches the reconstruction.
python sax.py
python consequence.py
python test_saxpivot.py
The pivots are quoted from current main; the distances are produced by the real estimator. The fix
is to evaluate the distance about the reconstruction pivot t0 + 0.5*(seg_sz - 1), so the 1d-SAX
distance equals the Euclidean distance between the reconstructions that define it.