Skip to content

Commit cbf968c

Browse files
committed
history: rework the tdtv
Better explanations and rationales for the various invariants. Improved the computation and highlighted its origins and alternatives.
1 parent 2c9c6d6 commit cbf968c

File tree

1 file changed

+53
-16
lines changed

1 file changed

+53
-16
lines changed

pkg/kv/kvserver/asim/history/thrashing.go

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -83,25 +83,62 @@ func computeThrashing(values []float64) thrashing {
8383
// respectively.
8484
//
8585
// Properties:
86-
// - tdtv(u,d) = tdtv(d,u) (symmetry)
87-
// - min(u,d) <= tdtv(u,d) <= u+d
88-
// - tdtv(u,0) = tdtv(0,u) = 0
89-
// - tdtv(u,u) = 2⋅u
90-
// - tdtv(k⋅u,k⋅d) = k⋅tdtv(u,d) for k≥0
91-
// - tdtv(l⋅u, d) > tdtv(u) for l>1 (same in second argument)
86+
// - tdtv(u,d) = tdtv(d,u) (symmetry)
87+
// We want this property because we don't want to privilege either direction
88+
// of swing.
89+
// - min(u,d) <= tdtv(u,d) <= u+d
90+
// We want the upper bound because u+d is the total variation, and we are
91+
// measuring a trend-discounting version of that.
92+
// We want the lower bound so that even when there is a dominant trend, we
93+
// don't discount for more than that dominant trend.
94+
// - tdtv(u,0) = tdtv(0,u) = 0
95+
// In other words, monotonic functions have zero tdtv, as they should.
96+
// - tdtv(u,u) = 2⋅u
97+
// If there is no dominant direction, we want tdtv to equal the total variation.
98+
// - tdtv(k⋅u,k⋅d) = k⋅tdtv(u,d) for k≥0
99+
// Scaling property: we want tdtv to be determined by the ratio of upwards
100+
// and downwards variations only. i.e. it is unitless: tdtv(u,d)=tdtv(u/d,1).
101+
// - tdtv(l⋅u, d) > tdtv(u, d) for l>1 and u, d ≠ 0 (concavity)
102+
// Strict monotonicty in each component - if either variation increases,
103+
// this must not reduce tdtv. In other words, additional variation will
104+
// always be penalized; there is no way for variations to "offset each
105+
// other".
92106
func tdtv(u, d float64) float64 {
93-
tmin := min(u, d)
94-
if tmin == 0 {
95-
// There's only one direction of movement, so we discount all variation.
107+
if u > d {
108+
u, d = d, u
109+
// We may now assume that u <= d.
110+
}
111+
112+
if u == 0 {
113+
// The smaller direction is zero, so the function is monotonic.
96114
return 0
97115
}
98-
frac := tmin / max(u, d) // in [0, 1]
99-
// The exponent can't exceed 1 because that would violate the scaling property
100-
// (last property above). For the endpoint exponent 1, tdtv=2⋅min(u,d), and for
101-
// the endpoint exponent 0, we get the (vanilla) total variation u+d.
102-
// The choice of 0.8 is somewhat arbitrary, but gives a reasonable trade-off.
103-
alpha := math.Pow(frac, 0.8)
104-
return alpha*(u+d) + (1-alpha)*tmin
116+
// NB: 0 < u <= d.
117+
r := u / d // in [0,1]
118+
119+
// Any concave and increasing function alpha with alpha(0)=0 and alpha(1)=1
120+
// works here:
121+
// alpha(0) = 0 to satisfy tdtv(_,0) = 0
122+
// alpha(1) = 1 to satisfy tdtv(u,u) = 2⋅u
123+
//
124+
alpha := func(r float64) float64 { // [0,1] -> [0,1]
125+
// The exponent can't exceed 1 because that would violate the scaling property
126+
// (last property above).
127+
// - for p=1, we get tdtv=2⋅min(u,d), i.e. twice the smaller variation (i.e.
128+
// complete trend-discounting).
129+
// - for p=0, tdtv equals the total variation (i.e. no trend discounting)
130+
//
131+
// The choice of 0.8 is somewhat arbitrary, but gives a reasonable trade-off.
132+
//
133+
// Other choices for alpha exist, like piecewise linear functions, or
134+
// log(1+lambda*r)/log(1+lambda).
135+
const p = 0.8
136+
return math.Pow(r, p)
137+
}
138+
// Interpolate between u (smaller variation) and u+d (total variation). The
139+
// more dominant a trend there is, the smaller r, and thus alpha(r), and thus
140+
// the tdtv.
141+
return u + alpha(r)*d
105142
}
106143

107144
func extrema(vs []float64) (vmin, vmax, vrange float64) {

0 commit comments

Comments
 (0)