@@ -83,25 +83,62 @@ func computeThrashing(values []float64) thrashing {
83
83
// respectively.
84
84
//
85
85
// 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".
92
106
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.
96
114
return 0
97
115
}
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
105
142
}
106
143
107
144
func extrema (vs []float64 ) (vmin , vmax , vrange float64 ) {
0 commit comments