Skip to content

Commit 469c5d8

Browse files
authored
Improved numeric robustness of angular distance calculation
As angular distance is composed of many floating point additions, it is changed in a increment counter followed by a multiplication to reduce floating point errors from escalating.
1 parent 51a2278 commit 469c5d8

File tree

1 file changed

+25
-16
lines changed

1 file changed

+25
-16
lines changed

app/engine/Flywheel.js

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,11 @@ export function createFlywheel (rowerSettings) {
6161
const cyclicErrorFilter = createCyclicErrorFilter(rowerSettings, minimumDragFactorSamples, recoveryDeltaTime)
6262
const strokedetectionMinimalGoodnessOfFit = rowerSettings.minimumStrokeQuality
6363
const minimumRecoverySlope = createWeighedSeries(rowerSettings.dragFactorSmoothing, rowerSettings.minimumRecoverySlope)
64-
let totalTime
65-
let currentAngularDistance
64+
let rawTime = 0
65+
let rawNumberOfImpulses = 0
66+
let totalTimeSpinning = 0
67+
let totalNumberOfImpulses = 0
68+
let _totalWork = 0
6669
let _deltaTimeBeforeFlank = {}
6770
let _angularVelocityAtBeginFlank
6871
let _angularVelocityBeforeFlank
@@ -72,9 +75,6 @@ export function createFlywheel (rowerSettings) {
7275
let _torqueBeforeFlank
7376
let inRecoveryPhase
7477
let maintainMetrics
75-
let totalNumberOfImpulses
76-
let totalTimeSpinning
77-
let _totalWork
7878
reset()
7979

8080
/**
@@ -138,14 +138,15 @@ export function createFlywheel (rowerSettings) {
138138
}
139139

140140
const cleanCurrentDt = cyclicErrorFilter.applyFilter(dataPoint, totalNumberOfImpulses + flankLength)
141-
totalTime += cleanCurrentDt.value
142-
currentAngularDistance += angularDisplacementPerImpulse
141+
rawTime += cleanCurrentDt.value
142+
rawNumberOfImpulses++
143+
const currentAngularDistance = rawNumberOfImpulses * angularDisplacementPerImpulse
143144

144145
// Let's feed the stroke detection algorithm
145-
_deltaTime.push(totalTime, cleanCurrentDt.value)
146+
_deltaTime.push(rawTime, cleanCurrentDt.value, cleanCurrentDt.goodnessOfFit)
146147

147148
// Calculate the metrics that are needed for more advanced metrics, like the foce curve
148-
_angularDistance.push(totalTime, currentAngularDistance)
149+
_angularDistance.push(rawTime, currentAngularDistance, cleanCurrentDt.goodnessOfFit)
149150
_angularVelocityAtBeginFlank = _angularDistance.firstDerivative(0)
150151
_angularAccelerationAtBeginFlank = _angularDistance.secondDerivative(0)
151152
_torqueAtBeginFlank = (rowerSettings.flywheelInertia * _angularAccelerationAtBeginFlank + drag.weighedAverage() * Math.pow(_angularVelocityAtBeginFlank, 2))
@@ -190,10 +191,10 @@ export function createFlywheel (rowerSettings) {
190191
if (rowerSettings.autoAdjustRecoverySlope) {
191192
// We are allowed to autoadjust stroke detection slope as well, so let's do that
192193
minimumRecoverySlope.push((1 - rowerSettings.autoAdjustRecoverySlopeMargin) * recoveryDeltaTime.slope(), recoveryDeltaTime.goodnessOfFit())
193-
log.debug(`*** Calculated recovery slope: ${recoveryDeltaTime.slope().toFixed(6)}, Goodness of Fit: ${recoveryDeltaTime.goodnessOfFit().toFixed(4)}`)
194+
log.trace(`*** Calculated recovery slope: ${recoveryDeltaTime.slope().toFixed(6)}, Goodness of Fit: ${recoveryDeltaTime.goodnessOfFit().toFixed(4)}`)
194195
} else {
195196
// We aren't allowed to adjust the slope, let's report the slope to help help the user configure it
196-
log.debug(`*** Calculated recovery slope: ${recoveryDeltaTime.slope().toFixed(6)}, Goodness of Fit: ${recoveryDeltaTime.goodnessOfFit().toFixed(4)}, not used as autoAdjustRecoverySlope isn't set to true`)
197+
log.trace(`*** Calculated recovery slope: ${recoveryDeltaTime.slope().toFixed(6)}, Goodness of Fit: ${recoveryDeltaTime.goodnessOfFit().toFixed(4)}, not used as autoAdjustRecoverySlope isn't set to true`)
197198
}
198199
} else {
199200
// As the drag calculation is considered unreliable, we must skip updating the systematic error filter that depends on it
@@ -397,24 +398,32 @@ export function createFlywheel (rowerSettings) {
397398
}
398399
}
399400

401+
/**
402+
* @param {float} slope - Recovery slope to be converted
403+
* @returns {float} Dragfactor to be used in all calculations
404+
* @description Helper function to convert a recovery slope into a dragfactor
405+
*/
400406
function slopeToDrag (slope) {
401407
return ((slope * rowerSettings.flywheelInertia) / angularDisplacementPerImpulse)
402408
}
403409

410+
/**
411+
* @description This function is used for clearing all data, returning the flywheel.js to its initial state
412+
*/
404413
function reset () {
405414
maintainMetrics = false
406415
inRecoveryPhase = false
416+
rawTime = 0
417+
rawNumberOfImpulses = 0
418+
totalTimeSpinning = 0
419+
totalNumberOfImpulses = -1
420+
_totalWork = 0
407421
drag.reset()
408422
cyclicErrorFilter.reset()
409423
cyclicErrorFilter.applyFilter(0, flankLength - 1)
410424
recoveryDeltaTime.reset()
411425
_deltaTime.reset()
412426
_angularDistance.reset()
413-
totalTime = 0
414-
currentAngularDistance = 0
415-
totalNumberOfImpulses = -1
416-
totalTimeSpinning = 0
417-
_totalWork = 0
418427
_deltaTime.push(0, 0)
419428
_angularDistance.push(0, 0)
420429
_deltaTimeBeforeFlank.clean = 0

0 commit comments

Comments
 (0)