1
1
/*
2
- * Copyright (C) 2015-2017 Apple Inc. All rights reserved.
2
+ * Copyright (C) 2015-2024 Apple Inc. All rights reserved.
3
3
*
4
4
* Redistribution and use in source and binary forms, with or without
5
5
* modification, are permitted provided that the following conditions
@@ -181,39 +181,39 @@ 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 . startIndex = Math . min ( startIndex , endIndex ) ;
185
+ this . endIndex = Math . max ( startIndex , endIndex ) ;
186
186
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 ,
198
202
t1 : 0 ,
199
203
t2 : 0
200
204
} ) ;
201
205
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 ;
202
212
}
203
213
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 ( ) ;
217
217
} , {
218
218
219
219
valueAt : function ( complexity )
@@ -223,6 +223,60 @@ Regression = Utilities.createClass(
223
223
return this . s1 + this . t1 * complexity ;
224
224
} ,
225
225
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
+ // Choose epsilon not too small to ensure the intersetion
237
+ // of the two segements is not too far from sampled data.
238
+ const epsilon = 0.0001 ;
239
+ return Math . abs ( n1 - n2 ) < epsilon ;
240
+ } ,
241
+
242
+ _setOptimal : function ( segment1 , segment2 , x , xn , options ) {
243
+ if ( segment1 . e + segment2 . e > this . e1 + this . e2 )
244
+ return false ;
245
+
246
+ segment1 . s = options . s1 !== undefined ? options . s1 : segment1 . s ;
247
+ segment1 . t = options . t1 !== undefined ? options . t1 : segment1 . t ;
248
+ segment2 . s = options . s2 !== undefined ? options . s2 : segment2 . s ;
249
+ segment2 . t = options . t2 !== undefined ? options . t2 : segment2 . t ;
250
+
251
+ // The score is the x coordinate of the intersection of segment1 and segment2.
252
+ let complexity = this . _intersection ( segment1 , segment2 ) ;
253
+
254
+ if ( ! this . _areEssentiallyEqual ( segment1 . t , segment2 . t ) ) {
255
+ // If segment1 and segment2 are not parallel, then they have to meet
256
+ // at complexity such that x <= complexity <= xn.
257
+ if ( ! ( complexity >= x && complexity <= xn ) )
258
+ return false ;
259
+ } else {
260
+ // If segment1 and segment2 are parallel, then they have to form one
261
+ // single line.
262
+ if ( ! this . _areEssentiallyEqual ( segment1 . s , segment2 . s ) )
263
+ return false ;
264
+ }
265
+
266
+ this . s1 = segment1 . s ;
267
+ this . t1 = segment1 . t ;
268
+ this . n1 = segment1 . n ;
269
+ this . e1 = segment1 . e ;
270
+
271
+ this . s2 = segment2 . s ;
272
+ this . t2 = segment2 . t ;
273
+ this . n2 = segment2 . n ;
274
+ this . e2 = segment2 . e ;
275
+
276
+ this . complexity = complexity ;
277
+ return true ;
278
+ } ,
279
+
226
280
// A generic two-segment piecewise regression calculator. Based on Kundu/Ubhaya
227
281
//
228
282
// Minimize sum of (y - y')^2
@@ -239,153 +293,139 @@ Regression = Utilities.createClass(
239
293
const complexityIndex = 0 ;
240
294
const frameLengthIndex = 1 ;
241
295
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
296
// Sort by increasing complexity.
257
- 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
-
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 ;
265
-
266
- for ( var i = 0 ; i < sortedSamples . length ; ++ i ) {
267
- var x = sortedSamples [ i ] [ complexityIndex ] ;
268
- var y = sortedSamples [ i ] [ frameLengthIndex ] ;
297
+ let sortedSamples = samples . slice ( ) . sort ( ( a , b ) => a [ complexityIndex ] - b [ complexityIndex ] ) ;
298
+
299
+ let a1 = 0 , b1 = 0 , c1 = 0 , d1 = 0 , h1 = 0 , k1 = 0 ;
300
+ let a2 = 0 , b2 = 0 , c2 = 0 , d2 = 0 , h2 = 0 , k2 = 0 ;
301
+
302
+ for ( let j = 0 ; j < sortedSamples . length ; ++ j ) {
303
+ let x = sortedSamples [ j ] [ complexityIndex ] ;
304
+ let y = sortedSamples [ j ] [ frameLengthIndex ] ;
305
+
269
306
a2 += 1 ;
270
307
b2 += x ;
271
308
c2 += x * x ;
272
309
d2 += y ;
273
- h2 += y * x ;
310
+ h2 += x * y ;
274
311
k2 += y * y ;
275
312
}
276
313
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
- }
314
+ for ( let j = 0 ; j < sortedSamples . length - 1 ; ++ j ) {
315
+ let x = sortedSamples [ j ] [ complexityIndex ] ;
316
+ let y = sortedSamples [ j ] [ frameLengthIndex ] ;
317
+ let xx = x * x ;
318
+ let xy = x * y ;
319
+ let yy = y * y ;
298
320
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
321
a1 += 1 ;
308
322
b1 += x ;
309
323
c1 += xx ;
310
324
d1 += y ;
311
- h1 += yx ;
325
+ h1 += xy ;
312
326
k1 += yy ;
313
- // a2, b2, etc. is sum from i+1 to sortedSamples.length - 1, inclusive
327
+
314
328
a2 -= 1 ;
315
329
b2 -= x ;
316
330
c2 -= xx ;
317
331
d2 -= y ;
318
- h2 -= yx ;
332
+ h2 -= xy ;
319
333
k2 -= yy ;
320
334
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
- }
335
+ let A = ( c1 * d1 ) - ( b1 * h1 ) ;
336
+ let B = ( a1 * h1 ) - ( b1 * d1 ) ;
337
+ let C = ( a1 * c1 ) - ( b1 * b1 ) ;
338
+ let D = ( c2 * d2 ) - ( b2 * h2 ) ;
339
+ let E = ( a2 * h2 ) - ( b2 * d2 ) ;
340
+ let F = ( a2 * c2 ) - ( b2 * b2 ) ;
341
+
342
+ let s1 = A / C ;
343
+ let t1 = B / C ;
344
+ let s2 = D / F ;
345
+ let t2 = E / F ;
341
346
342
347
if ( C == 0 || F == 0 )
343
348
continue ;
344
349
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 ;
350
+ let segment1 ;
351
+ let segment2 ;
352
+ let xp = ( j == 0 ) ? 0 : sortedSamples [ j - 1 ] [ complexityIndex ] ;
353
+
354
+ if ( j == 0 ) {
355
+ // Let segment1 be any line through (x[0], y[0]) which meets segment2 at
356
+ // a point (x’, y’) where x[0] < x' < x[1]. segment1 has no error.
357
+ let xMid = ( x + sortedSamples [ j + 1 ] [ complexityIndex ] ) / 2 ;
358
+ let yMid = s2 + t2 * xMid ;
359
+ let tMid = ( yMid - y ) / ( xMid - x ) ;
360
+ segment1 = {
361
+ s : y - tMid * x ,
362
+ t : tMid ,
363
+ n : 1 ,
364
+ e : 0
365
+ } ;
366
+ } else {
367
+ segment1 = {
368
+ s : s1 ,
369
+ t : t1 ,
370
+ n : j + 1 ,
371
+ e : k1 + ( a1 * s1 * s1 ) + ( c1 * t1 * t1 ) - ( 2 * d1 * s1 ) - ( 2 * h1 * t1 ) + ( 2 * b1 * s1 * t1 )
372
+ } ;
371
373
}
372
374
373
- if ( error1 + error2 < error1_best + error2_best )
374
- setBest ( s1 , t1 , error1 , s2 , t2 , error2 , i , x_prime , x ) ;
375
- }
375
+ if ( j == sortedSamples . length - 2 ) {
376
+ // Let segment2 be any line through (x[n - 1], y[n - 1]) which meets segment1
377
+ // at a point (x’, y’) where x[n - 2] < x' < x[n - 1]. segment2 has no error.
378
+ let xMid = ( x + sortedSamples [ j + 1 ] [ complexityIndex ] ) / 2 ;
379
+ let yMid = s1 + t1 * xMid ;
380
+ let tMid = ( yMid - sortedSamples [ j + 1 ] [ frameLengthIndex ] ) / ( xMid - sortedSamples [ j + 1 ] [ complexityIndex ] ) ;
381
+ segment2 = {
382
+ s : y - tMid * x ,
383
+ t : tMid ,
384
+ n : 1 ,
385
+ e : 0
386
+ } ;
387
+ } else {
388
+ segment2 = {
389
+ s : s2 ,
390
+ t : t2 ,
391
+ n : sortedSamples . length - ( j + 1 ) ,
392
+ e : k2 + ( a2 * s2 * s2 ) + ( c2 * t2 * t2 ) - ( 2 * d2 * s2 ) - ( 2 * h2 * t2 ) + ( 2 * b2 * s2 * t2 )
393
+ } ;
394
+ }
376
395
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
- } ;
396
+ if ( this . _setOptimal ( segment1 , segment2 , x , sortedSamples [ j + 1 ] [ complexityIndex ] , options ) )
397
+ continue
398
+
399
+ // These values remove the influence of this sample
400
+ let G = A + B * x - C * y ;
401
+ let J = D + E * x - F * y ;
402
+
403
+ let I = c1 - 2 * b1 * x + a1 * xx ;
404
+ let K = c2 - 2 * b2 * x + a2 * xx ;
405
+
406
+ // Calculate lambda, which divides the weight of this sample between the two lines
407
+ let lambda = ( G * F + G * K - J * C ) / ( I * J + G * K ) ;
408
+ if ( ! ( lambda > 0 && lambda < 1 ) )
409
+ continue ;
410
+
411
+ let lambda1 = 1 - lambda ;
412
+
413
+ segment1 = {
414
+ s : ( A + lambda * ( - h1 * x + d1 * xx + c1 * y - b1 * xy ) ) / ( C - lambda * I ) ,
415
+ t : ( B + lambda * ( h1 - d1 * x - b1 * y + a1 * xy ) ) / ( C - lambda * I ) ,
416
+ n : j + 1 ,
417
+ 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 )
418
+ } ;
419
+
420
+ segment2 = {
421
+ s : ( D + lambda1 * ( - h2 * x + d2 * xx + c2 * y - b2 * xy ) ) / ( F + lambda1 * K ) ,
422
+ t : ( E + lambda1 * ( h2 - d2 * x - b2 * y + a2 * xy ) ) / ( F + lambda1 * K ) ,
423
+ n : sortedSamples . length - ( j + 1 ) ,
424
+ 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 )
425
+ } ;
426
+
427
+ this . _setOptimal ( segment1 , segment2 , x , sortedSamples [ j + 1 ] [ complexityIndex ] , options ) ;
428
+ }
389
429
}
390
430
} ) ;
391
431
0 commit comments