@@ -181,39 +181,48 @@ Regression = Utilities.createClass(
181
181
// All samples are analyzed. startIndex, endIndex are just stored for use by the caller.
182
182
function ( samples , startIndex , endIndex , options )
183
183
{
184
- const desiredFrameLength = options . desiredFrameLength ;
185
- var profile ;
184
+ this . segment1 = {
185
+ s : 0 ,
186
+ t : 0 ,
187
+ n : 0 ,
188
+ e : Number . MAX_VALUE
189
+ } ;
186
190
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 ,
191
+ this . segment2 = {
192
+ s : 0 ,
193
+ t : 0 ,
194
+ n : 0 ,
195
+ e : Number . MAX_VALUE
196
+ } ;
197
+
198
+ if ( options . preferredProfile == Strings . json . profiles . flat ) {
199
+ this . _calculateRegression ( samples , {
200
+ s1 : options . desiredFrameLength ,
198
201
t1 : 0 ,
199
202
t2 : 0
200
203
} ) ;
201
204
this . profile = Strings . json . profiles . flat ;
205
+ } else {
206
+ this . _calculateRegression ( samples , {
207
+ s1 : options . desiredFrameLength ,
208
+ t1 : 0
209
+ } ) ;
210
+ this . profile = Strings . json . profiles . slope ;
202
211
}
203
212
204
213
this . startIndex = Math . min ( startIndex , endIndex ) ;
205
214
this . endIndex = Math . max ( startIndex , endIndex ) ;
206
215
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 ;
216
+ this . complexity = this . _complexity ( ) ;
217
+ this . s1 = this . segment1 . s ;
218
+ this . t1 = this . segment1 . t ;
219
+ this . s2 = this . segment2 . s ;
220
+ this . t2 = this . segment2 . t ;
221
+ this . stdev1 = Math . sqrt ( this . segment1 . e / this . segment1 . n ) ;
222
+ this . stdev2 = Math . sqrt ( this . segment2 . n / this . segment2 . n ) ;
223
+ this . n1 = this . segment1 . n ;
224
+ this . n2 = this . segment2 . n ;
225
+ this . error = this . _error ( ) ;
217
226
} , {
218
227
219
228
valueAt : function ( complexity )
@@ -223,6 +232,32 @@ Regression = Utilities.createClass(
223
232
return this . s1 + this . t1 * complexity ;
224
233
} ,
225
234
235
+ _complexity : function ( )
236
+ {
237
+ // The score is the x coordinate of the intersection of segment1 and segment2.
238
+ return ( this . segment1 . s - this . segment2 . s ) / ( this . segment2 . t - this . segment1 . t ) ;
239
+ } ,
240
+
241
+ _error : function ( ) {
242
+ return this . segment1 . e + this . segment2 . e ;
243
+ } ,
244
+
245
+ _setOptimal : function ( segment1 , segment2 , options ) {
246
+ if ( segment1 . e + segment2 . e > this . segment1 . e + this . segment2 . e )
247
+ return false ;
248
+
249
+ this . segment1 . s = options . s1 !== undefined ? options . s1 : segment1 . s ;
250
+ this . segment1 . t = options . t1 !== undefined ? options . t1 : segment1 . t ;
251
+ this . segment1 . e = segment1 . e ;
252
+ this . segment1 . n = segment1 . n ;
253
+
254
+ this . segment2 . s = options . s2 !== undefined ? options . s2 : segment2 . s ;
255
+ this . segment2 . t = options . t2 !== undefined ? options . t2 : segment2 . t ;
256
+ this . segment2 . e = segment2 . e ;
257
+ this . segment2 . n = segment2 . n ;
258
+ return true ;
259
+ } ,
260
+
226
261
// A generic two-segment piecewise regression calculator. Based on Kundu/Ubhaya
227
262
//
228
263
// Minimize sum of (y - y')^2
@@ -239,153 +274,136 @@ Regression = Utilities.createClass(
239
274
const complexityIndex = 0 ;
240
275
const frameLengthIndex = 1 ;
241
276
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
-
256
277
// Sort by increasing complexity.
257
278
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 ] ;
262
279
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 ;
280
+ let a1 = 0 , b1 = 0 , c1 = 0 , d1 = 0 , h1 = 0 , k1 = 0 ;
281
+ let a2 = 0 , b2 = 0 , c2 = 0 , d2 = 0 , h2 = 0 , k2 = 0 ;
282
+
283
+ for ( var j = 0 ; j < sortedSamples . length ; ++ j ) {
284
+ let x = sortedSamples [ j ] [ complexityIndex ] ;
285
+ let y = sortedSamples [ j ] [ frameLengthIndex ] ;
265
286
266
- for ( var i = 0 ; i < sortedSamples . length ; ++ i ) {
267
- var x = sortedSamples [ i ] [ complexityIndex ] ;
268
- var y = sortedSamples [ i ] [ frameLengthIndex ] ;
269
287
a2 += 1 ;
270
288
b2 += x ;
271
289
c2 += x * x ;
272
290
d2 += y ;
273
- h2 += y * x ;
291
+ h2 += x * y ;
274
292
k2 += y * y ;
275
293
}
276
294
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
- }
295
+ for ( let j = 0 ; j < sortedSamples . length - 1 ; ++ j ) {
296
+ let x = sortedSamples [ j ] [ complexityIndex ] ;
297
+ let y = sortedSamples [ j ] [ frameLengthIndex ] ;
298
+ let xx = x * x ;
299
+ let xy = x * y ;
300
+ let yy = y * y ;
298
301
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
307
302
a1 += 1 ;
308
303
b1 += x ;
309
304
c1 += xx ;
310
305
d1 += y ;
311
- h1 += yx ;
306
+ h1 += xy ;
312
307
k1 += yy ;
313
- // a2, b2, etc. is sum from i+1 to sortedSamples.length - 1, inclusive
308
+
314
309
a2 -= 1 ;
315
310
b2 -= x ;
316
311
c2 -= xx ;
317
312
d2 -= y ;
318
- h2 -= yx ;
313
+ h2 -= xy ;
319
314
k2 -= yy ;
320
315
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
- }
316
+ let A = ( c1 * d1 ) - ( b1 * h1 ) ;
317
+ let B = ( a1 * h1 ) - ( b1 * d1 ) ;
318
+ let C = ( a1 * c1 ) - ( b1 * b1 ) ;
319
+ let D = ( c2 * d2 ) - ( b2 * h2 ) ;
320
+ let E = ( a2 * h2 ) - ( b2 * d2 ) ;
321
+ let F = ( a2 * c2 ) - ( b2 * b2 ) ;
322
+
323
+ let s1 = A / C ;
324
+ let t1 = B / C ;
325
+ let s2 = D / F ;
326
+ let t2 = E / F ;
341
327
342
328
if ( C == 0 || F == 0 )
343
329
continue ;
344
330
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 ;
331
+ let segment1 ;
332
+ let segment2 ;
333
+
334
+ if ( j == 0 ) {
335
+ // Let segment1 be any line through (x0, y0) which meets segment2 at
336
+ // a point (x’, y’) where x[0] < x' < x[1]. segment1 has no error.
337
+ let xMid = ( x + sortedSamples [ j + 1 ] [ complexityIndex ] ) / 2 ;
338
+ let yMid = s2 + t2 * xMid ;
339
+ let tMid = ( yMid - y ) / ( xMid - x ) ;
340
+ segment1 = {
341
+ s : y - tMid * x ,
342
+ t : tMid ,
343
+ n : 1 ,
344
+ e : 0
345
+ } ;
346
+ } else {
347
+ segment1 = {
348
+ s : s1 ,
349
+ t : t1 ,
350
+ n : j + 1 ,
351
+ e : k1 + ( a1 * s1 * s1 ) + ( c1 * t1 * t1 ) - ( 2 * d1 * s1 ) - ( 2 * h1 * t1 ) + ( 2 * b1 * s1 * t1 )
352
+ } ;
371
353
}
372
354
373
- if ( error1 + error2 < error1_best + error2_best )
374
- setBest ( s1 , t1 , error1 , s2 , t2 , error2 , i , x_prime , x ) ;
375
- }
355
+ if ( j == sortedSamples . length - 2 ) {
356
+ // Let segment2 be any line through (x[n - 1], y[n - 1]) which meets segment1
357
+ // at a point (x’, y’) where x[n - 2] < x' < x[n - 1]. segment2 has no error.
358
+ let xMid = ( x + sortedSamples [ j + 1 ] [ complexityIndex ] ) / 2 ;
359
+ let yMid = s1 + t1 * xMid ;
360
+ let tMid = ( yMid - sortedSamples [ j + 1 ] [ frameLengthIndex ] ) / ( xMid - sortedSamples [ j + 1 ] [ complexityIndex ] ) ;
361
+ segment2 = {
362
+ s : y - tMid * x ,
363
+ t : tMid ,
364
+ n : 1 ,
365
+ e : 0
366
+ } ;
367
+ } else {
368
+ segment2 = {
369
+ s : s2 ,
370
+ t : t2 ,
371
+ n : sortedSamples . length - ( j + 1 ) ,
372
+ e : k2 + ( a2 * s2 * s2 ) + ( c2 * t2 * t2 ) - ( 2 * d2 * s2 ) - ( 2 * h2 * t2 ) + ( 2 * b2 * s2 * t2 )
373
+ } ;
374
+ }
376
375
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
- } ;
376
+ if ( this . _setOptimal ( segment1 , segment2 , options ) )
377
+ continue
378
+
379
+ let G = A + B * x - C * y ;
380
+ let H = D + E * x - F * y ;
381
+
382
+ let I = c1 - 2 * b1 * x + a1 * xx ;
383
+ let K = c2 - 2 * b2 * x + a2 * xx ;
384
+
385
+ let lambda = ( G * F + G * K - H * C ) / ( I * H + G * K ) ;
386
+ if ( ! ( lambda > 0 && lambda < 1 ) )
387
+ continue ;
388
+
389
+ let lambda1 = 1 - lambda ;
390
+
391
+ segment1 = {
392
+ s : ( A + lambda * ( - h1 * x + d1 * xx + c1 * y - b1 * xy ) ) / ( C - lambda * I ) ,
393
+ t : ( B + lambda * ( h1 - d1 * x - b1 * y + a1 * xy ) ) / ( C - lambda * I ) ,
394
+ n : j + 1 ,
395
+ 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 )
396
+ } ;
397
+
398
+ segment2 = {
399
+ s : ( D + lambda1 * ( - h2 * x + d2 * xx + c2 * y - b2 * xy ) ) / ( F + lambda1 * K ) ,
400
+ t : ( E + lambda1 * ( h2 - d2 * x - b2 * y + a2 * xy ) ) / ( F + lambda1 * K ) ,
401
+ n : sortedSamples . length - ( j + 1 ) ,
402
+ 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 )
403
+ } ;
404
+
405
+ this . _setOptimal ( segment1 , segment2 , options ) ;
406
+ }
389
407
}
390
408
} ) ;
391
409
0 commit comments