openrowingmonitor/app/engine/utils/FullTSQuadraticSeries.test.js

547 lines
21 KiB
JavaScript

'use strict'
/*
Open Rowing Monitor, https://github.com/laberning/openrowingmonitor
This tests the Quadratic Theil-Senn Regression algorithm. As regression is an estimation and methods have biasses,
we need to accept some slack with respect to real-life examples
*/
import { test } from 'uvu'
import * as assert from 'uvu/assert'
import { createTSQuadraticSeries } from './FullTSQuadraticSeries.js'
test('Quadratic Approximation startup behaviour', () => {
const dataSeries = createTSQuadraticSeries(10)
testCoefficientA(dataSeries, 0)
testCoefficientB(dataSeries, 0)
testCoefficientC(dataSeries, 0)
dataSeries.push(-1, 2)
testCoefficientA(dataSeries, 0)
testCoefficientB(dataSeries, 0)
testCoefficientC(dataSeries, 0)
dataSeries.push(0, 2)
testCoefficientA(dataSeries, 0)
testCoefficientB(dataSeries, 0)
testCoefficientC(dataSeries, 0)
dataSeries.push(1, 6)
testCoefficientA(dataSeries, 2)
testCoefficientB(dataSeries, 2)
testCoefficientC(dataSeries, 2)
})
test('Quadratic Approximation on a perfect noisefree function y = 2 * Math.pow(x, 2) + 2 * x + 2, 21 datapoints', () => {
// Data based on 2 x^2 + 2 x + 2
const dataSeries = createTSQuadraticSeries(21)
dataSeries.push(-10, 182)
dataSeries.push(-9, 146)
dataSeries.push(-8, 114)
dataSeries.push(-7, 86)
dataSeries.push(-6, 62)
dataSeries.push(-5, 42)
dataSeries.push(-4, 26)
dataSeries.push(-3, 14) // Pi ;)
dataSeries.push(-2, 6)
dataSeries.push(-1, 2)
dataSeries.push(0, 2)
dataSeries.push(1, 6)
dataSeries.push(2, 14)
dataSeries.push(3, 26)
dataSeries.push(4, 42)
dataSeries.push(5, 62)
dataSeries.push(6, 86)
dataSeries.push(7, 114)
dataSeries.push(8, 146)
dataSeries.push(9, 182)
dataSeries.push(10, 222)
testCoefficientA(dataSeries, 2)
testCoefficientB(dataSeries, 2)
testCoefficientC(dataSeries, 2)
})
test('Quadratic Approximation on a perfect noisefree function y = 2 * Math.pow(x, 2) + 2 * x + 2, with 10 datapoints and some shifting in the series', () => {
// Data based on 2 x^2 + 2 x + 2, split the dataset in two to see its behaviour when it is around the Vertex
const dataSeries = createTSQuadraticSeries(10)
dataSeries.push(-10, 182)
dataSeries.push(-9, 146)
dataSeries.push(-8, 114)
dataSeries.push(-7, 86)
dataSeries.push(-6, 62)
dataSeries.push(-5, 42)
dataSeries.push(-4, 26)
dataSeries.push(-3, 14) // Pi ;)
dataSeries.push(-2, 6)
dataSeries.push(-1, 2)
dataSeries.push(0, 2)
testCoefficientA(dataSeries, 2)
testCoefficientB(dataSeries, 2)
testCoefficientC(dataSeries, 2)
dataSeries.push(1, 6)
dataSeries.push(2, 14)
dataSeries.push(3, 26)
dataSeries.push(4, 42)
dataSeries.push(5, 62)
dataSeries.push(6, 86)
dataSeries.push(7, 114)
dataSeries.push(8, 146)
dataSeries.push(9, 182)
dataSeries.push(10, 222)
testCoefficientA(dataSeries, 2)
testCoefficientB(dataSeries, 2)
testCoefficientC(dataSeries, 2)
})
test('Quadratic Approximation on function y = 4 * Math.pow(x, 2) + 4 * x + 4, noisefree', () => {
// Data based on 4 x^2 + 4 x + 4
const dataSeries = createTSQuadraticSeries(11)
dataSeries.push(-11, 444)
dataSeries.push(-10, 364)
dataSeries.push(-9, 292)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(-8, 228)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(-7, 172)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(-6, 124)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(-5, 84)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(-4, 52)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(-3, 28)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(-2, 12)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(-1, 4)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(0, 4)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(1, 12)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(2, 28)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(3, 52)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(4, 84)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(5, 124)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(6, 172)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(7, 228)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(8, 292)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(9, 364)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(10, 444)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
})
test('Quadratic Approximation on function y = 4 * Math.pow(x, 2) + 4 * x + 4, with some noise (+/- 1)', () => {
// Data based on 4 x^2 + 4 x + 4
const dataSeries = createTSQuadraticSeries(11)
dataSeries.push(-11, 443)
dataSeries.push(-10, 365)
dataSeries.push(-9, 291)
testCoefficientA(dataSeries, 2)
testCoefficientB(dataSeries, -36)
testCoefficientC(dataSeries, -195)
dataSeries.push(-8, 229)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(-7, 171)
testCoefficientA(dataSeries, 3.3333333333333335)
testCoefficientB(dataSeries, -7.999999999999995)
testCoefficientC(dataSeries, -48.333333333333314)
dataSeries.push(-6, 125)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(-5, 83)
testCoefficientA(dataSeries, 3.8666666666666667)
testCoefficientB(dataSeries, 1.8666666666666742)
testCoefficientC(dataSeries, -4.3333333333332575) // This is quite acceptable as ORM ignores the C
dataSeries.push(-4, 53)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(-3, 27)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 3)
dataSeries.push(-2, 13)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(-1, 3)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 3)
dataSeries.push(0, 5)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 5)
dataSeries.push(1, 11)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 3)
dataSeries.push(2, 29)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 5)
dataSeries.push(3, 51)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 3)
dataSeries.push(4, 85)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 5)
dataSeries.push(5, 123)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 3)
dataSeries.push(6, 173)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 5)
dataSeries.push(7, 227)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 3)
dataSeries.push(8, 293)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 5)
dataSeries.push(9, 363)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(10, 444)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
})
test('Quadratic Approximation on function y = 4 * Math.pow(x, 2) + 4 * x + 4, with some noise (+/- 1) and spikes (+/- 9)', () => {
// Data based on 4 x^2 + 4 x + 4
const dataSeries = createTSQuadraticSeries(11)
dataSeries.push(-11, 443)
dataSeries.push(-10, 365)
dataSeries.push(-9, 291)
dataSeries.push(-8, 229)
dataSeries.push(-7, 171)
dataSeries.push(-6, 125)
dataSeries.push(-5, 83)
dataSeries.push(-4, 53)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
dataSeries.push(-3, 37) // FIRST SPIKE +9
testCoefficientA(dataSeries, 4.215277777777778)
testCoefficientB(dataSeries, 7.321527777777776)
testCoefficientC(dataSeries, 15.70208333333332)
dataSeries.push(-2, 3) // SECOND SPIKE -9
testCoefficientA(dataSeries, 3.9714285714285715)
testCoefficientB(dataSeries, 3.78571428571429) // Coefficient B seems to take a hit anyway
testCoefficientC(dataSeries, 4.35000000000003) // We get a 4.35000000000003 instead of 4, which is quite acceptable (especially since ORM ignores the C)
dataSeries.push(-1, 3)
testCoefficientA(dataSeries, 3.9555555555555557)
testCoefficientB(dataSeries, 3.37777777777778)
testCoefficientC(dataSeries, 2.8666666666666742)
dataSeries.push(0, 5)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 5)
dataSeries.push(1, 11)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 3)
dataSeries.push(2, 29)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 5)
dataSeries.push(3, 51)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 3)
dataSeries.push(4, 85)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 5)
dataSeries.push(5, 123)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 3)
dataSeries.push(6, 173)
testCoefficientA(dataSeries, 4.044444444444444)
testCoefficientB(dataSeries, 3.8222222222222215)
testCoefficientC(dataSeries, 3.5777777777777775)
dataSeries.push(7, 227)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 3)
dataSeries.push(8, 293)
testCoefficientA(dataSeries, 3.9047619047619047)
testCoefficientB(dataSeries, 4.761904761904762)
testCoefficientC(dataSeries, 2.47619047619048) // This is quite acceptable as ORM ignores the C
dataSeries.push(9, 363)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4) // We get a 3 instead of 4, which is quite acceptable (especially since ORM ignores the C)
dataSeries.push(10, 444)
testCoefficientA(dataSeries, 4)
testCoefficientB(dataSeries, 4)
testCoefficientC(dataSeries, 4)
})
test('Quadratic TS Estimation should be decent for standard real-life example from MathBits with some noise', () => {
// Data based on https://mathbits.com/MathBits/TISection/Statistics2/quadratic.html
const dataSeries = createTSQuadraticSeries(13)
dataSeries.push(10, 115.6)
dataSeries.push(15, 157.2)
dataSeries.push(20, 189.2)
dataSeries.push(24, 220.8)
dataSeries.push(30, 253.8)
dataSeries.push(34, 269.2)
dataSeries.push(40, 284.8)
dataSeries.push(45, 285.0)
dataSeries.push(48, 277.4)
dataSeries.push(50, 269.2)
dataSeries.push(58, 244.2)
dataSeries.push(60, 231.4)
dataSeries.push(64, 180.4)
testCoefficientA(dataSeries, -0.17702838827838824) // In the example, the TI084 results in -0.1737141137, which we consider acceptably close
testCoefficientB(dataSeries, 15.059093406593405) // In the example, the TI084 results in 14.52117133, which we consider acceptably close
testCoefficientC(dataSeries, -37.563076923077006) // In the example, the TI084 results in -21.89774466, which we consider acceptably close
})
test('Quadratic TS Estimation should be decent for standard real-life example from VarsityTutors with some noise', () => {
// Test based on https://www.varsitytutors.com/hotmath/hotmath_help/topics/quadratic-regression
const dataSeries = createTSQuadraticSeries(7)
dataSeries.push(-3, 7.5)
dataSeries.push(-2, 3)
dataSeries.push(-1, 0.5)
dataSeries.push(0, 1)
dataSeries.push(1, 3)
dataSeries.push(2, 6)
dataSeries.push(3, 14)
testCoefficientA(dataSeries, 1.0833333333333333) // The example results in 1.1071 for OLS, which we consider acceptably close
testCoefficientB(dataSeries, 0.9166666666666667) // The example results in 1 for OLS, which we consider acceptably close
testCoefficientC(dataSeries, 0.5000000000000004) // The example results in 0.5714 for OLS, which we consider acceptably close
})
test('Quadratic TS Estimation should be decent for standard example from VTUPulse with some noise, without the vertex being part of the dataset', () => {
// Test based on https://www.vtupulse.com/machine-learning/quadratic-polynomial-regression-model-solved-example/
const dataSeries = createTSQuadraticSeries(5)
dataSeries.push(3, 2.5)
dataSeries.push(4, 3.3)
dataSeries.push(5, 3.8)
dataSeries.push(6, 6.5)
dataSeries.push(7, 11.5)
testCoefficientA(dataSeries, 0.8583333333333334) // The example results in 0.7642857 for OLS, which we consider acceptably close given the small sample size
testCoefficientB(dataSeries, -6.566666666666666) // The example results in -5.5128571 for OLS, which we consider acceptably close given the small sample size
testCoefficientC(dataSeries, 15.174999999999994) // The example results in 12.4285714 for OLS, which we consider acceptably close given the small sample size
})
test('Quadratic TS Estimation should be decent for standard real-life example from Uni Berlin with some noise without the vertex being part of the dataset', () => {
// Test based on https://www.geo.fu-berlin.de/en/v/soga/Basics-of-statistics/Linear-Regression/Polynomial-Regression/Polynomial-Regression---An-example/index.html
const dataSeries = createTSQuadraticSeries(25)
dataSeries.push(0.001399613, -0.23436656)
dataSeries.push(0.971629779, 0.64689524)
dataSeries.push(0.579119475, -0.92635765)
dataSeries.push(0.335693937, 0.13000706)
dataSeries.push(0.736736086, -0.89294863)
dataSeries.push(0.492572335, 0.33854780)
dataSeries.push(0.737133774, -1.24171910)
dataSeries.push(0.563693769, -0.22523318)
dataSeries.push(0.877603280, -0.12962722)
dataSeries.push(0.141426545, 0.37632006)
dataSeries.push(0.307203910, 0.30299077)
dataSeries.push(0.024509308, -0.21162739)
dataSeries.push(0.843665029, -0.76468719)
dataSeries.push(0.771206067, -0.90455412)
dataSeries.push(0.149670258, 0.77097952)
dataSeries.push(0.359605608, 0.56466366)
dataSeries.push(0.049612895, 0.18897607)
dataSeries.push(0.409898906, 0.32531750)
dataSeries.push(0.935457898, -0.78703491)
dataSeries.push(0.149476207, 0.80585375)
dataSeries.push(0.234315216, 0.62944986)
dataSeries.push(0.455297119, 0.02353327)
dataSeries.push(0.102696671, 0.27621694)
dataSeries.push(0.715372314, -1.20379729)
dataSeries.push(0.681745393, -0.83059624)
testCoefficientA(dataSeries, -2.030477132951317)
testCoefficientB(dataSeries, 0.6253742507247935)
testCoefficientC(dataSeries, 0.2334077291108024)
})
test('Quadratic TS Estimation should be decent for standard real-life example from Statology.org with some noise and chaotic X values', () => {
// Test based on https://www.statology.org/quadratic-regression-r/
const dataSeries = createTSQuadraticSeries(11)
dataSeries.push(6, 14)
dataSeries.push(9, 28)
dataSeries.push(12, 50)
dataSeries.push(14, 70)
dataSeries.push(30, 89)
dataSeries.push(35, 94)
dataSeries.push(40, 90)
dataSeries.push(47, 75)
dataSeries.push(51, 59)
dataSeries.push(55, 44)
dataSeries.push(60, 27)
testCoefficientA(dataSeries, -0.10119047619047619) // The example results in -0.1012 for R after two rounds, which we consider acceptably close
testCoefficientB(dataSeries, 6.767857142857142) // The example results in 6.7444 for R after two rounds, which we consider acceptably close
testCoefficientC(dataSeries, -19.55952380952374) // The example results in 18.2536 for R after two rounds, but for ORM, this factor is irrelevant
})
test('Quadratic TS Estimation should be decent for standard real-life example from StatsDirect.com with some noise and chaotic X values', () => {
// Test based on https://www.statsdirect.com/help/regression_and_correlation/polynomial.htm
const dataSeries = createTSQuadraticSeries(10)
dataSeries.push(1290, 1182)
dataSeries.push(1350, 1172)
dataSeries.push(1470, 1264)
dataSeries.push(1600, 1493)
dataSeries.push(1710, 1571)
dataSeries.push(1840, 1711)
dataSeries.push(1980, 1804)
dataSeries.push(2230, 1840)
dataSeries.push(2400, 1956)
dataSeries.push(2930, 1954)
testCoefficientA(dataSeries, -0.00046251263566907585) // The example results in -0.00045 through QR decomposition by Givens rotations, which we consider acceptably close
testCoefficientB(dataSeries, 2.429942262608943) // The example results in 2.39893 for QR decomposition by Givens rotations, which we consider acceptably close
testCoefficientC(dataSeries, -1221.3216719814116) // The example results in -1216.143887 for QR decomposition by Givens rotations, but for ORM, this factor is irrelevant
})
test('Quadratic Approximation with a clean function and a reset', () => {
// Data based on 2 x^2 + 2 x + 2
const dataSeries = createTSQuadraticSeries(10)
dataSeries.push(-10, 182)
dataSeries.push(-9, 146)
dataSeries.push(-8, 114)
dataSeries.push(-7, 86)
dataSeries.push(-6, 62)
dataSeries.push(-5, 42)
testCoefficientA(dataSeries, 2)
testCoefficientB(dataSeries, 2)
testCoefficientC(dataSeries, 2)
dataSeries.push(-4, 26)
dataSeries.push(-3, 14) // Pi ;)
dataSeries.push(-2, 6)
dataSeries.push(-1, 2)
dataSeries.push(0, 2)
dataSeries.push(1, 6)
dataSeries.push(2, 14)
testCoefficientA(dataSeries, 2)
testCoefficientB(dataSeries, 2)
testCoefficientC(dataSeries, 2)
dataSeries.push(3, 26)
dataSeries.push(4, 42)
dataSeries.push(5, 62)
dataSeries.push(6, 86)
dataSeries.push(7, 114)
dataSeries.push(8, 146)
dataSeries.push(9, 182)
dataSeries.push(10, 222)
testCoefficientA(dataSeries, 2)
testCoefficientB(dataSeries, 2)
testCoefficientC(dataSeries, 2)
dataSeries.reset()
testCoefficientA(dataSeries, 0)
testCoefficientB(dataSeries, 0)
testCoefficientC(dataSeries, 0)
dataSeries.push(-1, 2)
testCoefficientA(dataSeries, 0)
testCoefficientB(dataSeries, 0)
testCoefficientC(dataSeries, 0)
dataSeries.push(0, 2)
testCoefficientA(dataSeries, 0)
testCoefficientB(dataSeries, 0)
testCoefficientC(dataSeries, 0)
dataSeries.push(1, 6)
testCoefficientA(dataSeries, 2)
testCoefficientB(dataSeries, 2)
testCoefficientC(dataSeries, 2)
})
test('Quadratic TS Estimation should result in a straight line for function y = x', () => {
// As ORM will encounter straight lines (when forces are balanced on the flywheel, there is no acceleration/deceleration), so we need to test this as well
const dataSeries = createTSQuadraticSeries(7)
dataSeries.push(0, 0)
dataSeries.push(1, 1)
dataSeries.push(2, 2)
dataSeries.push(3, 3)
dataSeries.push(4, 4)
dataSeries.push(5, 5)
dataSeries.push(6, 6)
testCoefficientA(dataSeries, 0)
testCoefficientB(dataSeries, 1)
testCoefficientC(dataSeries, 0)
})
function testCoefficientA (series, expectedValue) {
assert.ok(series.coefficientA() === expectedValue, `Expected value for coefficientA at X-position ${series.xAtSeriesEnd()} is ${expectedValue}, encountered a ${series.coefficientA()}`)
}
function testCoefficientB (series, expectedValue) {
assert.ok(series.coefficientB() === expectedValue, `Expected value for coefficientB at X-position ${series.xAtSeriesEnd()} is ${expectedValue}, encountered a ${series.coefficientB()}`)
}
function testCoefficientC (series, expectedValue) {
assert.ok(series.coefficientC() === expectedValue, `Expected value for coefficientC at X-position ${series.xAtSeriesEnd()} is ${expectedValue}, encountered a ${series.coefficientC()}`)
}
/*
function testSlope (series, position, expectedValue) {
assert.ok(series.slope(position) === expectedValue, `Expected value for Slope-${position} at X-position ${series.xAtSeriesEnd()} (slope at X-position ${series.xAtPosition(position)}) is ${expectedValue}, encountered a ${series.slope(position)}`)
}
function reportAll (series) {
assert.ok(series.coefficientA() === 99, `time: ${series.xAtSeriesEnd()}, coefficientA: ${series.coefficientA()}, coefficientB: ${series.coefficientB()}, coefficientC: ${series.coefficientC()}, Slope-10: ${series.slope(10)}, Slope-9: ${series.slope(9)}, Slope-8: ${series.slope(8)}, Slope-7: ${series.slope(7)}, Slope-6: ${series.slope(6)}, Slope-5: ${series.slope(5)}, Slope-4: ${series.slope(4)}, Slope-3: ${series.slope(3)}, Slope-2: ${series.slope(2)}, Slope-1: ${series.slope(1)}, Slope-0: ${series.slope(0)}`)
}
*/
test.run()