294 this.debuggingSegmentation = false;
295
296 function splitIntoSegmentsUntilGoodEnough(values) {
297 if (values.length < 2)
298 return [0, values.length];
299
300 var matrix = new SampleVarianceUpperTriangularMatrix(values);
301
302 var SchwarzCriterionBeta = Math.log1p(values.length - 1) / values.length;
303
304 var BirgeAndMassartC = 2.5; // Suggested by the authors.
305 var BirgeAndMassartPenalization = function (segmentCount) {
306 return segmentCount * (1 + BirgeAndMassartC * Math.log1p(values.length / segmentCount - 1));
307 }
308
309 var segmentation;
310 var minTotalCost = Infinity;
311 var maxK = 50;
312
313 for (var k = 1; k < maxK; k++) {
314 var start = Date.now();
315 var result = findOptimalSegmentation(values, matrix, k);
316 var cost = result.cost / values.length;
317 var penalty = SchwarzCriterionBeta * BirgeAndMassartPenalization(k);
318 if (cost + penalty < minTotalCost) {
319 minTotalCost = cost + penalty;
320 segmentation = result.segmentation;
321 } else
322 maxK = Math.min(maxK, k + 3);
323 if (Statistics.debuggingSegmentation)
324 console.log('splitIntoSegmentsUntilGoodEnough', k, Date.now() - start, cost + penalty);
325 }
326
327 return segmentation;
328 }
329
330 function findOptimalSegmentation(values, costMatrix, segmentCount) {
331 // Dynamic programming. cost[i][k] = The cost to segmenting values up to i into k segments.
332 var cost = new Array(values.length);
333 for (var i = 0; i < values.length; i++) {
334 cost[i] = new Float32Array(segmentCount + 1);
335 }
336
337 var previousNode = new Array(values.length);
338 for (var i = 0; i < values.length; i++)
339 previousNode[i] = new Array(segmentCount + 1);
340
341 cost[0] = [0]; // The cost of segmenting single value is always 0.
342 previousNode[0] = [-1];
343 for (var segmentStart = 0; segmentStart < values.length; segmentStart++) {
344 var costBySegment = cost[segmentStart];
345 for (var count = 0; count < segmentCount; count++) {
346 if (previousNode[segmentStart][count] === undefined)
347 continue;
348 for (var segmentEnd = segmentStart + 1; segmentEnd < values.length; segmentEnd++) {
349 var newCost = costBySegment[count] + costMatrix.costBetween(segmentStart, segmentEnd);
350 if (previousNode[segmentEnd][count + 1] === undefined || newCost < cost[segmentEnd][count + 1]) {
351 cost[segmentEnd][count + 1] = newCost;
352 previousNode[segmentEnd][count + 1] = segmentStart;
353 }
354 }
355 }
356 }
357
358 if (Statistics.debuggingSegmentation) {
359 console.log('findOptimalSegmentation with k=', segmentCount);
360 for (var i = 0; i < cost.length; i++) {
361 var t = cost[i];
362 var s = '';
363 for (var j = 0; j < t.length; j++) {
364 var p = previousNode[i][j];
365 s += '(k=' + j;
366 if (p !== undefined)
367 s += ' c=' + t[j] + ' p=' + p
368 s += ')';
369 }
370 console.log(i, values[i], s);
371 }
372 }
373
374 var currentIndex = values.length - 1;
375 var segmentation = new Array(segmentCount);
376 segmentation[0] = values.length;
377 for (var i = 0; i < segmentCount; i++) {
378 currentIndex = previousNode[currentIndex][segmentCount - i];
379 segmentation[i + 1] = currentIndex;
380 }
381
382 return {segmentation: segmentation.reverse(), cost: cost[values.length - 1][segmentCount]};
383 }
384
385 function SampleVarianceUpperTriangularMatrix(values) {
386 // The cost of segment (i, j].
387 var costMatrix = new Array(values.length - 1);
388 for (var i = 0; i < values.length - 1; i++) {
389 var remainingValueCount = values.length - i - 1;
390 costMatrix[i] = new Float32Array(remainingValueCount);
391 var sum = values[i];
392 var squareSum = sum * sum;
393 costMatrix[i][0] = 0;
394 for (var j = i + 1; j < values.length; j++) {
395 var currentValue = values[j];
396 sum += currentValue;
397 squareSum += currentValue * currentValue;
398 var sampleSize = j - i + 1;
399 var stdev = Statistics.sampleStandardDeviation(sampleSize, sum, squareSum);
400 costMatrix[i][j - i - 1] = sampleSize * Math.log1p(stdev * stdev - 1);
401 }
402 }
403 this.costMatrix = costMatrix;
404 }
405
406 SampleVarianceUpperTriangularMatrix.prototype.costBetween = function(from, to) {
407 if (from >= this.costMatrix.length || from == to)
408 return 0; // The cost of the segment that starts at the last data point is 0.
409 return this.costMatrix[from][to - from - 1];
410 }
411