Skip to content

Commit b12b93d

Browse files
committed
Fix the two piecewise-linear regression calculation
#66 The current implementation of the regression calculation has these flaws: 1. When processing (x[0], y[0]), L1 must be any line through (x0, y0) which meets L2 at a point (x’, y’) where x[0] < x' < x[1]. L1 has no error. 2. When processing (x[n - 2], y[n - 2]), L2 must be any line through (x[n - 1], y[n - 1]) which meets L1 at a point (x’, y’) where x[n - 2] < x' < x[n - 1]. L2 has no error. 3. The lambda calculation is incorrect. It includes a term called H which is equal to C - I. Looking at the algorithm of Kundu/Ubhaya, this should be just C. 4. lambda should to be used with calculating L1 and (1 - lambda) should to be used with calculating L2. Currently (1 - lambda) is used in calculating L1 and L2. 5. The current calculation has this condition if (t1 != t2) continue; This condition is almost always true even if t1 and t2 are essentiallyEqual.
1 parent f1c7edb commit b12b93d

File tree

1 file changed

+182
-144
lines changed

1 file changed

+182
-144
lines changed

MotionMark/resources/statistics.js

Lines changed: 182 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2015-2017 Apple Inc. All rights reserved.
2+
* Copyright (C) 2015-2014 Apple Inc. All rights reserved.
33
*
44
* Redistribution and use in source and binary forms, with or without
55
* modification, are permitted provided that the following conditions
@@ -181,39 +181,39 @@ Regression = Utilities.createClass(
181181
// All samples are analyzed. startIndex, endIndex are just stored for use by the caller.
182182
function(samples, startIndex, endIndex, options)
183183
{
184-
const desiredFrameLength = options.desiredFrameLength;
185-
var profile;
184+
this.startIndex = Math.min(startIndex, endIndex);
185+
this.endIndex = Math.max(startIndex, endIndex);
186186

187-
if (!options.preferredProfile || options.preferredProfile == Strings.json.profiles.slope) {
188-
profile = this._calculateRegression(samples, {
189-
shouldClip: true,
190-
s1: desiredFrameLength,
191-
t1: 0
192-
});
193-
this.profile = Strings.json.profiles.slope;
194-
} else if (options.preferredProfile == Strings.json.profiles.flat) {
195-
profile = this._calculateRegression(samples, {
196-
shouldClip: true,
197-
s1: desiredFrameLength,
187+
this.s1 = 0;
188+
this.t1 = 0;
189+
this.n1 = 0;
190+
this.e1 = Number.MAX_VALUE;
191+
192+
this.s2 = 0;
193+
this.t2 = 0;
194+
this.n2 = 0;
195+
this.e2 = Number.MAX_VALUE;
196+
197+
this.complexity = 0;
198+
199+
if (options.preferredProfile == Strings.json.profiles.flat) {
200+
this._calculateRegression(samples, {
201+
s1: options.desiredFrameLength,
198202
t1: 0,
199203
t2: 0
200204
});
201205
this.profile = Strings.json.profiles.flat;
206+
} else {
207+
this._calculateRegression(samples, {
208+
s1: options.desiredFrameLength,
209+
t1: 0
210+
});
211+
this.profile = Strings.json.profiles.slope;
202212
}
203213

204-
this.startIndex = Math.min(startIndex, endIndex);
205-
this.endIndex = Math.max(startIndex, endIndex);
206-
207-
this.complexity = profile.complexity;
208-
this.s1 = profile.s1;
209-
this.t1 = profile.t1;
210-
this.s2 = profile.s2;
211-
this.t2 = profile.t2;
212-
this.stdev1 = profile.stdev1;
213-
this.stdev2 = profile.stdev2;
214-
this.n1 = profile.n1;
215-
this.n2 = profile.n2;
216-
this.error = profile.error;
214+
this.stdev1 = Math.sqrt(this.e1 / this.n1);
215+
this.stdev2 = Math.sqrt(this.e2 / this.n2);
216+
this.error = this._error();
217217
}, {
218218

219219
valueAt: function(complexity)
@@ -223,6 +223,58 @@ Regression = Utilities.createClass(
223223
return this.s1 + this.t1 * complexity;
224224
},
225225

226+
_intersection: function(segment1, segment2)
227+
{
228+
return (segment1.s - segment2.s) / (segment2.t - segment1.t);
229+
},
230+
231+
_error: function() {
232+
return this.e1 + this.e2;
233+
},
234+
235+
_areEssentiallyEqual: function(n1, n2) {
236+
const epsilon = 0.0001;
237+
return Math.abs(n1 - n2) < epsilon;
238+
},
239+
240+
_setOptimal: function(segment1, segment2, x, xn, options) {
241+
if (segment1.e + segment2.e > this.e1 + this.e2)
242+
return false;
243+
244+
segment1.s = options.s1 !== undefined ? options.s1 : segment1.s;
245+
segment1.t = options.t1 !== undefined ? options.t1 : segment1.t;
246+
segment2.s = options.s2 !== undefined ? options.s2 : segment2.s;
247+
segment2.t = options.t2 !== undefined ? options.t2 : segment2.t;
248+
249+
// The score is the x coordinate of the intersection of segment1 and segment2.
250+
let complexity = this._intersection(segment1, segment2);
251+
252+
if (!this._areEssentiallyEqual(segment1.t, segment2.t)) {
253+
// If segment1 and segment2 are not parallel, then they have to meet
254+
// at complexity such that xp < complexity < x.
255+
if (!(complexity >= x && complexity <= xn))
256+
return false;
257+
} else {
258+
// If segment1 and segment2 are parallel, then they have to form one
259+
// single line.
260+
if (!this._areEssentiallyEqual(segment1.s, segment2.s))
261+
return false;
262+
}
263+
264+
this.s1 = segment1.s;
265+
this.t1 = segment1.t;
266+
this.n1 = segment1.n;
267+
this.e1 = segment1.e;
268+
269+
this.s2 = segment2.s;
270+
this.t2 = segment2.t;
271+
this.n2 = segment2.n;
272+
this.e2 = segment2.e;
273+
274+
this.complexity = complexity;
275+
return true;
276+
},
277+
226278
// A generic two-segment piecewise regression calculator. Based on Kundu/Ubhaya
227279
//
228280
// Minimize sum of (y - y')^2
@@ -239,153 +291,139 @@ Regression = Utilities.createClass(
239291
const complexityIndex = 0;
240292
const frameLengthIndex = 1;
241293

242-
if (samples.length == 1) {
243-
// Only one sample point; we can't calculate any regression.
244-
var x = samples[0][complexityIndex];
245-
return {
246-
complexity: x,
247-
s1: x,
248-
t1: 0,
249-
s2: x,
250-
t2: 0,
251-
error1: 0,
252-
error2: 0
253-
};
254-
}
255-
256294
// Sort by increasing complexity.
257295
var sortedSamples = samples.slice().sort((a, b) => a[complexityIndex] - b[complexityIndex]);
258-
259-
// x is expected to increase in complexity
260-
var lowComplexity = sortedSamples[0][complexityIndex];
261-
var highComplexity = sortedSamples[samples.length - 1][complexityIndex];
262296

263-
var a1 = 0, b1 = 0, c1 = 0, d1 = 0, h1 = 0, k1 = 0;
264-
var a2 = 0, b2 = 0, c2 = 0, d2 = 0, h2 = 0, k2 = 0;
297+
let a1 = 0, b1 = 0, c1 = 0, d1 = 0, h1 = 0, k1 = 0;
298+
let a2 = 0, b2 = 0, c2 = 0, d2 = 0, h2 = 0, k2 = 0;
299+
300+
for (var j = 0; j < sortedSamples.length; ++j) {
301+
let x = sortedSamples[j][complexityIndex];
302+
let y = sortedSamples[j][frameLengthIndex];
265303

266-
for (var i = 0; i < sortedSamples.length; ++i) {
267-
var x = sortedSamples[i][complexityIndex];
268-
var y = sortedSamples[i][frameLengthIndex];
269304
a2 += 1;
270305
b2 += x;
271306
c2 += x * x;
272307
d2 += y;
273-
h2 += y * x;
308+
h2 += x * y;
274309
k2 += y * y;
275310
}
276311

277-
var s1_best, t1_best, s2_best, t2_best, n1_best, n2_best, error1_best, error2_best, x_best, x_prime;
278-
279-
function setBest(s1, t1, error1, s2, t2, error2, splitIndex, x_prime, x)
280-
{
281-
s1_best = s1;
282-
t1_best = t1;
283-
error1_best = error1;
284-
s2_best = s2;
285-
t2_best = t2;
286-
error2_best = error2;
287-
// Number of samples included in the first segment, inclusive of splitIndex
288-
n1_best = splitIndex + 1;
289-
// Number of samples included in the second segment
290-
n2_best = samples.length - splitIndex - 1;
291-
if (!options.shouldClip || (x_prime >= lowComplexity && x_prime <= highComplexity))
292-
x_best = x_prime;
293-
else {
294-
// Discontinuous piecewise regression
295-
x_best = x;
296-
}
297-
}
312+
for (let j = 0; j < sortedSamples.length - 1; ++j) {
313+
let x = sortedSamples[j][complexityIndex];
314+
let y = sortedSamples[j][frameLengthIndex];
315+
let xx = x * x;
316+
let xy = x * y;
317+
let yy = y * y;
298318

299-
// Iterate from 0 to n - 2, inclusive
300-
for (var i = 0; i < sortedSamples.length - 1; ++i) {
301-
var x = sortedSamples[i][complexityIndex];
302-
var y = sortedSamples[i][frameLengthIndex];
303-
var xx = x * x;
304-
var yx = y * x;
305-
var yy = y * y;
306-
// a1, b1, etc. is sum from 0 to i, inclusive
307319
a1 += 1;
308320
b1 += x;
309321
c1 += xx;
310322
d1 += y;
311-
h1 += yx;
323+
h1 += xy;
312324
k1 += yy;
313-
// a2, b2, etc. is sum from i+1 to sortedSamples.length - 1, inclusive
325+
314326
a2 -= 1;
315327
b2 -= x;
316328
c2 -= xx;
317329
d2 -= y;
318-
h2 -= yx;
330+
h2 -= xy;
319331
k2 -= yy;
320332

321-
var A = c1*d1 - b1*h1;
322-
var B = a1*h1 - b1*d1;
323-
var C = a1*c1 - b1*b1;
324-
var D = c2*d2 - b2*h2;
325-
var E = a2*h2 - b2*d2;
326-
var F = a2*c2 - b2*b2;
327-
var s1 = options.s1 !== undefined ? options.s1 : (A / C);
328-
var t1 = options.t1 !== undefined ? options.t1 : (B / C);
329-
var s2 = options.s2 !== undefined ? options.s2 : (D / F);
330-
var t2 = options.t2 !== undefined ? options.t2 : (E / F);
331-
// Assumes that the two segments meet
332-
var x_prime = (s1 - s2) / (t2 - t1);
333-
334-
var error1 = (k1 + a1*s1*s1 + c1*t1*t1 - 2*d1*s1 - 2*h1*t1 + 2*b1*s1*t1) || Number.MAX_VALUE;
335-
var error2 = (k2 + a2*s2*s2 + c2*t2*t2 - 2*d2*s2 - 2*h2*t2 + 2*b2*s2*t2) || Number.MAX_VALUE;
336-
337-
if (i == 0) {
338-
setBest(s1, t1, error1, s2, t2, error2, i, x_prime, x);
339-
continue;
340-
}
333+
let A = (c1 * d1) - (b1 * h1);
334+
let B = (a1 * h1) - (b1 * d1);
335+
let C = (a1 * c1) - (b1 * b1);
336+
let D = (c2 * d2) - (b2 * h2);
337+
let E = (a2 * h2) - (b2 * d2);
338+
let F = (a2 * c2) - (b2 * b2);
339+
340+
let s1 = A / C;
341+
let t1 = B / C;
342+
let s2 = D / F;
343+
let t2 = E / F;
341344

342345
if (C == 0 || F == 0)
343346
continue;
344347

345-
// Projected point is not between this and the next sample
346-
var nextSampleComplexity = sortedSamples[i + 1][complexityIndex];
347-
if (x_prime > nextSampleComplexity || x_prime < x) {
348-
// Calculate lambda, which divides the weight of this sample between the two lines
349-
350-
// These values remove the influence of this sample
351-
var I = c1 - 2*b1*x + a1*xx;
352-
var H = C - I;
353-
var G = A + B*x - C*y;
354-
355-
var J = D + E*x - F*y;
356-
var K = c2 - 2*b2*x + a2*xx;
357-
358-
var lambda = (G*F + G*K - H*J) / (I*J + G*K);
359-
if (lambda > 0 && lambda < 1) {
360-
var lambda1 = 1 - lambda;
361-
s1 = options.s1 !== undefined ? options.s1 : ((A - lambda1*(-h1*x + d1*xx + c1*y - b1*yx)) / (C - lambda1*I));
362-
t1 = options.t1 !== undefined ? options.t1 : ((B - lambda1*(h1 - d1*x - b1*y + a1*yx)) / (C - lambda1*I));
363-
s2 = options.s2 !== undefined ? options.s2 : ((D + lambda1*(-h2*x + d2*xx + c2*y - b2*yx)) / (F + lambda1*K));
364-
t2 = options.t2 !== undefined ? options.t2 : ((E + lambda1*(h2 - d2*x - b2*y + a2*yx)) / (F + lambda1*K));
365-
x_prime = (s1 - s2) / (t2 - t1);
366-
367-
error1 = ((k1 + a1*s1*s1 + c1*t1*t1 - 2*d1*s1 - 2*h1*t1 + 2*b1*s1*t1) - lambda1 * Math.pow(y - (s1 + t1*x), 2)) || Number.MAX_VALUE;
368-
error2 = ((k2 + a2*s2*s2 + c2*t2*t2 - 2*d2*s2 - 2*h2*t2 + 2*b2*s2*t2) + lambda1 * Math.pow(y - (s2 + t2*x), 2)) || Number.MAX_VALUE;
369-
} else if (t1 != t2)
370-
continue;
348+
let segment1;
349+
let segment2;
350+
let xp = (j == 0) ? 0 : sortedSamples[j - 1][complexityIndex];
351+
352+
if (j == 0) {
353+
// Let segment1 be any line through (x[0], y[0]) which meets segment2 at
354+
// a point (x’, y’) where x[0] < x' < x[1]. segment1 has no error.
355+
let xMid = (x + sortedSamples[j + 1][complexityIndex]) / 2;
356+
let yMid = s2 + t2 * xMid;
357+
let tMid = (yMid - y) / (xMid - x);
358+
segment1 = {
359+
s: y - tMid * x,
360+
t: tMid,
361+
n: 1,
362+
e: 0
363+
};
364+
} else {
365+
segment1 = {
366+
s: s1,
367+
t: t1,
368+
n: j + 1,
369+
e: k1 + (a1 * s1 * s1) + (c1 * t1 * t1) - (2 * d1 * s1) - (2 * h1 * t1) + (2 * b1 * s1 * t1)
370+
};
371371
}
372372

373-
if (error1 + error2 < error1_best + error2_best)
374-
setBest(s1, t1, error1, s2, t2, error2, i, x_prime, x);
375-
}
373+
if (j == sortedSamples.length - 2) {
374+
// Let segment2 be any line through (x[n - 1], y[n - 1]) which meets segment1
375+
// at a point (x’, y’) where x[n - 2] < x' < x[n - 1]. segment2 has no error.
376+
let xMid = (x + sortedSamples[j + 1][complexityIndex]) / 2;
377+
let yMid = s1 + t1 * xMid;
378+
let tMid = (yMid - sortedSamples[j + 1][frameLengthIndex]) / (xMid - sortedSamples[j + 1][complexityIndex]);
379+
segment2 = {
380+
s: y - tMid * x,
381+
t: tMid,
382+
n: 1,
383+
e: 0
384+
};
385+
} else {
386+
segment2 = {
387+
s: s2,
388+
t: t2,
389+
n: sortedSamples.length - (j + 1),
390+
e: k2 + (a2 * s2 * s2) + (c2 * t2 * t2) - (2 * d2 * s2) - (2 * h2 * t2) + (2 * b2 * s2 * t2)
391+
};
392+
}
376393

377-
return {
378-
complexity: x_best,
379-
s1: s1_best,
380-
t1: t1_best,
381-
stdev1: Math.sqrt(error1_best / n1_best),
382-
s2: s2_best,
383-
t2: t2_best,
384-
stdev2: Math.sqrt(error2_best / n2_best),
385-
error: error1_best + error2_best,
386-
n1: n1_best,
387-
n2: n2_best
388-
};
394+
if (this._setOptimal(segment1, segment2, x, sortedSamples[j + 1][complexityIndex], options))
395+
continue
396+
397+
// These values remove the influence of this sample
398+
let G = A + B * x - C * y;
399+
let J = D + E * x - F * y;
400+
401+
let I = c1 - 2 * b1 * x + a1 * xx;
402+
let K = c2 - 2 * b2 * x + a2 * xx;
403+
404+
// Calculate lambda, which divides the weight of this sample between the two lines
405+
let lambda = (G * F + G * K - J * C) / (I * J + G * K);
406+
if (!(lambda > 0 && lambda < 1))
407+
continue;
408+
409+
let lambda1 = 1 - lambda;
410+
411+
segment1 = {
412+
s: (A + lambda * (-h1 * x + d1 * xx + c1 * y - b1 * xy)) / (C - lambda * I),
413+
t: (B + lambda * (h1 - d1 * x - b1 * y + a1 * xy)) / (C - lambda * I),
414+
n: j + 1,
415+
e: (k1 + a1 * s1 * s1 + c1 * t1 * t1 - 2 * d1 * s1 - 2 * h1 * t1 + 2 * b1 * s1 * t1) - lambda * Math.pow(y - (s1 + t1 * x), 2)
416+
};
417+
418+
segment2 = {
419+
s: (D + lambda1 * (-h2 * x + d2 * xx + c2 * y - b2 * xy)) / (F + lambda1 * K),
420+
t: (E + lambda1 * (h2 - d2 * x - b2 * y + a2 * xy)) / (F + lambda1 * K),
421+
n: sortedSamples.length - (j + 1),
422+
e: (k2 + a2 * s2 * s2 + c2 * t2 * t2 - 2 * d2 * s2 - 2 * h2 * t2 + 2 * b2 * s2 * t2) + lambda1 * Math.pow(y - (s2 + t2 * x), 2)
423+
};
424+
425+
this._setOptimal(segment1, segment2, x, sortedSamples[j + 1][complexityIndex], options);
426+
}
389427
}
390428
});
391429

0 commit comments

Comments
 (0)