diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 80f5ee0b7e..90e303600d 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: - node-version: [14.x, 16.x] + node-version: [14.x, 18.x] steps: - name: Checkout repository diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..2640e1df91 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +unsafe-perm=true +user=0 diff --git a/app/ant/AntManager.js b/app/ant/AntManager.js deleted file mode 100644 index 8a6bcec4d5..0000000000 --- a/app/ant/AntManager.js +++ /dev/null @@ -1,63 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - This manager creates a module to listen to ANT+ devices. - This currently can be used to get the heart rate from ANT+ heart rate sensors. - - Requires an ANT+ USB stick, the following models might work: - - Garmin USB or USB2 ANT+ or an off-brand clone of it (ID 0x1008) - - Garmin mini ANT+ (ID 0x1009) -*/ -import log from 'loglevel' -import Ant from 'ant-plus' -import EventEmitter from 'node:events' - -function createAntManager () { - const emitter = new EventEmitter() - const antStick = new Ant.GarminStick2() - const antStick3 = new Ant.GarminStick3() - // it seems that we have to use two separate heart rate sensors to support both old and new - // ant sticks, since the library requires them to be bound before open is called - const heartrateSensor = new Ant.HeartRateSensor(antStick) - const heartrateSensor3 = new Ant.HeartRateSensor(antStick3) - - heartrateSensor.on('hbData', (data) => { - emitter.emit('heartrateMeasurement', { heartrate: data.ComputedHeartRate, batteryLevel: data.BatteryLevel }) - }) - - heartrateSensor3.on('hbData', (data) => { - emitter.emit('heartrateMeasurement', { heartrate: data.ComputedHeartRate, batteryLevel: data.BatteryLevel }) - }) - - antStick.on('startup', () => { - log.info('classic ANT+ stick found') - heartrateSensor.attach(0, 0) - }) - - antStick3.on('startup', () => { - log.info('mini ANT+ stick found') - heartrateSensor3.attach(0, 0) - }) - - antStick.on('shutdown', () => { - log.info('classic ANT+ stick lost') - }) - - antStick3.on('shutdown', () => { - log.info('mini ANT+ stick lost') - }) - - if (!antStick.open()) { - log.debug('classic ANT+ stick NOT found') - } - - if (!antStick3.open()) { - log.debug('mini ANT+ stick NOT found') - } - - return Object.assign(emitter, { - }) -} - -export { createAntManager } diff --git a/app/ble/CentralService.js b/app/ble/CentralService.js deleted file mode 100644 index f8b28a51ea..0000000000 --- a/app/ble/CentralService.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - Starts the central manager in a forked thread since noble does not like - to run in the same thread as bleno -*/ -import { createCentralManager } from './CentralManager.js' -import process from 'process' -import config from '../tools/ConfigManager.js' -import log from 'loglevel' - -log.setLevel(config.loglevel.default) -const centralManager = createCentralManager() - -centralManager.on('heartrateMeasurement', (heartrateMeasurement) => { - process.send(heartrateMeasurement) -}) diff --git a/app/ble/PeripheralManager.js b/app/ble/PeripheralManager.js deleted file mode 100644 index 12371f6f63..0000000000 --- a/app/ble/PeripheralManager.js +++ /dev/null @@ -1,91 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - This manager creates the different Bluetooth Low Energy (BLE) Peripherals and allows - switching between them -*/ -import config from '../tools/ConfigManager.js' -import { createFtmsPeripheral } from './FtmsPeripheral.js' -import { createPm5Peripheral } from './Pm5Peripheral.js' -import log from 'loglevel' -import EventEmitter from 'node:events' - -const modes = ['FTMS', 'FTMSBIKE', 'PM5'] -function createPeripheralManager () { - const emitter = new EventEmitter() - let peripheral - let mode - - createPeripheral(config.bluetoothMode) - - function getPeripheral () { - return peripheral - } - - function getPeripheralMode () { - return mode - } - - function switchPeripheralMode (newMode) { - // if now mode was passed, select the next one from the list - if (newMode === undefined) { - newMode = modes[(modes.indexOf(mode) + 1) % modes.length] - } - createPeripheral(newMode) - } - - function notifyMetrics (type, metrics) { - peripheral.notifyData(type, metrics) - } - - function notifyStatus (status) { - peripheral.notifyStatus(status) - } - - async function createPeripheral (newMode) { - if (peripheral) { - await peripheral.destroy() - } - - if (newMode === 'PM5') { - log.info('bluetooth profile: Concept2 PM5') - peripheral = createPm5Peripheral(controlCallback) - mode = 'PM5' - } else if (newMode === 'FTMSBIKE') { - log.info('bluetooth profile: FTMS Indoor Bike') - peripheral = createFtmsPeripheral(controlCallback, { - simulateIndoorBike: true - }) - mode = 'FTMSBIKE' - } else { - log.info('bluetooth profile: FTMS Rower') - peripheral = createFtmsPeripheral(controlCallback, { - simulateIndoorBike: false - }) - mode = 'FTMS' - } - peripheral.triggerAdvertising() - - emitter.emit('control', { - req: { - name: 'peripheralMode', - peripheralMode: mode - } - }) - } - - function controlCallback (event) { - emitter.emit('control', event) - } - - return Object.assign(emitter, { - getPeripheral, - getPeripheralMode, - switchPeripheralMode, - notifyMetrics, - notifyStatus - }) -} - -export { createPeripheralManager } diff --git a/app/ble/ftms/DeviceInformationService.js b/app/ble/ftms/DeviceInformationService.js deleted file mode 100644 index 28f2f64803..0000000000 --- a/app/ble/ftms/DeviceInformationService.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - todo: Could provide some info on the device here, maybe OS, Node version etc... -*/ -import bleno from '@abandonware/bleno' - -export default class DeviceInformationService extends bleno.PrimaryService { - constructor (controlPointCallback) { - super({ - // uuid of "Device Information Service" - uuid: '180a', - characteristics: [ - ] - }) - } -} diff --git a/app/ble/ftms/IndoorBikeFeatureCharacteristic.js b/app/ble/ftms/IndoorBikeFeatureCharacteristic.js deleted file mode 100644 index 4c01098157..0000000000 --- a/app/ble/ftms/IndoorBikeFeatureCharacteristic.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - This implements the Indoor Bike Feature Characteristic as defined by the specification. - Used to inform the Central about the features that the Open Rowing Monitor supports. - Make sure that The Fitness Machine Features and Target Setting Features that are announced here - are supported in IndoorBikeDataCharacteristic and FitnessMachineControlPointCharacteristic. -*/ -import bleno from '@abandonware/bleno' -import log from 'loglevel' - -export default class IndoorBikeDataCharacteristic extends bleno.Characteristic { - constructor (uuid, description, value) { - super({ - // Fitness Machine Feature - uuid: '2ACC', - properties: ['read'], - value: null - }) - } - - onReadRequest (offset, callback) { - // see https://www.bluetooth.com/specifications/specs/fitness-machine-service-1-0 for details - // Fitness Machine Features for the IndoorBikeDataCharacteristic - // Cadence Supported (1), Total Distance Supported (2), Expended Energy Supported (9), - // Heart Rate Measurement Supported (10), Elapsed Time Supported (12), Power Measurement Supported (14) - // 00000110 01010110 - // Target Setting Features for the IndoorBikeDataCharacteristic - // none - // 0000000 0000000 - const features = [0x06, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] - log.debug('Features of Indoor Bike requested') - callback(this.RESULT_SUCCESS, features.slice(offset, features.length)) - } -} diff --git a/app/ble/ftms/RowerFeatureCharacteristic.js b/app/ble/ftms/RowerFeatureCharacteristic.js deleted file mode 100644 index 04e929e85b..0000000000 --- a/app/ble/ftms/RowerFeatureCharacteristic.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - This implements the Rower Feature Characteristic as defined by the specification. - Used to inform the Central about the features that the Open Rowing Monitor supports. - Make sure that The Fitness Machine Features and Target Setting Features that are announced here - are supported in RowerDataCharacteristic and FitnessMachineControlPointCharacteristic. -*/ -import bleno from '@abandonware/bleno' -import log from 'loglevel' - -export default class RowerFeatureCharacteristic extends bleno.Characteristic { - constructor () { - super({ - // Fitness Machine Feature - uuid: '2ACC', - properties: ['read'], - value: null - }) - } - - onReadRequest (offset, callback) { - // see https://www.bluetooth.com/specifications/specs/fitness-machine-service-1-0 for details - // Fitness Machine Features for the RowerDataCharacteristic - // Total Distance Supported (2), Pace Supported (5), Expended Energy Supported (9), - // Heart Rate Measurement Supported (10), Elapsed Time Supported (bit 12), - // Power Measurement Supported (14) - // 00100100 01010110 - // Target Setting Features for the RowerDataCharacteristic - // none - // 0000000 0000000 - const features = [0x24, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] - log.debug('Features of Rower requested') - callback(this.RESULT_SUCCESS, features.slice(offset, features.length)) - }; -} diff --git a/app/ble/pm5/DeviceInformationService.js b/app/ble/pm5/DeviceInformationService.js deleted file mode 100644 index 9741d54269..0000000000 --- a/app/ble/pm5/DeviceInformationService.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - Provides the required Device Information of the PM5 -*/ -import bleno from '@abandonware/bleno' -import { constants, getFullUUID } from './Pm5Constants.js' -import ValueReadCharacteristic from './characteristic/ValueReadCharacteristic.js' - -export default class DeviceInformationService extends bleno.PrimaryService { - constructor () { - super({ - // InformationenService uuid as defined by the PM5 specification - uuid: getFullUUID('0010'), - characteristics: [ - // C2 module number string - new ValueReadCharacteristic(getFullUUID('0011'), constants.model, 'model'), - // C2 serial number string - new ValueReadCharacteristic(getFullUUID('0012'), constants.serial, 'serial'), - // C2 hardware revision string - new ValueReadCharacteristic(getFullUUID('0013'), constants.hardwareRevision, 'hardwareRevision'), - // C2 firmware revision string - new ValueReadCharacteristic(getFullUUID('0014'), constants.firmwareRevision, 'firmwareRevision'), - // C2 manufacturer name string - new ValueReadCharacteristic(getFullUUID('0015'), constants.manufacturer, 'manufacturer'), - // Erg Machine Type - new ValueReadCharacteristic(getFullUUID('0016'), constants.ergMachineType, 'ergMachineType') - ] - }) - } -} diff --git a/app/client/components/AppDialog.js b/app/client/components/AppDialog.js index eab5d2efae..e37a27901c 100644 --- a/app/client/components/AppDialog.js +++ b/app/client/components/AppDialog.js @@ -46,10 +46,15 @@ export class AppDialog extends AppElement { justify-content: center; align-items: center; } - button:hover { + button:hover:not(.disabled) { filter: brightness(150%); } + button.disabled { + filter: brightness(50%); + pointer: none + } + fieldset { border: 0; margin: unset; @@ -67,6 +72,8 @@ export class AppDialog extends AppElement { padding: 0; } ` + @property({ type: Boolean }) + isValid = true @property({ type: Boolean, reflect: true }) dialogOpen @@ -74,13 +81,13 @@ export class AppDialog extends AppElement { render () { return html` -
+
- - + +
@@ -95,6 +102,13 @@ export class AppDialog extends AppElement { } } + confirm () { + if (this.isValid) { + this.close({ target: { returnValue: 'confirm' } }) + this.dialogOpen = false + } + } + firstUpdated () { this.dialog.value.showModal() } diff --git a/app/client/components/AppElement.js b/app/client/components/AppElement.js index efe6c39454..59b00cabed 100644 --- a/app/client/components/AppElement.js +++ b/app/client/components/AppElement.js @@ -6,22 +6,9 @@ */ import { LitElement } from 'lit' -import { property } from 'lit/decorators.js' -import { APP_STATE } from '../store/appState.js' export * from 'lit' export class AppElement extends LitElement { - // this is how we implement a global state: a global state object is passed via properties - // to child components - @property({ type: Object }) - appState = APP_STATE - - // ..and state changes are send back to the root component of the app by dispatching - // a CustomEvent - updateState () { - this.sendEvent('appStateChanged', this.appState) - } - // a helper to dispatch events to the parent components sendEvent (eventType, eventData) { this.dispatchEvent( diff --git a/app/client/components/DashboardActions.js b/app/client/components/DashboardActions.js index 8eb53597a4..60823fc08d 100644 --- a/app/client/components/DashboardActions.js +++ b/app/client/components/DashboardActions.js @@ -6,36 +6,52 @@ */ import { AppElement, html, css } from './AppElement.js' -import { customElement, state } from 'lit/decorators.js' -import { icon_undo, icon_expand, icon_compress, icon_poweroff, icon_bluetooth, icon_upload } from '../lib/icons.js' +import { customElement, property, state } from 'lit/decorators.js' +import { icon_undo, icon_expand, icon_compress, icon_poweroff, icon_bluetooth, icon_upload, icon_heartbeat, icon_antplus } from '../lib/icons.js' import './AppDialog.js' @customElement('dashboard-actions') export class DashboardActions extends AppElement { static styles = css` button { + position: relative; outline:none; background-color: var(--theme-button-color); border: 0; border-radius: var(--theme-border-radius); color: var(--theme-font-color); - margin: 0.2em 0; + margin: 0.2em 4px; font-size: 60%; text-decoration: none; display: inline-flex; - width: 3.5em; - height: 2.5em; + width: 3.2em; + min-width: 3.2em; + height: 2.2em; justify-content: center; align-items: center; } + button:hover { filter: brightness(150%); } + button > div.text { + position: absolute; + left: 2px; + bottom: 2px; + font-size: 40%; + } + #fullscreen-icon { display: inline-flex; } + .top-button-group { + display: flex; + flex-wrap: wrap; + justify-content: center; + } + #windowed-icon { display: none; } @@ -45,7 +61,7 @@ export class DashboardActions extends AppElement { } .peripheral-mode { - font-size: 80%; + font-size: 50%; } @media (display-mode: fullscreen) { @@ -58,25 +74,56 @@ export class DashboardActions extends AppElement { } ` - @state({ type: Object }) - dialog + @property({ type: Object }) + config = {} + + @state() + _appMode = 'BROWSER' + + @state() + _dialog render () { return html` - - ${this.renderOptionalButtons()} - -
${this.peripheralMode()}
- ${this.dialog ? this.dialog : ''} +
+ + ${this.renderOptionalButtons()} + + +
+
+ +
${this.blePeripheralMode()}
+
+ ${this._dialog ? this._dialog : ''} ` } + firstUpdated () { + switch (new URLSearchParams(window.location.search).get('mode')) { + case 'standalone': + this._appMode = 'STANDALONE' + break + case 'kiosk': + this._appMode = 'KIOSK' + break + default: + this._appMode = 'BROWSER' + } + } + renderOptionalButtons () { const buttons = [] // changing to fullscreen mode only makes sence when the app is openend in a regular // webbrowser (kiosk and standalone mode are always in fullscreen view) and if the // browser supports this feature - if (this.appState?.appMode === 'BROWSER' && document.documentElement.requestFullscreen) { + if (this._appMode === 'BROWSER' && document.documentElement.requestFullscreen) { buttons.push(html` `) } - if (this.appState?.config?.stravaUploadEnabled) { + if (this.config?.stravaUploadEnabled) { buttons.push(html` `) @@ -101,16 +148,21 @@ export class DashboardActions extends AppElement { return buttons } - peripheralMode () { - const value = this.appState?.config?.peripheralMode - if (value === 'PM5') { - return 'C2 PM5' - } else if (value === 'FTMSBIKE') { - return 'FTMS Bike' - } else if (value === 'FTMS') { - return 'FTMS Rower' - } else { - return '' + blePeripheralMode () { + const value = this.config?.blePeripheralMode + switch (value) { + case 'PM5': + return 'C2 PM5' + case 'FTMSBIKE': + return 'FTMS Bike' + case 'CSC': + return 'Bike Speed + Cadence' + case 'CPS': + return 'Bike Power' + case 'FTMS': + return 'FTMS Rower' + default: + return 'Off' } } @@ -129,19 +181,27 @@ export class DashboardActions extends AppElement { this.sendEvent('triggerAction', { command: 'reset' }) } - switchPeripheralMode () { - this.sendEvent('triggerAction', { command: 'switchPeripheralMode' }) + switchBlePeripheralMode () { + this.sendEvent('triggerAction', { command: 'switchBlePeripheralMode' }) + } + + switchAntPeripheralMode () { + this.sendEvent('triggerAction', { command: 'switchAntPeripheralMode' }) + } + + switchHrmPeripheralMode () { + this.sendEvent('triggerAction', { command: 'switchHrmMode' }) } uploadTraining () { - this.dialog = html` + this._dialog = html` ${icon_upload}
Upload to Strava?

Do you want to finish your workout and upload it to Strava?

` function dialogClosed (event) { - this.dialog = undefined + this._dialog = undefined if (event.detail === 'confirm') { this.sendEvent('triggerAction', { command: 'uploadTraining' }) } @@ -149,14 +209,14 @@ export class DashboardActions extends AppElement { } shutdown () { - this.dialog = html` + this._dialog = html` ${icon_poweroff}
Shutdown Open Rowing Monitor?

Do you want to shutdown the device?

` function dialogClosed (event) { - this.dialog = undefined + this._dialog = undefined if (event.detail === 'confirm') { this.sendEvent('triggerAction', { command: 'shutdown' }) } diff --git a/app/client/components/DashboardForceCurve.js b/app/client/components/DashboardForceCurve.js new file mode 100644 index 0000000000..edf14e5ec5 --- /dev/null +++ b/app/client/components/DashboardForceCurve.js @@ -0,0 +1,125 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Component that renders a metric of the dashboard +*/ + +import { AppElement, html, css } from './AppElement.js' +import { customElement, property, state } from 'lit/decorators.js' +import ChartDataLabels from 'chartjs-plugin-datalabels' +import { Chart, Filler, Legend, LinearScale, LineController, LineElement, PointElement } from 'chart.js' + +@customElement('dashboard-force-curve') +export class DashboardForceCurve extends AppElement { + static styles = css` + canvas { + margin-top: 24px; + } + ` + + constructor () { + super() + Chart.register(ChartDataLabels, Legend, Filler, LinearScale, LineController, PointElement, LineElement) + } + + @property({ type: Object }) + value = [] + + @state() + _chart + + firstUpdated () { + const ctx = this.renderRoot.querySelector('#chart').getContext('2d') + this._chart = new Chart( + ctx, + { + type: 'line', + data: { + datasets: [ + { + fill: true, + data: this.value?.map((data, index) => ({ y: data, x: index })), + pointRadius: 1, + borderColor: 'rgb(255,255,255)', + backgroundColor: 'rgb(220,220,220)' + } + ] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + datalabels: { + anchor: 'center', + align: 'top', + formatter: (value) => `Peak: ${Math.round(value.y)}`, + display: (ctx) => Math.max( + ...ctx.dataset.data.map((point) => point.y) + ) === ctx.dataset.data[ctx.dataIndex].y, + font: { + size: 16 + }, + color: 'rgb(255,255,255)' + }, + legend: { + title: { + display: true, + text: 'Force Curve', + color: 'rgb(255,255,255)', + font: { + size: 32 + }, + padding: { + } + }, + labels: { + boxWidth: 0, + font: { + size: 0 + } + } + } + }, + scales: { + x: { + type: 'linear', + display: false + }, + y: { + ticks: { + color: 'rgb(255,255,255)' + } + } + }, + animations: { + tension: { + duration: 200, + easing: 'easeInQuad' + }, + y: { + duration: 200, + easing: 'easeInQuad' + }, + x: { + duration: 200, + easing: 'easeInQuad' + } + } + } + } + ) + } + + render () { + if (this._chart?.data) { + this._chart.data.datasets[0].data = this.value?.map((data, index) => ({ y: data, x: index })) + this.forceCurve = this.value + this._chart.update() + } + + return html` + + ` + } +} diff --git a/app/client/components/DashboardMetric.js b/app/client/components/DashboardMetric.js index 185c89f470..76f0d5ca2f 100644 --- a/app/client/components/DashboardMetric.js +++ b/app/client/components/DashboardMetric.js @@ -35,19 +35,19 @@ export class DashboardMetric extends AppElement { ` @property({ type: Object }) - icon + icon = '' @property({ type: String }) unit = '' @property({ type: String }) - value = '' + value render () { return html` -
${this.icon}
+
${this.icon}
- ${this.value !== undefined ? this.value : '--'} + ${this.value !== undefined ? this.value : '--'} ${this.unit}
diff --git a/app/client/components/PerformanceDashboard.js b/app/client/components/PerformanceDashboard.js index 0d04013f78..dbde786e67 100644 --- a/app/client/components/PerformanceDashboard.js +++ b/app/client/components/PerformanceDashboard.js @@ -6,12 +6,10 @@ */ import { AppElement, html, css } from './AppElement.js' -import { APP_STATE } from '../store/appState.js' -import { customElement, property } from 'lit/decorators.js' -import './DashboardMetric.js' -import './DashboardActions.js' -import './BatteryIcon.js' -import { icon_route, icon_stopwatch, icon_bolt, icon_paddle, icon_heartbeat, icon_fire, icon_clock } from '../lib/icons.js' +import { customElement, property, state } from 'lit/decorators.js' +import './SettingsDialog.js' +import { icon_settings } from '../lib/icons.js' +import { DASHBOARD_METRICS } from '../store/dashboardMetrics.js' @customElement('performance-dashboard') export class PerformanceDashboard extends AppElement { @@ -22,7 +20,6 @@ export class PerformanceDashboard extends AppElement { padding: 1vw; grid-gap: 1vw; grid-template-columns: repeat(4, minmax(0, 1fr)); - grid-template-rows: repeat(2, minmax(0, 1fr)); } @media (orientation: portrait) { @@ -32,7 +29,7 @@ export class PerformanceDashboard extends AppElement { } } - dashboard-metric, dashboard-actions { + dashboard-metric, dashboard-actions, dashboard-force-curve { background: var(--theme-widget-color); text-align: center; position: relative; @@ -43,64 +40,69 @@ export class PerformanceDashboard extends AppElement { dashboard-actions { padding: 0.5em 0 0 0; } + + .settings { + padding: 0.1em 0; + position: absolute; + bottom: 0; + right: 0; + z-index: 20; + } + + .settings .icon { + cursor: pointer; + height: 1em; + } + + .settings:hover .icon { + filter: brightness(150%); + } ` + @property() + appState = {} + + @state() + _dialog - @property({ type: Object }) - metrics + dashboardMetricComponentsFactory = (appState) => { + const metrics = appState.metrics + const configs = appState.config - @property({ type: Object }) - appState = APP_STATE + const dashboardMetricComponents = Object.keys(DASHBOARD_METRICS).reduce((dashboardMetrics, key) => { + dashboardMetrics[key] = DASHBOARD_METRICS[key].template(metrics, configs) + + return dashboardMetrics + }, {}) + + return dashboardMetricComponents + } render () { - const metrics = this.calculateFormattedMetrics(this.appState.metrics) + const metricConfig = [...new Set(this.appState.config.guiConfigs.dashboardMetrics)].reduce((prev, metricName) => { + prev.push(this.dashboardMetricComponentsFactory(this.appState)[metricName]) + return prev + }, []) + return html` - - - - - ${metrics?.heartrate?.value - ? html` - - ${metrics?.heartrateBatteryLevel?.value - ? html` - - ` - : '' - } - ` - : html``} - - - + +
+ ${icon_settings} + ${this._dialog ? this._dialog : ''} +
+ + ${metricConfig} ` } - // todo: so far this is just a port of the formatter from the initial proof of concept client - // we could split this up to make it more readable and testable - calculateFormattedMetrics (metrics) { - const fieldFormatter = { - distanceTotal: (value) => value >= 10000 - ? { value: (value / 1000).toFixed(1), unit: 'km' } - : { value: Math.round(value), unit: 'm' }, - caloriesTotal: (value) => Math.round(value), - power: (value) => Math.round(value), - strokesPerMinute: (value) => Math.round(value) - } + openSettings () { + this._dialog = html`` - const formattedMetrics = {} - for (const [key, value] of Object.entries(metrics)) { - const valueFormatted = fieldFormatter[key] ? fieldFormatter[key](value) : value - if (valueFormatted.value !== undefined && valueFormatted.unit !== undefined) { - formattedMetrics[key] = { - value: valueFormatted.value, - unit: valueFormatted.unit - } - } else { - formattedMetrics[key] = { - value: valueFormatted - } - } + function dialogClosed (event) { + this._dialog = undefined } - return formattedMetrics } } diff --git a/app/client/components/SettingsDialog.js b/app/client/components/SettingsDialog.js new file mode 100644 index 0000000000..8bd7fd01b9 --- /dev/null +++ b/app/client/components/SettingsDialog.js @@ -0,0 +1,255 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Component that renders the action buttons of the dashboard +*/ + +import { AppElement, html, css } from './AppElement.js' +import { customElement, property, query, queryAll, state } from 'lit/decorators.js' +import { icon_settings } from '../lib/icons.js' +import './AppDialog.js' +import { DASHBOARD_METRICS } from '../store/dashboardMetrics.js' + +@customElement('settings-dialog') +export class DashboardActions extends AppElement { + static styles = css` + .metric-selector-feedback{ + font-size: 0.5em; + padding-top: 8px; + } + + .settings-dialog>div.metric-selector{ + display: grid; + grid-template-columns: repeat(3,max-content); + gap: 8px; + } + + .experimental-settings { + display: flex; + flex-direction: column; + } + + .experimental-settings label { + width: fit-content; + margin-top: 8px; + font-size: 0.7em; + } + + .experimental-settings label>input { + font-size: 0.7em; + } + + .settings-dialog>div>label{ + font-size: 0.6em; + width: fit-content; + } + + input[type="checkbox"]{ + cursor: pointer; + align-self: center; + width: 1.5em; + height: 1.5em; + } + + label>span { + cursor: pointer; + -webkit-user-select: none; + user-select: none; + } + + .icon { + height: 1.6em; + } + + legend{ + text-align: center; + } + + table { + min-height: 70px; + margin-top: 8px; + width: 100%; + } + + table, th, td { + font-size: 0.9em; + border: 1px solid white; + border-collapse: collapse; + } + + tr { + height: 50%; + } + + th, td { + padding: 8px; + text-align: center; + background-color: var(--theme-widget-color); + } + + .show-icons-selector { + display: flex; + gap: 8px; + } + + app-dialog > *:last-child { + margin-bottom: -24px; + } + ` + + @property({ type: Object }) + config = {} + + @queryAll('.metric-selector input') + _inputs + + @query('input[name="showIcons"]') + _showIconInput + + @query('input[name="maxNumberOfTiles"]') + _maxNumberOfTilesInput + + @state() + _selectedMetrics = [] + + @state() + _sumSelectedSlots = 0 + + @state() + _isValid = false + + @state() + _showIcons = true + + @state() + _maxNumberOfTiles = 8 + + render () { + return html` + + ${icon_settings}
Settings
+ +

Select metrics to be shown:

+
+ ${this.renderAvailableMetricList()} +
+
Slots remaining: ${this._maxNumberOfTiles - this._sumSelectedSlots} + + ${this.renderSelectedMetrics()} +
+
+

+ +

+

+ Experimental settings: + +

+
+ ` + } + + firstUpdated () { + this._selectedMetrics = [...this.config.dashboardMetrics] + this._sumSelectedSlots = this._selectedMetrics.length + this._showIcons = this.config.showIcons + this._maxNumberOfTiles = this.config.maxNumberOfTiles + if (this._sumSelectedSlots === this._maxNumberOfTiles) { + this._isValid = true + } else { + this._isValid = false + } + [...this._inputs].forEach(input => { + input.checked = this._selectedMetrics.find(metric => metric === input.name) !== undefined + }) + this._showIconInput.checked = this._showIcons + this._maxNumberOfTilesInput.checked = this._maxNumberOfTiles === 12 + } + + renderAvailableMetricList () { + return Object.keys(DASHBOARD_METRICS).map(key => html` + + `) + } + + renderSelectedMetrics () { + const selectedMetrics = [html`${[0, 1, 2, 3].map(index => html`${this._selectedMetrics[index]}`)}`] + selectedMetrics.push(html`${[4, 5, 6, 7].map(index => html`${this._selectedMetrics[index]}`)}`) + if (this._maxNumberOfTiles === 12) { + selectedMetrics.push(html`${[8, 9, 10, 11].map(index => html`${this._selectedMetrics[index]}`)}`) + } + + return selectedMetrics + } + + toggleCheck (e) { + if (e.target.checked && + ((this._selectedMetrics.length % 4 === 3 && e.target.size > 1) || + (this._sumSelectedSlots + e.target.size > this._maxNumberOfTiles))) { + this._isValid = this.isFormValid() + e.target.checked = false + return + } + + if (e.target.checked) { + for (let index = 0; index < e.target.size; index++) { + this._selectedMetrics = [...this._selectedMetrics, e.target.name] + } + } else { + for (let index = 0; index < e.target.size; index++) { + this._selectedMetrics.splice(this._selectedMetrics.findIndex(metric => metric === e.target.name), 1) + this._selectedMetrics = [...this._selectedMetrics] + } + } + + this._sumSelectedSlots = this._selectedMetrics.length + if (this.isFormValid()) { + this._isValid = true + } else { + this._isValid = false + } + } + + toggleIcons (e) { + this._showIcons = e.target.checked + } + + toggleMaxTiles (e) { + this._maxNumberOfTiles = e.target.checked ? 12 : 8 + this._isValid = this.isFormValid() + } + + isFormValid () { + return this._sumSelectedSlots === this._maxNumberOfTiles && this._selectedMetrics[3] !== this._selectedMetrics[4] && this._selectedMetrics[7] !== this._selectedMetrics?.[8] + } + + close (event) { + this.dispatchEvent(new CustomEvent('close')) + if (event.detail === 'confirm') { + this.sendEvent('changeGuiSetting', { + dashboardMetrics: this._selectedMetrics, + showIcons: this._showIcons, + maxNumberOfTiles: this._maxNumberOfTiles + }) + } + } +} diff --git a/app/client/index.js b/app/client/index.js index b26dfcd4e6..b88be5bc6d 100644 --- a/app/client/index.js +++ b/app/client/index.js @@ -14,10 +14,7 @@ import './components/PerformanceDashboard.js' @customElement('web-app') export class App extends LitElement { @state() - appState = APP_STATE - - @state() - metrics + _appState = APP_STATE constructor () { super() @@ -28,6 +25,11 @@ export class App extends LitElement { // todo: we also want a mechanism here to get notified of state changes }) + const config = this._appState.config.guiConfigs + Object.keys(config).forEach(key => { + config[key] = JSON.parse(localStorage.getItem(key)) ?? config[key] + }) + // this is how we implement changes to the global state: // once any child component sends this CustomEvent we update the global state according // to the changes that were passed to us @@ -39,20 +41,28 @@ export class App extends LitElement { this.addEventListener('triggerAction', (event) => { this.app.handleAction(event.detail) }) + + // notify the app about the triggered action + this.addEventListener('changeGuiSetting', (event) => { + Object.keys(event.detail).forEach(key => { + localStorage.setItem(key, JSON.stringify(event.detail[key])) + }) + this.updateState({ config: { ...this._appState.config, guiConfigs: { ...event.detail } } }) + }) } // the global state is updated by replacing the appState with a copy of the new state // todo: maybe it is more convenient to just pass the state elements that should be changed? // i.e. do something like this.appState = { ..this.appState, ...newState } updateState = (newState) => { - this.appState = { ...newState } + this._appState = { ...this._appState, ...newState } } // return a deep copy of the state to other components to minimize risk of side effects getState = () => { // could use structuredClone once the browser support is wider // https://developer.mozilla.org/en-US/docs/Web/API/structuredClone - return JSON.parse(JSON.stringify(this.appState)) + return JSON.parse(JSON.stringify(this._appState)) } // once we have multiple views, then we would rather reference some kind of router here @@ -60,8 +70,7 @@ export class App extends LitElement { render () { return html` ` } diff --git a/app/client/lib/app.js b/app/client/lib/app.js index 90f58019fe..b5e4904885 100644 --- a/app/client/lib/app.js +++ b/app/client/lib/app.js @@ -8,16 +8,8 @@ import NoSleep from 'nosleep.js' import { filterObjectByKeys } from './helper.js' -const rowingMetricsFields = ['strokesTotal', 'distanceTotal', 'caloriesTotal', 'power', 'heartrate', - 'heartrateBatteryLevel', 'splitFormatted', 'strokesPerMinute', 'durationTotalFormatted'] - export function createApp (app) { - const urlParameters = new URLSearchParams(window.location.search) - const mode = urlParameters.get('mode') - const appMode = mode === 'standalone' ? 'STANDALONE' : mode === 'kiosk' ? 'KIOSK' : 'BROWSER' - app.updateState({ ...app.getState(), appMode }) - - const stravaAuthorizationCode = urlParameters.get('code') + const stravaAuthorizationCode = new URLSearchParams(window.location.search).get('code') let socket @@ -71,18 +63,11 @@ export function createApp (app) { const data = message.data switch (message.type) { case 'config': { - app.updateState({ ...app.getState(), config: data }) + app.updateState({ ...app.getState(), config: { ...app.getState().config, ...data } }) break } case 'metrics': { - let activeFields = rowingMetricsFields - // if we are in reset state only update heart rate - if (data.strokesTotal === 0) { - activeFields = ['heartrate', 'heartrateBatteryLevel'] - } - - const filteredData = filterObjectByKeys(data, activeFields) - app.updateState({ ...app.getState(), metrics: filteredData }) + app.updateState({ ...app.getState(), metrics: data }) break } case 'authorizeStrava': { @@ -118,14 +103,21 @@ export function createApp (app) { function resetFields () { const appState = app.getState() // drop all metrics except heartrate - appState.metrics = filterObjectByKeys(appState.metrics, ['heartrate', 'heartrateBatteryLevel']) - app.updateState(appState) + app.updateState({ ...appState, metrics: { ...filterObjectByKeys(appState.metrics, ['heartrate', 'heartRateBatteryLevel']) } }) } function handleAction (action) { switch (action.command) { - case 'switchPeripheralMode': { - if (socket)socket.send(JSON.stringify({ command: 'switchPeripheralMode' })) + case 'switchBlePeripheralMode': { + if (socket)socket.send(JSON.stringify({ command: 'switchBlePeripheralMode' })) + break + } + case 'switchAntPeripheralMode': { + if (socket)socket.send(JSON.stringify({ command: 'switchAntPeripheralMode' })) + break + } + case 'switchHrmMode': { + if (socket)socket.send(JSON.stringify({ command: 'switchHrmMode' })) break } case 'reset': { diff --git a/app/client/lib/helper.js b/app/client/lib/helper.js index 16bae1394f..a5f382b70c 100644 --- a/app/client/lib/helper.js +++ b/app/client/lib/helper.js @@ -1,4 +1,5 @@ 'use strict' + /* Open Rowing Monitor, https://github.com/laberning/openrowingmonitor @@ -14,3 +15,46 @@ export function filterObjectByKeys (object, keys) { return obj }, {}) } + +/** + * Pipe for converting seconds to a human readable time format 00:00 + * + * @param {number} seconds The actual time in seconds. +*/ +export function secondsToTimeString (timeInSeconds) { + if (timeInSeconds === undefined || timeInSeconds === null || isNaN(timeInSeconds)) return '--' + if (timeInSeconds === Infinity) return '∞' + const timeInRoundedSeconds = Math.round(timeInSeconds) + const hours = Math.floor(timeInRoundedSeconds / 3600) + const minutes = Math.floor(timeInRoundedSeconds / 60) - (hours * 60) + const seconds = Math.floor(timeInRoundedSeconds % 60) + if (hours > 0) { + return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}` + } else { + return `${minutes}:${seconds.toString().padStart(2, '0')}` + } +} + +/** + * Pipe for formatting distance in meters with units + * + * @param {number} value The distance in meters. +*/ +export function formatDistance (value) { + return value >= 99999.5 + ? { distance: formatNumber((value / 1000), 2), unit: 'km' } + : { distance: formatNumber(value), unit: 'm' } +} + +/** + * Pipe for formatting numbers to specific decimal + * + * @param {number} value The number. + * @param {number} decimalPlaces The number of decimal places to round to (default: 0). +*/ +export function formatNumber (value, decimalPlaces = 0) { + const decimal = Math.pow(10, decimalPlaces) + if (value === undefined || value === null || value === Infinity || isNaN(value) || value === 0) { return '--' } + + return Math.round(value * decimal) / decimal +} diff --git a/app/client/lib/icons.js b/app/client/lib/icons.js index 23b9a75668..4adea7a67b 100644 --- a/app/client/lib/icons.js +++ b/app/client/lib/icons.js @@ -25,3 +25,7 @@ export const icon_expand = svg`` export const icon_bluetooth = svg`` export const icon_upload = svg`` + +export const icon_antplus = svg`` +export const icon_settings = svg`` +export const rower_icon = svg`` diff --git a/app/client/store/appState.js b/app/client/store/appState.js index 3a93f55ebb..2e1e0a1883 100644 --- a/app/client/store/appState.js +++ b/app/client/store/appState.js @@ -6,16 +6,23 @@ */ export const APP_STATE = { - // currently can be STANDALONE (Mobile Home Screen App), KIOSK (Raspberry Pi deployment) or '' (default) - appMode: '', // contains all the rowing metrics that are delivered from the backend metrics: {}, config: { - // currently can be FTMS, FTMSBIKE or PM5 - peripheralMode: '', + // currently can be FTMS, FTMSBIKE, PM5, CSC, CPS, OFF + blePeripheralMode: '', + // currently can be ANT, BLE, OFF + hrmPeripheralMode: '', + // currently can be FE, OFF + antPeripheralMode: '', // true if upload to strava is enabled stravaUploadEnabled: false, // true if remote device shutdown is enabled - shutdownEnabled: false + shutdownEnabled: false, + guiConfigs: { + dashboardMetrics: ['distance', 'timer', 'pace', 'power', 'stkRate', 'totalStk', 'calories', 'actions'], + showIcons: true, + maxNumberOfTiles: 8 + } } } diff --git a/app/client/store/dashboardMetrics.js b/app/client/store/dashboardMetrics.js new file mode 100644 index 0000000000..355284017e --- /dev/null +++ b/app/client/store/dashboardMetrics.js @@ -0,0 +1,82 @@ +import { html } from 'lit' +import { formatDistance, formatNumber, secondsToTimeString } from '../lib/helper' +import { icon_bolt, icon_clock, icon_fire, icon_heartbeat, icon_paddle, icon_route, icon_stopwatch, rower_icon } from '../lib/icons' +import '../components/DashboardForceCurve.js' +import '../components/DashboardActions.js' +import '../components/DashboardMetric.js' +import '../components/BatteryIcon.js' + +export const DASHBOARD_METRICS = { + distance: { + displayName: 'Distance', + size: 1, + template: (metrics, config) => { + const distance = metrics?.sessiontype === 'Distance' ? Math.max(metrics?.intervalTargetDistance - metrics?.intervalLinearDistance, 0) : metrics?.totalLinearDistance + const linearDistance = formatDistance(distance ?? 0) + + return simpleMetricFactory(linearDistance.distance, linearDistance.unit, config.guiConfigs.showIcons ? icon_route : '') + } + }, + + pace: { displayName: 'Pace/500', size: 1, template: (metrics, config) => simpleMetricFactory(secondsToTimeString(500 / metrics?.cycleLinearVelocity), '/500m', config.guiConfigs.showIcons ? icon_stopwatch : '') }, + + power: { displayName: 'Power', size: 1, template: (metrics, config) => simpleMetricFactory(formatNumber(metrics?.cyclePower), 'watt', config.guiConfigs.showIcons ? icon_bolt : '') }, + + stkRate: { displayName: 'Stroke rate', size: 1, template: (metrics, config) => simpleMetricFactory(formatNumber(metrics?.cycleStrokeRate), '/min', config.guiConfigs.showIcons ? icon_paddle : '') }, + heartRate: { + displayName: 'Heart rate', + size: 1, + template: (metrics, config) => html` + ${metrics?.heartRateBatteryLevel > 0 + ? html`` + : ''} + ` + }, + + totalStk: { displayName: 'Total strokes', size: 1, template: (metrics, config) => simpleMetricFactory(metrics?.totalNumberOfStrokes, 'stk', config.guiConfigs.showIcons ? icon_paddle : '') }, + + calories: { + displayName: 'Calories', + size: 1, + template: (metrics, config) => { + const calories = metrics?.sessiontype === 'Calories' ? Math.max(metrics?.intervalTargetCalories - metrics?.intervalLinearCalories, 0) : metrics?.totalCalories + + return simpleMetricFactory(formatNumber(calories ?? 0), 'kcal', config.guiConfigs.showIcons ? icon_fire : '') + } + }, + + timer: { + displayName: 'Timer', + size: 1, + template: (metrics, config) => { + const time = metrics?.sessiontype === 'Time' ? Math.max(metrics?.intervalTargetTime - metrics?.intervalMovingTime, 0) : metrics?.totalMovingTime + + return simpleMetricFactory(secondsToTimeString(time ?? 0), '', config.guiConfigs.showIcons ? icon_clock : '') + } + }, + + distancePerStk: { displayName: 'Dist per Stroke', size: 1, template: (metrics, config) => simpleMetricFactory(formatNumber(metrics?.cycleDistance, 1), 'm', config.guiConfigs.showIcons ? rower_icon : '') }, + + dragFactor: { displayName: 'Drag factor', size: 1, template: (metrics, config) => simpleMetricFactory(formatNumber(metrics?.dragFactor), '', config.guiConfigs.showIcons ? 'Drag' : '') }, + + driveLength: { displayName: 'Drive length', size: 1, template: (metrics, config) => simpleMetricFactory(formatNumber(metrics?.driveLength, 2), 'm', config.guiConfigs.showIcons ? 'Drive' : '') }, + + driveDuration: { displayName: 'Drive duration', size: 1, template: (metrics, config) => simpleMetricFactory(formatNumber(metrics?.driveDuration, 2), 'sec', config.guiConfigs.showIcons ? 'Drive' : '') }, + + recoveryDuration: { displayName: 'Recovery duration', size: 1, template: (metrics, config) => simpleMetricFactory(formatNumber(metrics?.recoveryDuration, 2), 'sec', config.guiConfigs.showIcons ? 'Recovery' : '') }, + + forceCurve: { displayName: 'Force curve', size: 2, template: (metrics) => html`` }, + + actions: { displayName: 'Actions', size: 1, template: (_, config) => html`` } +} + +/** + * Helper function to create a simple metric tile + * + * @param {string | number} value The metric to show + * @param {string} unit The unit of the metric. + * @param {string | import('lit').TemplateResult<2>} icon The number of decimal places to round to (default: 0). +*/ +function simpleMetricFactory (value = '--', unit = '', icon = '') { + return html`` +} diff --git a/app/engine/Flywheel.js b/app/engine/Flywheel.js new file mode 100644 index 0000000000..6b8f260251 --- /dev/null +++ b/app/engine/Flywheel.js @@ -0,0 +1,370 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + This models the flywheel with all of its attributes, which we can also test for being powered + + All times and distances are defined as being before the beginning of the flank, as RowingEngine's metrics + solely depend on times and angular positions before the flank (as they are to be certain to belong to a specific + drive or recovery phase). + + Please note: The array contains a buffer of flankLenght measured currentDt's, BEFORE they are actually processed + + Please note2: This implements Linear regression to obtain the drag factor. We deliberatly DO NOT include the flank data + as we don't know wether they will belong to a Drive or Recovery phase. So we include things which we know for certain that + are part of a specific phase, i.e. dirtyDataPoints[flankLength], which will be eliminated from the flank + + The calculation of angular velocity and acceleration is based on Quadratic Regression, as the second derivative tends to be + quite fragile when small errors are thrown in the mix. The math behind this approach can be found in https://physics.info/motion-equations/ + which is intended for simple linear motion, but the formula are identical when applied to angular distances, velocities and + accelerations. +*/ + +import loglevel from 'loglevel' +import { createStreamFilter } from './utils/StreamFilter.js' +import { createOLSLinearSeries } from './utils/OLSLinearSeries.js' +import { createTSLinearSeries } from './utils/FullTSLinearSeries.js' +import { createTSQuadraticSeries } from './utils/FullTSQuadraticSeries.js' +import { createWeighedSeries } from './utils/WeighedSeries.js' + +const log = loglevel.getLogger('RowingEngine') + +function createFlywheel (rowerSettings) { + const angularDisplacementPerImpulse = (2.0 * Math.PI) / rowerSettings.numOfImpulsesPerRevolution + const flankLength = rowerSettings.flankLength + const minimumDragFactorSamples = Math.floor(rowerSettings.minimumRecoveryTime / rowerSettings.maximumTimeBetweenImpulses) + const minumumTorqueBeforeStroke = rowerSettings.minumumForceBeforeStroke * (rowerSettings.sprocketRadius / 100) + const currentDt = createStreamFilter(rowerSettings.smoothing, rowerSettings.maximumTimeBetweenImpulses) + const _deltaTime = createOLSLinearSeries(flankLength) + const _angularDistance = createTSQuadraticSeries(flankLength) + const _angularVelocityMatrix = [] + const _angularAccelerationMatrix = [] + const drag = createWeighedSeries(rowerSettings.dragFactorSmoothing, (rowerSettings.dragFactor / 1000000)) + const recoveryDeltaTime = createTSLinearSeries() + const strokedetectionMinimalGoodnessOfFit = rowerSettings.minimumStrokeQuality + const minumumRecoverySlope = createWeighedSeries(rowerSettings.dragFactorSmoothing, rowerSettings.minumumRecoverySlope) + let _deltaTimeBeforeFlank + let _angularVelocityAtBeginFlank + let _angularVelocityBeforeFlank + let _angularAccelerationAtBeginFlank + let _angularAccelerationBeforeFlank + let _torqueAtBeginFlank + let _torqueBeforeFlank + let inRecoveryPhase + let maintainMetrics + let totalNumberOfImpulses + let totalTimeSpinning + let currentCleanTime + let currentRawTime + let currentAngularDistance + reset() + + function pushValue (dataPoint) { + if (isNaN(dataPoint) || dataPoint < 0 || dataPoint > rowerSettings.maximumStrokeTimeBeforePause) { + // This typicaly happends after a pause, we need to fix this as it throws off all time calculations + log.debug(`*** WARNING: currentDt of ${dataPoint} sec isn't between 0 and maximumStrokeTimeBeforePause (${rowerSettings.maximumStrokeTimeBeforePause} sec)`) + dataPoint = currentDt.clean() + } + + if (dataPoint > rowerSettings.maximumTimeBetweenImpulses && maintainMetrics) { + // This shouldn't happen, but let's log it to clarify there is some issue going on here + log.debug(`*** WARNING: currentDt of ${dataPoint} sec is above maximumTimeBetweenImpulses (${rowerSettings.maximumTimeBetweenImpulses} sec)`) + } + + if (dataPoint < rowerSettings.minimumTimeBetweenImpulses && maintainMetrics) { + // This shouldn't happen, but let's log it to clarify there is some issue going on here + log.debug(`*** WARNING: currentDt of ${dataPoint} sec is below minimumTimeBetweenImpulses (${rowerSettings.minimumTimeBetweenImpulses} sec)`) + } + + currentDt.push(dataPoint) + + if (maintainMetrics && (_deltaTime.length() >= flankLength)) { + // If we maintain metrics, update the angular position, spinning time of the flywheel and the associated metrics, + // Also we nend feed the Drag calculation. We need to do this, BEFORE the array shifts, as the valueAtSeriesBeginvalue + // value before the shift is certain to be part of a specific rowing phase (i.e. Drive or Recovery), once the buffer is filled completely + totalNumberOfImpulses += 1 + _deltaTimeBeforeFlank = _deltaTime.yAtSeriesBegin() + totalTimeSpinning += _deltaTimeBeforeFlank + _angularVelocityBeforeFlank = _angularVelocityAtBeginFlank + _angularAccelerationBeforeFlank = _angularAccelerationAtBeginFlank + _torqueBeforeFlank = _torqueAtBeginFlank + + // Feed the drag calculation, as we didn't reset the Semaphore in the previous cycle based on the current flank + if (inRecoveryPhase) { + recoveryDeltaTime.push(totalTimeSpinning, _deltaTimeBeforeFlank) + } + } else { + _deltaTimeBeforeFlank = 0 + _angularVelocityBeforeFlank = 0 + _angularAccelerationBeforeFlank = 0 + _torqueBeforeFlank = 0 + } + + // Let's feed the stroke detection algorithm + // Please note that deltaTime MUST use dirty data to be ale to use the OLS algorithms effictively (Otherwise the Goodness of Fit can't be used as a filter!) + currentRawTime += currentDt.raw() + currentAngularDistance += angularDisplacementPerImpulse + _deltaTime.push(currentRawTime, currentDt.raw()) + + // Next are the metrics that are needed for more advanced metrics, like the foce curve + currentCleanTime += currentDt.clean() + _angularDistance.push(currentCleanTime, currentAngularDistance) + + // Let's update the matrix and calculate the angular velocity and acceleration + if (_angularVelocityMatrix.length >= flankLength) { + // The angularVelocityMatrix has reached its maximum length + _angularVelocityMatrix.shift() + _angularAccelerationMatrix.shift() + } + + // Let's make room for a new set of values for angular velocity and acceleration + _angularVelocityMatrix[_angularVelocityMatrix.length] = createWeighedSeries(flankLength, 0) + _angularAccelerationMatrix[_angularAccelerationMatrix.length] = createWeighedSeries(flankLength, 0) + + let i = 0 + const goodnessOfFit = _angularDistance.goodnessOfFit() + + while (i < _angularVelocityMatrix.length) { + _angularVelocityMatrix[i].push(_angularDistance.firstDerivativeAtPosition(i), goodnessOfFit) + _angularAccelerationMatrix[i].push(_angularDistance.secondDerivativeAtPosition(i), goodnessOfFit) + i++ + } + + _angularVelocityAtBeginFlank = _angularVelocityMatrix[0].weighedAverage() + _angularAccelerationAtBeginFlank = _angularAccelerationMatrix[0].weighedAverage() + + // And finally calculate the torque + _torqueAtBeginFlank = (rowerSettings.flywheelInertia * _angularAccelerationAtBeginFlank + drag.weighedAverage() * Math.pow(_angularVelocityAtBeginFlank, 2)) + } + + function maintainStateOnly () { + maintainMetrics = false + } + + function maintainStateAndMetrics () { + maintainMetrics = true + } + + function markRecoveryPhaseStart () { + inRecoveryPhase = true + recoveryDeltaTime.reset() + } + + function markRecoveryPhaseCompleted () { + // Completion of the recovery phase + inRecoveryPhase = false + + // As goodnessOfFit is calculated in-situ (for 220 datapoints on a C2) and is CPU intensive, we only calculate it only once and reuse the cached value + const goodnessOfFit = recoveryDeltaTime.goodnessOfFit() + + // Calculation of the drag-factor + if (rowerSettings.autoAdjustDragFactor && recoveryDeltaTime.length() > minimumDragFactorSamples && recoveryDeltaTime.slope() > 0 && (!drag.reliable() || goodnessOfFit >= rowerSettings.minimumDragQuality)) { + drag.push(slopeToDrag(recoveryDeltaTime.slope()), goodnessOfFit) + + log.debug(`*** Calculated drag factor: ${(slopeToDrag(recoveryDeltaTime.slope()) * 1000000).toFixed(4)}, no. samples: ${recoveryDeltaTime.length()}, Goodness of Fit: ${goodnessOfFit.toFixed(4)}`) + if (rowerSettings.autoAdjustRecoverySlope) { + // We are allowed to autoadjust stroke detection slope as well, so let's do that + minumumRecoverySlope.push((1 - rowerSettings.autoAdjustRecoverySlopeMargin) * recoveryDeltaTime.slope(), goodnessOfFit) + log.debug(`*** Calculated recovery slope: ${recoveryDeltaTime.slope().toFixed(6)}, Goodness of Fit: ${goodnessOfFit.toFixed(4)}`) + } else { + // We aren't allowed to adjust the slope, let's report the slope to help help the user configure it + log.debug(`*** Calculated recovery slope: ${recoveryDeltaTime.slope().toFixed(6)}, Goodness of Fit: ${goodnessOfFit.toFixed(4)}, not used as autoAdjustRecoverySlope isn't set to true`) + } + } else { + if (!rowerSettings.autoAdjustDragFactor) { + // autoAdjustDampingConstant = false, thus the update is skipped, but let's log the dragfactor anyway + log.debug(`*** Calculated drag factor: ${(slopeToDrag(recoveryDeltaTime.slope()) * 1000000).toFixed(4)}, slope: ${recoveryDeltaTime.slope().toFixed(8)}, not used because autoAdjustDragFactor is not true`) + } else { + log.debug(`*** Calculated drag factor: ${(slopeToDrag(recoveryDeltaTime.slope()) * 1000000).toFixed(4)}, not used because reliability was too low. no. samples: ${recoveryDeltaTime.length()}, fit: ${goodnessOfFit.toFixed(4)}`) + } + } + } + + function spinningTime () { + // This function returns the time the flywheel is spinning in seconds BEFORE the beginning of the flank + return totalTimeSpinning + } + + function deltaTime () { + return _deltaTimeBeforeFlank + } + + function angularPosition () { + // This function returns the absolute angular position of the flywheel in Radians BEFORE the beginning of the flank + return totalNumberOfImpulses * angularDisplacementPerImpulse + } + + function angularVelocity () { + // This function returns the angular velocity of the flywheel in Radians/sec BEFORE the flank + if (maintainMetrics && (_deltaTime.length() >= flankLength)) { + return Math.max(0, _angularVelocityBeforeFlank) + } else { + return 0 + } + } + + function angularAcceleration () { + // This function returns the angular acceleration of the flywheel in Radians/sec^2 BEFORE the flanl + if (maintainMetrics && (_deltaTime.length() >= flankLength)) { + return _angularAccelerationBeforeFlank + } else { + return 0 + } + } + + function torque () { + if (maintainMetrics && (_deltaTime.length() >= flankLength)) { + return _torqueBeforeFlank + } else { + return 0 + } + } + + function dragFactor () { + // Ths function returns the current dragfactor of the flywheel + return drag.weighedAverage() + } + + function isDwelling () { + // Check if the flywheel is spinning down beyond a recovery phase indicating that the rower has stopped rowing + // We conclude this based on + // * A decelerating flywheel as the slope of the CurrentDt's goes up + // * All CurrentDt's in the flank are above the maximum + if (_deltaTime.slope() > 0 && deltaTimesAbove(rowerSettings.maximumTimeBetweenImpulses)) { + return true + } else { + return false + } + } + + function isAboveMinimumSpeed () { + // Check if the flywheel has reached its minimum speed. We conclude this based on all CurrentDt's in the flank are below + // the maximum, indicating a sufficiently fast flywheel + if (deltaTimesEqualorBelow(rowerSettings.maximumTimeBetweenImpulses)) { + return true + } else { + return false + } + } + + function isUnpowered () { + if (deltaTimeSlopeAbove(minumumRecoverySlope.weighedAverage()) && torqueAbsent() && _deltaTime.length() >= flankLength) { + // We reached the minimum number of increasing currentDt values + return true + } else { + return false + } + } + + function isPowered () { + if ((deltaTimeSlopeBelow(minumumRecoverySlope.weighedAverage()) && torquePresent()) || _deltaTime.length() < flankLength) { + return true + } else { + return false + } + } + + function deltaTimesAbove (threshold) { + if (_deltaTime.minimumY() >= threshold && _deltaTime.length() >= flankLength) { + return true + } else { + return false + } + } + + function deltaTimesEqualorBelow (threshold) { + if (_deltaTime.maximumY() <= threshold && _deltaTime.length() >= flankLength) { + return true + } else { + return false + } + } + + function deltaTimeSlopeBelow (threshold) { + // This is a typical indication that the flywheel is accelerating. We use the slope of successive currentDt's + // A (more) negative slope indicates a powered flywheel. When set to 0, it determines whether the DeltaT's are decreasing + // When set to a value below 0, it will become more stringent. In automatic, a percentage of the current slope (i.e. dragfactor) is used + if (_deltaTime.slope() < threshold && _deltaTime.length() >= flankLength) { + return true + } else { + return false + } + } + + function deltaTimeSlopeAbove (threshold) { + // This is a typical indication that the flywheel is deccelerating. We use the slope of successive currentDt's + // A (more) positive slope indicates a unpowered flywheel. When set to 0, it determines whether the DeltaT's are increasing + // When set to a value below 0, it will become more stringent as it will detect a power inconsistent with the drag + // Typically, a percentage of the current slope (i.e. dragfactor) is use + if (_deltaTime.slope() >= threshold && _deltaTime.goodnessOfFit() >= strokedetectionMinimalGoodnessOfFit && _deltaTime.length() >= flankLength) { + return true + } else { + return false + } + } + + function torquePresent () { + // This is a typical indication that the flywheel is decelerating which might work on some machines: successive currentDt's are increasing + if (_torqueAtBeginFlank > minumumTorqueBeforeStroke) { + return true + } else { + return false + } + } + + function torqueAbsent () { + // This is a typical indication that the flywheel is Accelerating which might work on some machines: successive currentDt's are decreasing + if (_torqueAtBeginFlank < minumumTorqueBeforeStroke) { + return true + } else { + return false + } + } + + function slopeToDrag (slope) { + return ((slope * rowerSettings.flywheelInertia) / angularDisplacementPerImpulse) + } + + function reset () { + maintainMetrics = false + inRecoveryPhase = false + drag.reset() + recoveryDeltaTime.reset() + _deltaTime.reset() + _angularDistance.reset() + totalNumberOfImpulses = 0 + totalTimeSpinning = 0 + currentCleanTime = 0 + currentRawTime = 0 + currentAngularDistance = 0 + _deltaTime.push(0, 0) + _angularDistance.push(0, 0) + _deltaTimeBeforeFlank = 0 + _angularVelocityBeforeFlank = 0 + _angularAccelerationBeforeFlank = 0 + _torqueAtBeginFlank = 0 + _torqueBeforeFlank = 0 + } + + return { + pushValue, + maintainStateOnly, + maintainStateAndMetrics, + markRecoveryPhaseStart, + markRecoveryPhaseCompleted, + spinningTime, + deltaTime, + angularPosition, + angularVelocity, + angularAcceleration, + torque, + dragFactor, + isDwelling, + isAboveMinimumSpeed, + isUnpowered, + isPowered, + reset + } +} + +export { createFlywheel } diff --git a/app/engine/Flywheel.test.js b/app/engine/Flywheel.test.js new file mode 100644 index 0000000000..331b0640d4 --- /dev/null +++ b/app/engine/Flywheel.test.js @@ -0,0 +1,357 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/jaapvanekris/openrowingmonitor +*/ +import { test } from 'uvu' +import * as assert from 'uvu/assert' +import { deepMerge } from '../tools/Helper.js' +import { replayRowingSession } from '../tools/RowingRecorder.js' +import rowerProfiles from '../../config/rowerProfiles.js' + +import { createFlywheel } from './Flywheel.js' + +const baseConfig = { // Based on Concept 2 settings, as this is the validation system + numOfImpulsesPerRevolution: 6, + sprocketRadius: 1.4, + maximumStrokeTimeBeforePause: 6.0, + dragFactor: 110, + autoAdjustDragFactor: true, + minimumDragQuality: 0.95, + dragFactorSmoothing: 3, + minimumTimeBetweenImpulses: 0.005, + maximumTimeBetweenImpulses: 0.020, + flankLength: 12, + smoothing: 1, + minimumStrokeQuality: 0.36, + minumumForceBeforeStroke: 10, + minumumRecoverySlope: 0.00070, + autoAdjustRecoverySlope: true, + autoAdjustRecoverySlopeMargin: 0.15, + minimumDriveTime: 0.40, + minimumRecoveryTime: 0.90, + flywheelInertia: 0.1031, + magicConstant: 2.8 +} + +// Test behaviour for no datapoints +test('Correct Flywheel behaviour at initialisation', () => { + const flywheel = createFlywheel(baseConfig) + testDeltaTime(flywheel, 0) + testSpinningTime(flywheel, 0) + testAngularPosition(flywheel, 0) + testAngularVelocity(flywheel, 0) + testAngularAcceleration(flywheel, 0) + testTorque(flywheel, 0) + testDragFactor(flywheel, 0.00011) + testIsDwelling(flywheel, false) + testIsUnpowered(flywheel, false) + testIsPowered(flywheel, true) +}) + +// Test behaviour for one datapoint + +// Test behaviour for perfect upgoing flank + +// Test behaviour for perfect downgoing flank + +// Test behaviour for perfect stroke +test('Correct Flywheel behaviour for a noisefree stroke', () => { + const flywheel = createFlywheel(baseConfig) + flywheel.maintainStateAndMetrics() + testDeltaTime(flywheel, 0) + testSpinningTime(flywheel, 0) + testAngularPosition(flywheel, 0) + testAngularVelocity(flywheel, 0) + testAngularAcceleration(flywheel, 0) + testTorque(flywheel, 0) + testDragFactor(flywheel, 0.00011) + testIsDwelling(flywheel, false) + testIsUnpowered(flywheel, false) + testIsPowered(flywheel, true) + flywheel.pushValue(0.011221636) + flywheel.pushValue(0.011175504) + flywheel.pushValue(0.01116456) + flywheel.pushValue(0.011130263) + flywheel.pushValue(0.011082613) + flywheel.pushValue(0.011081761) + flywheel.pushValue(0.011062297) + flywheel.pushValue(0.011051853) + flywheel.pushValue(0.010973313) + flywheel.pushValue(0.010919756) + flywheel.pushValue(0.01086431) + flywheel.pushValue(0.010800864) + flywheel.pushValue(0.010956987) + flywheel.pushValue(0.010653396) + flywheel.pushValue(0.010648619) + flywheel.pushValue(0.010536818) + flywheel.pushValue(0.010526151) + flywheel.pushValue(0.010511225) + flywheel.pushValue(0.010386684) + testDeltaTime(flywheel, 0.011062297) + testSpinningTime(flywheel, 0.077918634) + testAngularPosition(flywheel, 8.377580409572781) + testAngularVelocity(flywheel, 94.76231358849583) + testAngularAcceleration(flywheel, 28.980404808837132) + testTorque(flywheel, 3.975668304221995) + testDragFactor(flywheel, 0.00011) + testIsDwelling(flywheel, false) + testIsUnpowered(flywheel, false) + testIsPowered(flywheel, true) + flywheel.pushValue(0.010769) + flywheel.pushValue(0.010707554) + flywheel.pushValue(0.010722165) + flywheel.pushValue(0.01089567) + flywheel.pushValue(0.010917504) + flywheel.pushValue(0.010997969) + flywheel.pushValue(0.011004655) + flywheel.pushValue(0.011013618) + flywheel.pushValue(0.011058193) + flywheel.pushValue(0.010807149) + flywheel.pushValue(0.0110626) + flywheel.pushValue(0.011090787) + flywheel.pushValue(0.011099509) + flywheel.pushValue(0.011131862) + flywheel.pushValue(0.011209919) + testDeltaTime(flywheel, 0.010722165) + testSpinningTime(flywheel, 0.23894732900000007) + testAngularPosition(flywheel, 24.085543677521745) + testAngularVelocity(flywheel, 97.13471664858164) + testAngularAcceleration(flywheel, -29.657593800236377) + testTorque(flywheel, -2.0198310711803433) + testDragFactor(flywheel, 0.00011) + testIsDwelling(flywheel, false) + testIsUnpowered(flywheel, true) + testIsPowered(flywheel, false) + flywheel.pushValue(0.020769) + flywheel.pushValue(0.020707554) + flywheel.pushValue(0.020722165) + flywheel.pushValue(0.02089567) + flywheel.pushValue(0.020917504) + flywheel.pushValue(0.020997969) + flywheel.pushValue(0.021004655) + flywheel.pushValue(0.021013618) + flywheel.pushValue(0.021058193) + flywheel.pushValue(0.020807149) + flywheel.pushValue(0.0210626) + flywheel.pushValue(0.021090787) + flywheel.pushValue(0.021099509) + flywheel.pushValue(0.021131862) + flywheel.pushValue(0.021209919) + testDeltaTime(flywheel, 0.020722165) + testSpinningTime(flywheel, 0.43343548300000007) + testAngularPosition(flywheel, 39.79350694547071) + testAngularVelocity(flywheel, 50.71501160141977) + testAngularAcceleration(flywheel, -159.90034506799844) + testTorque(flywheel, -16.202804212320103) + testDragFactor(flywheel, 0.00011) + testIsDwelling(flywheel, true) + testIsUnpowered(flywheel, true) + testIsPowered(flywheel, false) +}) + +// Test behaviour for noisy upgoing flank + +// Test behaviour for noisy downgoing flank + +// Test behaviour for noisy stroke + +// Test drag factor calculation + +// Test Dynamic stroke detection + +// Test behaviour for not maintaining metrics +test('Correct Flywheel behaviour at maintainStateOnly', () => { + const flywheel = createFlywheel(baseConfig) + flywheel.maintainStateAndMetrics() + testDeltaTime(flywheel, 0) + testSpinningTime(flywheel, 0) + testAngularPosition(flywheel, 0) + testAngularVelocity(flywheel, 0) + testAngularAcceleration(flywheel, 0) + testTorque(flywheel, 0) + testDragFactor(flywheel, 0.00011) + testIsDwelling(flywheel, false) + testIsUnpowered(flywheel, false) + testIsPowered(flywheel, true) + flywheel.maintainStateOnly() + flywheel.pushValue(0.011221636) + flywheel.pushValue(0.011175504) + flywheel.pushValue(0.01116456) + flywheel.pushValue(0.011130263) + flywheel.pushValue(0.011082613) + flywheel.pushValue(0.011081761) + flywheel.pushValue(0.011062297) + flywheel.pushValue(0.011051853) + flywheel.pushValue(0.010973313) + flywheel.pushValue(0.010919756) + flywheel.pushValue(0.01086431) + flywheel.pushValue(0.010800864) + flywheel.pushValue(0.010956987) + flywheel.pushValue(0.010653396) + flywheel.pushValue(0.010648619) + flywheel.pushValue(0.010536818) + flywheel.pushValue(0.010526151) + flywheel.pushValue(0.010511225) + flywheel.pushValue(0.010386684) + testDeltaTime(flywheel, 0) + testSpinningTime(flywheel, 0) + testAngularPosition(flywheel, 0) + testAngularVelocity(flywheel, 0) + testAngularAcceleration(flywheel, 0) + testTorque(flywheel, 0) + testDragFactor(flywheel, 0.00011) + testIsDwelling(flywheel, false) + testIsUnpowered(flywheel, false) + testIsPowered(flywheel, true) + flywheel.pushValue(0.010769) + flywheel.pushValue(0.010707554) + flywheel.pushValue(0.010722165) + flywheel.pushValue(0.01089567) + flywheel.pushValue(0.010917504) + flywheel.pushValue(0.010997969) + flywheel.pushValue(0.011004655) + flywheel.pushValue(0.011013618) + flywheel.pushValue(0.011058193) + flywheel.pushValue(0.010807149) + flywheel.pushValue(0.0110626) + flywheel.pushValue(0.011090787) + flywheel.pushValue(0.011099509) + flywheel.pushValue(0.011131862) + flywheel.pushValue(0.011209919) + testDeltaTime(flywheel, 0) + testSpinningTime(flywheel, 0) + testAngularPosition(flywheel, 0) + testAngularVelocity(flywheel, 0) + testAngularAcceleration(flywheel, 0) + testTorque(flywheel, 0) + testDragFactor(flywheel, 0.00011) + testIsDwelling(flywheel, false) + testIsUnpowered(flywheel, true) + testIsPowered(flywheel, false) +}) + +test('Correct Flywheel behaviour with a SportsTech WRX700', async () => { + const flywheel = createFlywheel(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.Sportstech_WRX700)) + testSpinningTime(flywheel, 0) + testAngularPosition(flywheel, 0) + testDragFactor(flywheel, (rowerProfiles.Sportstech_WRX700.dragFactor / 1000000)) + flywheel.maintainStateAndMetrics() + + // Inject 16 strokes + await replayRowingSession(flywheel.pushValue, { filename: 'recordings/WRX700_2magnets.csv', realtime: false, loop: false }) + testSpinningTime(flywheel, 46.302522627) + testAngularPosition(flywheel, 741.4158662471912) + testDragFactor(flywheel, (rowerProfiles.Sportstech_WRX700.dragFactor / 1000000)) +}) + +test('Correct Flywheel behaviour with a DKN R-320', async () => { + const flywheel = createFlywheel(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.DKN_R320)) + testSpinningTime(flywheel, 0) + testAngularPosition(flywheel, 0) + testDragFactor(flywheel, (rowerProfiles.DKN_R320.dragFactor / 1000000)) + flywheel.maintainStateAndMetrics() + + // Inject 10 strokes + await replayRowingSession(flywheel.pushValue, { filename: 'recordings/DKNR320.csv', realtime: false, loop: false }) + + testSpinningTime(flywheel, 22.249536391000003) + testAngularPosition(flywheel, 496.37163926718733) + // As dragfactor is static, it should remain the same + testDragFactor(flywheel, (rowerProfiles.DKN_R320.dragFactor / 1000000)) +}) + +test('Correct Flywheel behaviour with a NordicTrack RX800', async () => { + const flywheel = createFlywheel(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.NordicTrack_RX800)) + testSpinningTime(flywheel, 0) + testAngularPosition(flywheel, 0) + testDragFactor(flywheel, (rowerProfiles.NordicTrack_RX800.dragFactor / 1000000)) + flywheel.maintainStateAndMetrics() + + // Inject 10 strokes + await replayRowingSession(flywheel.pushValue, { filename: 'recordings/RX800.csv', realtime: false, loop: false }) + + testSpinningTime(flywheel, 22.65622640199999) + testAngularPosition(flywheel, 1446.7034169780998) + // As we don't detect strokes here (this is a function of Rower.js, the dragcalculation shouldn't be triggered + testDragFactor(flywheel, (rowerProfiles.NordicTrack_RX800.dragFactor / 1000000)) +}) + +test('Correct Flywheel behaviour with a full session on a SportsTech WRX700', async () => { + const flywheel = createFlywheel(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.Sportstech_WRX700)) + testSpinningTime(flywheel, 0) + testAngularPosition(flywheel, 0) + testDragFactor(flywheel, (rowerProfiles.Sportstech_WRX700.dragFactor / 1000000)) + flywheel.maintainStateAndMetrics() + + // Inject 846 strokes + await replayRowingSession(flywheel.pushValue, { filename: 'recordings/WRX700_2magnets_session.csv', realtime: false, loop: false }) + testSpinningTime(flywheel, 2342.741183077012) + testAngularPosition(flywheel, 37337.82868791469) + // The dragfactor should remain static + testDragFactor(flywheel, (rowerProfiles.Sportstech_WRX700.dragFactor / 1000000)) +}) + +test('A full session for a Concept2 RowErg should produce plausible results', async () => { + const flywheel = createFlywheel(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.Concept2_RowErg)) + testSpinningTime(flywheel, 0) + testAngularPosition(flywheel, 0) + testDragFactor(flywheel, (rowerProfiles.Concept2_RowErg.dragFactor / 1000000)) + flywheel.maintainStateAndMetrics() + + await replayRowingSession(flywheel.pushValue, { filename: 'recordings/Concept2_RowErg_Session_2000meters.csv', realtime: false, loop: false }) + + testSpinningTime(flywheel, 591.0432650000008) + testAngularPosition(flywheel, 65961.92655232249) + // As we don't detect strokes here (this is a function of Rower.js, the dragcalculation shouldn't be triggered + testDragFactor(flywheel, (rowerProfiles.Concept2_RowErg.dragFactor / 1000000)) +}) + +// Test behaviour after reset + +function testDeltaTime (flywheel, expectedValue) { + assert.ok(flywheel.deltaTime() === expectedValue, `deltaTime should be ${expectedValue} sec at ${flywheel.spinningTime()} sec, is ${flywheel.deltaTime()}`) +} + +function testSpinningTime (flywheel, expectedValue) { + assert.ok(flywheel.spinningTime() === expectedValue, `spinningTime should be ${expectedValue} sec at ${flywheel.spinningTime()} sec, is ${flywheel.spinningTime()}`) +} + +function testAngularPosition (flywheel, expectedValue) { + assert.ok(flywheel.angularPosition() === expectedValue, `angularPosition should be ${expectedValue} Radians at ${flywheel.spinningTime()} sec, is ${flywheel.angularPosition()}`) +} + +function testAngularVelocity (flywheel, expectedValue) { + assert.ok(flywheel.angularVelocity() === expectedValue, `angularVelocity should be ${expectedValue} Radians/sec at ${flywheel.spinningTime()} sec, is ${flywheel.angularVelocity()}`) +} + +function testAngularAcceleration (flywheel, expectedValue) { + assert.ok(flywheel.angularAcceleration() === expectedValue, `angularAcceleration should be ${expectedValue} Radians/sec^2 at ${flywheel.spinningTime()} sec, is ${flywheel.angularAcceleration()}`) +} + +function testTorque (flywheel, expectedValue) { + assert.ok(flywheel.torque() === expectedValue, `Torque should be ${expectedValue} N/M at ${flywheel.spinningTime()} sec, is ${flywheel.torque()}`) +} + +function testDragFactor (flywheel, expectedValue) { + assert.ok(flywheel.dragFactor() === expectedValue, `Drag Factor should be ${expectedValue} N*m*s^2 at ${flywheel.spinningTime()} sec, is ${flywheel.dragFactor()}`) +} + +function testIsDwelling (flywheel, expectedValue) { + assert.ok(flywheel.isDwelling() === expectedValue, `isDwelling should be ${expectedValue} at ${flywheel.spinningTime()} sec, is ${flywheel.isDwelling()}`) +} + +function testIsUnpowered (flywheel, expectedValue) { + assert.ok(flywheel.isUnpowered() === expectedValue, `isUnpowered should be ${expectedValue} at ${flywheel.spinningTime()} sec, is ${flywheel.isUnpowered()}`) +} + +function testIsPowered (flywheel, expectedValue) { + assert.ok(flywheel.isPowered() === expectedValue, `isPowered should be ${expectedValue} at ${flywheel.spinningTime()} sec, is ${flywheel.isPowered()}`) +} + +/* +function reportAll (flywheel) { + assert.ok(0, `deltaTime: ${flywheel.deltaTime()}, spinningTime: ${flywheel.spinningTime()}, ang. pos: ${flywheel.angularPosition()}, ang. vel: ${flywheel.angularVelocity()}, Ang. acc: ${flywheel.angularAcceleration()}, Torque: ${flywheel.torque()}, DF: ${flywheel.dragFactor()}`) +} +*/ + +test.run() diff --git a/app/engine/MovingFlankDetector.js b/app/engine/MovingFlankDetector.js deleted file mode 100644 index 3454eb70e0..0000000000 --- a/app/engine/MovingFlankDetector.js +++ /dev/null @@ -1,193 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - A Detector used to test for up-going and down-going flanks - - Please note: The array contains flankLength + 1 measured currentDt's, thus flankLength number of flanks between them - They are arranged that dataPoints[0] is the youngest, and dataPoints[flankLength] the oldest -*/ -import loglevel from 'loglevel' -import { createMovingAverager } from './averager/MovingAverager.js' -const log = loglevel.getLogger('RowingEngine') - -function createMovingFlankDetector (rowerSettings) { - const angularDisplacementPerImpulse = (2.0 * Math.PI) / rowerSettings.numOfImpulsesPerRevolution - const dirtyDataPoints = new Array(rowerSettings.flankLength + 1) - dirtyDataPoints.fill(rowerSettings.maximumTimeBetweenImpulses) - const cleanDataPoints = new Array(rowerSettings.flankLength + 1) - cleanDataPoints.fill(rowerSettings.maximumTimeBetweenImpulses) - const angularVelocity = new Array(rowerSettings.flankLength + 1) - angularVelocity.fill(angularDisplacementPerImpulse / rowerSettings.minimumTimeBetweenImpulses) - const angularAcceleration = new Array(rowerSettings.flankLength + 1) - angularAcceleration.fill(0.1) - const movingAverage = createMovingAverager(rowerSettings.smoothing, rowerSettings.maximumTimeBetweenImpulses) - let numberOfSequentialCorrections = 0 - const maxNumberOfSequentialCorrections = (rowerSettings.smoothing >= 2 ? rowerSettings.smoothing : 2) - - function pushValue (dataPoint) { - // add the new dataPoint to the array, we have to move data points starting at the oldest ones - let i = rowerSettings.flankLength - while (i > 0) { - // older data points are moved toward the higher numbers - dirtyDataPoints[i] = dirtyDataPoints[i - 1] - cleanDataPoints[i] = cleanDataPoints[i - 1] - angularVelocity[i] = angularVelocity[i - 1] - angularAcceleration[i] = angularAcceleration[i - 1] - i = i - 1 - } - dirtyDataPoints[0] = dataPoint - - // reduce noise in the measurements by applying some sanity checks - // noise filter on the value of dataPoint: it should be within sane levels and should not deviate too much from the previous reading - if (dataPoint < rowerSettings.minimumTimeBetweenImpulses || dataPoint > rowerSettings.maximumTimeBetweenImpulses) { - // impulseTime is outside plausible ranges, so we assume it is close to the previous clean one - log.debug(`noise filter corrected currentDt, ${dataPoint} was not between minimumTimeBetweenImpulses and maximumTimeBetweenImpulses, changed to ${cleanDataPoints[1]}`) - dataPoint = cleanDataPoints[1] - } - - // lets test if pushing this value would fit the curve we are looking for - movingAverage.pushValue(dataPoint) - - if (movingAverage.getAverage() > (rowerSettings.maximumDownwardChange * cleanDataPoints[1]) && movingAverage.getAverage() < (rowerSettings.maximumUpwardChange * cleanDataPoints[1])) { - numberOfSequentialCorrections = 0 - } else { - // impulses are outside plausible ranges - if (numberOfSequentialCorrections <= maxNumberOfSequentialCorrections) { - // We haven't made too many corrections, so we assume it is close to the previous one - log.debug(`noise filter corrected currentDt, ${dataPoint} was too much of an accelleration/decelleration with respect to ${movingAverage.getAverage()}, changed to previous value, ${cleanDataPoints[1]}`) - movingAverage.replaceLastPushedValue(cleanDataPoints[1]) - } else { - // We made too many corrections (typically, one currentDt is too long, the next is to short or vice versa), let's allow the algorithm to pick it up otherwise we might get stuck - log.debug(`noise filter wanted to corrected currentDt (${dataPoint} sec), but it had already made ${numberOfSequentialCorrections} corrections, filter temporarily disabled`) - } - numberOfSequentialCorrections = numberOfSequentialCorrections + 1 - } - - // determine the moving average, to reduce noise - cleanDataPoints[0] = movingAverage.getAverage() - - // determine the derived data - if (cleanDataPoints[0] > 0) { - angularVelocity[0] = angularDisplacementPerImpulse / cleanDataPoints[0] - angularAcceleration[0] = (angularVelocity[0] - angularVelocity[1]) / cleanDataPoints[0] - } else { - log.error('Impuls of 0 seconds encountered, this should not be possible (division by 0 prevented)') - angularVelocity[0] = 0 - angularAcceleration[0] = 0 - } - } - - function isFlywheelUnpowered () { - let numberOfErrors = 0 - if (rowerSettings.naturalDeceleration < 0) { - // A valid natural deceleration of the flywheel has been provided, this has to be maintained for a flank length - // to count as an indication for an unpowered flywheel - // Please note that angularAcceleration[] contains flank-information already, so we need to check from - // rowerSettings.flankLength -1 until 0 flanks - let i = rowerSettings.flankLength - 1 - while (i >= 0) { - if (angularAcceleration[i] > rowerSettings.naturalDeceleration) { - // There seems to be some power present, so we detected an error - numberOfErrors = numberOfErrors + 1 - } - i = i - 1 - } - } else { - // No valid natural deceleration has been provided, we rely on pure deceleration for recovery detection - let i = rowerSettings.flankLength - while (i > 0) { - if (cleanDataPoints[i] >= cleanDataPoints[i - 1]) { - // Oldest interval (dataPoints[i]) is larger than the younger one (datapoint[i-1], as the distance is - // fixed, we are accelerating - numberOfErrors = numberOfErrors + 1 - } - i = i - 1 - } - } - if (numberOfErrors > rowerSettings.numberOfErrorsAllowed) { - return false - } else { - return true - } - } - - function isFlywheelPowered () { - let numberOfErrors = 0 - if (rowerSettings.naturalDeceleration < 0) { - // A valid natural deceleration of the flywheel has been provided, this has to be consistently encountered - // for a flank length to count as an indication of a powered flywheel - // Please note that angularAcceleration[] contains flank-information already, so we need to check from - // rowerSettings.flankLength -1 until 0 flanks - let i = rowerSettings.flankLength - 1 - while (i >= 0) { - if (angularAcceleration[i] < rowerSettings.naturalDeceleration) { - // Some deceleration is below the natural deceleration, so we detected an error - numberOfErrors = numberOfErrors + 1 - } - i = i - 1 - } - } else { - // No valid natural deceleration of the flywheel has been provided, we rely on pure acceleration for stroke detection - let i = rowerSettings.flankLength - while (i > 1) { - if (cleanDataPoints[i] < cleanDataPoints[i - 1]) { - // Oldest interval (dataPoints[i]) is shorter than the younger one (datapoint[i-1], as the distance is fixed, we - // discovered a deceleration - numberOfErrors = numberOfErrors + 1 - } - i = i - 1 - } - if (cleanDataPoints[1] <= cleanDataPoints[0]) { - // We handle the last measurement more specifically: at least the youngest measurement must be really accelerating - // This prevents when the currentDt "flatlines" (i.e. error correction kicks in) a ghost-stroke is detected - numberOfErrors = numberOfErrors + 1 - } - } - if (numberOfErrors > rowerSettings.numberOfErrorsAllowed) { - return false - } else { - return true - } - } - - function timeToBeginOfFlank () { - // We expect the curve to bend between dirtyDataPoints[rowerSettings.flankLength] and dirtyDataPoints[rowerSettings.flankLength+1], - // as acceleration FOLLOWS the start of the pulling the handle, we assume it must have started before that - let i = rowerSettings.flankLength - let total = 0.0 - while (i >= 0) { - total += dirtyDataPoints[i] - i = i - 1 - } - return total - } - - function noImpulsesToBeginFlank () { - return rowerSettings.flankLength - } - - function impulseLengthAtBeginFlank () { - // As this is fed into the speed calculation where small changes have big effects, and we typically use it when - // the curve is in a plateau, we return the cleaned data and not the dirty data - // Regardless of the way to determine the acceleration, cleanDataPoints[rowerSettings.flankLength] is always the - // impulse at the beginning of the flank being investigated - return cleanDataPoints[rowerSettings.flankLength] - } - - function accelerationAtBeginOfFlank () { - return angularAcceleration[rowerSettings.flankLength - 1] - } - - return { - pushValue, - isFlywheelUnpowered, - isFlywheelPowered, - timeToBeginOfFlank, - noImpulsesToBeginFlank, - impulseLengthAtBeginFlank, - accelerationAtBeginOfFlank - } -} - -export { createMovingFlankDetector } diff --git a/app/engine/Rower.js b/app/engine/Rower.js new file mode 100644 index 0000000000..8b497fe4ff --- /dev/null +++ b/app/engine/Rower.js @@ -0,0 +1,376 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + The Rowing Engine models the physics of a real rowing boat. + It takes impulses from the flywheel of a rowing machine and estimates + parameters such as energy, stroke rates and movement. + + This implementation uses concepts that are described here: + Physics of Rowing by Anu Dudhia: http://eodg.atm.ox.ac.uk/user/dudhia/rowing/physics + Also Dave Vernooy has some good explanations here: https://dvernooy.github.io/projects/ergware +*/ + +import loglevel from 'loglevel' +import { createFlywheel } from './Flywheel.js' +import { createCurveMetrics } from './utils/curveMetrics.js' + +const log = loglevel.getLogger('RowingEngine') + +function createRower (rowerSettings) { + const flywheel = createFlywheel(rowerSettings) + const sprocketRadius = rowerSettings.sprocketRadius / 100 + const driveHandleForce = createCurveMetrics() + const driveHandleVelocity = createCurveMetrics() + const driveHandlePower = createCurveMetrics() + let _strokeState = 'WaitingForDrive' + let _totalNumberOfStrokes = -1.0 + let recoveryPhaseStartTime = 0.0 + let _recoveryDuration = 0.0 + let drivePhaseStartTime = 0.0 + let _driveDuration = 0.0 + let drivePhaseStartAngularPosition = 0.0 + let drivePhaseAngularDisplacement = 0.0 + let _driveLinearDistance = 0.0 + let recoveryPhaseStartAngularPosition = 0.0 + let recoveryPhaseAngularDisplacement = 0.0 + let _recoveryLinearDistance = 0.0 + const minimumCycleDuration = rowerSettings.minimumDriveTime + rowerSettings.minimumRecoveryTime + let _cycleDuration = minimumCycleDuration + let _cycleLinearVelocity = 0.0 + let _cyclePower = 0.0 + let totalLinearDistance = 0.0 + let preliminaryTotalLinearDistance = 0.0 + let _driveLength = 0.0 + + // called if the sensor detected an impulse, currentDt is an interval in seconds + function handleRotationImpulse (currentDt) { + // Provide the flywheel with new data + flywheel.pushValue(currentDt) + + // This is the core of the finite state machine that defines all state transitions + switch (true) { + case (_strokeState === 'Stopped'): + // We are in a stopped state, so don't do anything + break + case (_strokeState === 'WaitingForDrive' && flywheel.isPowered() && flywheel.isAboveMinimumSpeed()): + // We change into the "Drive" phase since were waiting for a drive phase, and we see a clear force exerted on the flywheel + log.debug(`*** Rowing (re)started with a DRIVE phase at time: ${flywheel.spinningTime().toFixed(4)} sec`) + // As we are not certain what caused the "WaitingForDrive" (a fresh start or a restart after pause),, we explicitly start the flywheel maintaining metrics again + flywheel.maintainStateAndMetrics() + _strokeState = 'Drive' + startDrivePhase() + break + case (_strokeState === 'WaitingForDrive'): + // We can't change into the "Drive" phase since we are waiting for a drive phase, but there isn't a clear force exerted on the flywheel. So, there is nothing more to do + break + case (_strokeState === 'Drive' && ((flywheel.spinningTime() - drivePhaseStartTime) >= rowerSettings.minimumDriveTime) && flywheel.isUnpowered()): + // We change into the "Recovery" phase since we have been long enough in the Drive phase, and we see a clear lack of power exerted on the flywheel + log.debug(`*** RECOVERY phase started at time: ${flywheel.spinningTime().toFixed(4)} sec`) + _strokeState = 'Recovery' + endDrivePhase() + startRecoveryPhase() + break + case (_strokeState === 'Drive' && flywheel.isUnpowered()): + // We seem to have lost power to the flywheel, but it is too early according to the settings. We stay in the Drive Phase + log.debug(`Time: ${flywheel.spinningTime().toFixed(4)} sec: Delta Time trend is upwards, suggests no power, but waiting for drive phase length (${(flywheel.spinningTime() - drivePhaseStartTime).toFixed(4)} sec) to exceed minimumDriveTime (${rowerSettings.minimumDriveTime} sec)`) + updateDrivePhase() + break + case (_strokeState === 'Drive'): + // We stay in the "Drive" phase as the decceleration is lacking + updateDrivePhase() + break + case (_strokeState === 'Recovery' && ((flywheel.spinningTime() - drivePhaseStartTime) >= rowerSettings.maximumStrokeTimeBeforePause) && flywheel.isDwelling()): + // The Flywheel is spinning too slowly to create valid CurrentDt's and the last Drive started over maximumStrokeTime ago, we consider it a pause + log.debug(`*** PAUSED rowing at time: ${flywheel.spinningTime().toFixed(4)} sec, rower hasn't moved in ${(flywheel.spinningTime() - drivePhaseStartTime).toFixed(4)} seconds and flywheel is dwelling`) + flywheel.maintainStateOnly() + _strokeState = 'WaitingForDrive' + endRecoveryPhase() + break + case (_strokeState === 'Recovery' && ((flywheel.spinningTime() - recoveryPhaseStartTime) >= rowerSettings.minimumRecoveryTime) && flywheel.isPowered()): + // We change into the "Drive" phase since we have been long enough in the Recovery phase, and we see a clear force + // exerted on the flywheel + log.debug(`*** DRIVE phase started at time: ${flywheel.spinningTime().toFixed(4)} sec`) + _strokeState = 'Drive' + endRecoveryPhase() + startDrivePhase() + break + case (_strokeState === 'Recovery' && flywheel.isPowered()): + // We see a force, but the "Recovery" phase has been too short, we stay in the "Recovery" phase + log.debug(`Time: ${flywheel.spinningTime().toFixed(4)} sec: Delta Time trend is downwards, suggesting power, but waiting for recovery phase length (${(flywheel.spinningTime() - recoveryPhaseStartTime).toFixed(4)} sec) to exceed minimumRecoveryTime (${rowerSettings.minimumRecoveryTime} sec)`) + updateRecoveryPhase() + break + case (_strokeState === 'Recovery'): + // No force on the flywheel, let's continue the "Recovery" phase of the stroke + updateRecoveryPhase() + break + default: + log.error(`Time: ${flywheel.spinningTime().toFixed(4)} sec, state ${_strokeState} found in the Rowing Engine, which is not captured by Finite State Machine`) + } + } + + function startDrivePhase () { + // Next, we start the Drive Phase + _totalNumberOfStrokes++ + drivePhaseStartTime = flywheel.spinningTime() + drivePhaseStartAngularPosition = flywheel.angularPosition() + driveHandleForce.reset() + const forceOnHandle = flywheel.torque() / sprocketRadius + driveHandleForce.push(flywheel.deltaTime(), forceOnHandle) + driveHandleVelocity.reset() + const velocityOfHandle = flywheel.angularVelocity() * sprocketRadius + driveHandleVelocity.push(flywheel.deltaTime(), velocityOfHandle) + driveHandlePower.reset() + const powerOnHandle = flywheel.torque() * flywheel.angularVelocity() + driveHandlePower.push(flywheel.deltaTime(), powerOnHandle) + } + + function updateDrivePhase () { + // Update the key metrics on each impulse + drivePhaseAngularDisplacement = flywheel.angularPosition() - drivePhaseStartAngularPosition + _driveLinearDistance = calculateLinearDistance(drivePhaseAngularDisplacement, (flywheel.spinningTime() - drivePhaseStartTime)) + preliminaryTotalLinearDistance = totalLinearDistance + _driveLinearDistance + const forceOnHandle = flywheel.torque() / sprocketRadius + driveHandleForce.push(flywheel.deltaTime(), forceOnHandle) + const velocityOfHandle = flywheel.angularVelocity() * sprocketRadius + driveHandleVelocity.push(flywheel.deltaTime(), velocityOfHandle) + const powerOnHandle = flywheel.torque() * flywheel.angularVelocity() + driveHandlePower.push(flywheel.deltaTime(), powerOnHandle) + } + + function endDrivePhase () { + // Here, we conclude the Drive Phase + // The FSM guarantees that we have a credible driveDuration and cycletime + _driveDuration = flywheel.spinningTime() - drivePhaseStartTime + _cycleDuration = _recoveryDuration + _driveDuration + drivePhaseAngularDisplacement = flywheel.angularPosition() - drivePhaseStartAngularPosition + _driveLength = drivePhaseAngularDisplacement * sprocketRadius + _driveLinearDistance = calculateLinearDistance(drivePhaseAngularDisplacement, _driveDuration) + totalLinearDistance += _driveLinearDistance + _cyclePower = calculateCyclePower() + _cycleLinearVelocity = calculateLinearVelocity(drivePhaseAngularDisplacement + recoveryPhaseAngularDisplacement, _cycleDuration) + preliminaryTotalLinearDistance = totalLinearDistance + } + + function startRecoveryPhase () { + // Next, we start the Recovery Phase + recoveryPhaseStartTime = flywheel.spinningTime() + recoveryPhaseStartAngularPosition = flywheel.angularPosition() + flywheel.markRecoveryPhaseStart() + } + + function updateRecoveryPhase () { + // Update the key metrics on each impulse + recoveryPhaseAngularDisplacement = flywheel.angularPosition() - recoveryPhaseStartAngularPosition + _recoveryLinearDistance = calculateLinearDistance(recoveryPhaseAngularDisplacement, (flywheel.spinningTime() - recoveryPhaseStartTime)) + preliminaryTotalLinearDistance = totalLinearDistance + _recoveryLinearDistance + } + + function endRecoveryPhase () { + // First, we conclude the recovery phase + // The FSM guarantees that we have a credible recoveryDuration and cycletime + _recoveryDuration = flywheel.spinningTime() - recoveryPhaseStartTime + _cycleDuration = _recoveryDuration + _driveDuration + recoveryPhaseAngularDisplacement = flywheel.angularPosition() - recoveryPhaseStartAngularPosition + _recoveryLinearDistance = calculateLinearDistance(recoveryPhaseAngularDisplacement, _recoveryDuration) + totalLinearDistance += _recoveryLinearDistance + preliminaryTotalLinearDistance = totalLinearDistance + _cycleLinearVelocity = calculateLinearVelocity(drivePhaseAngularDisplacement + recoveryPhaseAngularDisplacement, _cycleDuration) + _cyclePower = calculateCyclePower() + flywheel.markRecoveryPhaseCompleted() + } + + function calculateLinearDistance (baseAngularDisplacement, baseTime) { + if (baseAngularDisplacement >= 0) { + return Math.pow((flywheel.dragFactor() / rowerSettings.magicConstant), 1.0 / 3.0) * baseAngularDisplacement + } else { + log.error(`Time: ${flywheel.spinningTime().toFixed(4)} sec: calculateLinearDistance error: baseAngularDisplacement was not credible, baseTime: ${baseAngularDisplacement}`) + return 0 + } + } + + function calculateLinearVelocity (baseAngularDisplacement, baseTime) { + // Here we calculate the AVERAGE speed for the displays, NOT the topspeed of the stroke + const prevLinearVelocity = _cycleLinearVelocity + if (baseAngularDisplacement > 0 && baseTime > 0) { + // let's prevent division's by zero and make sure data is credible + const baseAngularVelocity = baseAngularDisplacement / baseTime + return Math.pow((flywheel.dragFactor() / rowerSettings.magicConstant), 1.0 / 3.0) * baseAngularVelocity + } else { + log.error(`Time: ${flywheel.spinningTime().toFixed(4)} sec: calculateLinearVelocity error, Angular Displacement = ${baseAngularDisplacement}, baseTime = ${baseTime}`) + return prevLinearVelocity + } + } + + function calculateCyclePower () { + // Here we calculate the AVERAGE power for the displays, NOT the top power of the stroke + const prevCyclePower = _cyclePower + if (_driveDuration >= rowerSettings.minimumDriveTime && _cycleDuration >= minimumCycleDuration) { + // let's prevent division's by zero and make sure data is credible + return flywheel.dragFactor() * Math.pow((recoveryPhaseAngularDisplacement + drivePhaseAngularDisplacement) / _cycleDuration, 3.0) + } else { + log.error(`Time: ${flywheel.spinningTime().toFixed(4)} sec: calculateCyclePower error: driveDuration = ${_driveDuration.toFixed(4)} sec, _cycleDuration = ${_cycleDuration.toFixed(4)} sec`) + return prevCyclePower + } + } + + function strokeState () { + return _strokeState + } + + function totalNumberOfStrokes () { + return _totalNumberOfStrokes + } + + function totalMovingTimeSinceStart () { + return flywheel.spinningTime() + } + + function driveLastStartTime () { + return drivePhaseStartTime + } + + function totalLinearDistanceSinceStart () { + return Math.max(preliminaryTotalLinearDistance, totalLinearDistance) + } + + function cycleDuration () { + // ToDo: return 0 in the situation where the first cycle hasn't completed yet + return _cycleDuration + } + + function cycleLinearDistance () { + // ToDo: return 0 in the situation where the first cycle hasn't completed yet + return _driveLinearDistance + _recoveryLinearDistance + } + + function cycleLinearVelocity () { + // ToDo: return 0 in the situation where the first cycle hasn't completed yet + return _cycleLinearVelocity + } + + function cyclePower () { + // ToDo: return 0 in the situation where the first cycle hasn't completed yet + return _cyclePower + } + + function driveDuration () { + return _driveDuration + } + + function driveLinearDistance () { + return _driveLinearDistance + } + + function driveLength () { + return _driveLength + } + + function driveAverageHandleForce () { + return driveHandleForce.average() + } + + function drivePeakHandleForce () { + return driveHandleForce.peak() + } + + function driveHandleForceCurve () { + return driveHandleForce.curve() + } + + function driveHandleVelocityCurve () { + return driveHandleVelocity.curve() + } + + function driveHandlePowerCurve () { + return driveHandlePower.curve() + } + + function recoveryDuration () { + return _recoveryDuration + } + + function recoveryDragFactor () { + return flywheel.dragFactor() * 1000000 + } + + function instantHandlePower () { + if (_strokeState === 'Drive') { + return flywheel.torque() * flywheel.angularVelocity() + } else { + return 0 + } + } + + function allowMovement () { + if (_strokeState === 'Stopped') { + // We have to check whether there actually was a stop/pause, in order to prevent weird behaviour from the state machine + log.debug(`*** ALLOW MOVEMENT command by RowingEngine recieved at time: ${flywheel.spinningTime().toFixed(4)} sec`) + _strokeState = 'WaitingForDrive' + } + } + + function pauseMoving () { + log.debug(`*** PAUSE MOVING command recieved by RowingEngine at time: ${flywheel.spinningTime().toFixed(4)} sec, distance: ${preliminaryTotalLinearDistance.toFixed(2)} meters`) + flywheel.maintainStateOnly() + _strokeState = 'WaitingForDrive' + } + + function stopMoving () { + log.debug(`*** STOP MOVING command recieved by RowingEngine at time: ${flywheel.spinningTime().toFixed(4)} sec, distance: ${preliminaryTotalLinearDistance.toFixed(2)} meters`) + flywheel.maintainStateOnly() + _strokeState = 'Stopped' + } + + function reset () { + _strokeState = 'WaitingForDrive' + flywheel.reset() + _totalNumberOfStrokes = -1.0 + drivePhaseStartTime = 0.0 + drivePhaseStartAngularPosition = 0.0 + _driveDuration = 0.0 + drivePhaseAngularDisplacement = 0.0 + _driveLinearDistance = 0.0 + recoveryPhaseStartTime = 0.0 + _recoveryDuration = 0.0 + recoveryPhaseStartAngularPosition = 0.0 + recoveryPhaseAngularDisplacement = 0.0 + _recoveryLinearDistance = 0.0 + _cycleDuration = 0.0 + _cycleLinearVelocity = 0.0 + totalLinearDistance = 0.0 + preliminaryTotalLinearDistance = 0.0 + _cyclePower = 0.0 + _driveLength = 0.0 + } + + return { + handleRotationImpulse, + allowMovement, + pauseMoving, + stopMoving, + strokeState, + totalNumberOfStrokes, + driveLastStartTime, + totalMovingTimeSinceStart, + totalLinearDistanceSinceStart, + cycleDuration, + cycleLinearDistance, + cycleLinearVelocity, + cyclePower, + driveDuration, + driveLinearDistance, + driveLength, + driveAverageHandleForce, + drivePeakHandleForce, + driveHandleForceCurve, + driveHandleVelocityCurve, + driveHandlePowerCurve, + recoveryDuration, + recoveryDragFactor, + instantHandlePower, + reset + } +} + +export { createRower } diff --git a/app/engine/Rower.test.js b/app/engine/Rower.test.js new file mode 100644 index 0000000000..357911a625 --- /dev/null +++ b/app/engine/Rower.test.js @@ -0,0 +1,489 @@ +'use strict' +/* + + This test is a test of the Rower object, that tests wether this object fills all fields correctly, given one validated rower, (the + Concept2 RowErg) using a validated cycle of strokes. This thoroughly tests the raw physics of the translation of Angular physics + to Linear physics. The combination with all possible known rowers is tested when testing the above function RowingStatistics, as + these statistics are dependent on these settings as well. +*/ +import { test } from 'uvu' +import * as assert from 'uvu/assert' +import rowerProfiles from '../../config/rowerProfiles.js' +import { replayRowingSession } from '../tools/RowingRecorder.js' +import { deepMerge } from '../tools/Helper.js' + +import { createRower } from './Rower.js' + +const baseConfig = { // Based on Concept 2 settings, as this is the validation system + numOfImpulsesPerRevolution: 6, + sprocketRadius: 1.4, + maximumStrokeTimeBeforePause: 0.3, // Modification to standard settings to shorten test cases + dragFactor: 110, + autoAdjustDragFactor: true, + minimumDragQuality: 0.95, + dragFactorSmoothing: 3, + minimumTimeBetweenImpulses: 0.005, + maximumTimeBetweenImpulses: 0.017, + flankLength: 12, + smoothing: 1, + minimumStrokeQuality: 0.36, + minumumForceBeforeStroke: 20, // Modification to standard settings to shorten test cases + minumumRecoverySlope: 0.00070, + autoAdjustRecoverySlope: false, // Modification to standard settings to shorten test cases + autoAdjustRecoverySlopeMargin: 0.04, + minimumDriveTime: 0.04, // Modification to standard settings to shorten test cases + minimumRecoveryTime: 0.09, // Modification to standard settings to shorten test cases + flywheelInertia: 0.10138, + magicConstant: 2.8 +} + +// Test behaviour for no datapoints +test('Correct rower behaviour at initialisation', () => { + const rower = createRower(baseConfig) + testStrokeState(rower, 'WaitingForDrive') + testTotalMovingTimeSinceStart(rower, 0) + testTotalNumberOfStrokes(rower, 0) + testTotalLinearDistanceSinceStart(rower, 0) + testCycleDuration(rower, 0.13) // Default value + testCycleLinearDistance(rower, 0) + testCycleLinearVelocity(rower, 0) + testCyclePower(rower, 0) + testDriveDuration(rower, 0) + testDriveLinearDistance(rower, 0) + testDriveLength(rower, 0) + testDriveAverageHandleForce(rower, 0) + testDrivePeakHandleForce(rower, 0) + testRecoveryDuration(rower, 0) + testRecoveryDragFactor(rower, 110) + testInstantHandlePower(rower, 0) +}) + +// Test behaviour for one datapoint + +// Test behaviour for three perfect identical strokes, including settingling behaviour of metrics +test('Test behaviour for three perfect identical strokes, including settingling behaviour of metrics', () => { + const rower = createRower(baseConfig) + testStrokeState(rower, 'WaitingForDrive') + testTotalMovingTimeSinceStart(rower, 0) + testTotalLinearDistanceSinceStart(rower, 0) + testTotalNumberOfStrokes(rower, 0) + testCycleDuration(rower, 0.13) // Default value + testCycleLinearDistance(rower, 0) + testCycleLinearVelocity(rower, 0) + testCyclePower(rower, 0) + testDriveDuration(rower, 0) + testDriveLinearDistance(rower, 0) + testDriveLength(rower, 0) + testDriveAverageHandleForce(rower, 0) + testDrivePeakHandleForce(rower, 0) + testRecoveryDuration(rower, 0) + testRecoveryDragFactor(rower, 110) + testInstantHandlePower(rower, 0) + // Drive initial stroke starts here + rower.handleRotationImpulse(0.011221636) + rower.handleRotationImpulse(0.011175504) + rower.handleRotationImpulse(0.01116456) + rower.handleRotationImpulse(0.011130263) + rower.handleRotationImpulse(0.011082613) + rower.handleRotationImpulse(0.011081761) + rower.handleRotationImpulse(0.011062297) + rower.handleRotationImpulse(0.011051853) + rower.handleRotationImpulse(0.010973313) + rower.handleRotationImpulse(0.010919756) + rower.handleRotationImpulse(0.01086431) + rower.handleRotationImpulse(0.010800864) + rower.handleRotationImpulse(0.010956987) + rower.handleRotationImpulse(0.010653396) + rower.handleRotationImpulse(0.010648619) + rower.handleRotationImpulse(0.010536818) + rower.handleRotationImpulse(0.010526151) + rower.handleRotationImpulse(0.010511225) + rower.handleRotationImpulse(0.010386684) + testStrokeState(rower, 'Drive') + testTotalMovingTimeSinceStart(rower, 0.077918634) + testTotalLinearDistanceSinceStart(rower, 0.2491943602992768) + testTotalNumberOfStrokes(rower, 1) + testCycleDuration(rower, 0.13) // still default value + testCycleLinearDistance(rower, 0.2491943602992768) // Known issue: this shouldn't be filled at this time as the cycle isn't completed yet + testCycleLinearVelocity(rower, 0) // This isn't filled after the first drive, as we haven't survived a complete cycle yet + testCyclePower(rower, 0) // This isn't filled after the first drive, as we haven't survived a complete cycle yet + testDriveDuration(rower, 0) // Shouldn't this one be filled after the first drive? + testDriveLinearDistance(rower, 0.2491943602992768) + testDriveLength(rower, 0) // Shouldn't this one be filled after the first drive? + testDriveAverageHandleForce(rower, 1691.793078056684) + testDrivePeakHandleForce(rower, 10246.062011594136) + testRecoveryDuration(rower, 0) + testRecoveryDragFactor(rower, 110) + testInstantHandlePower(rower, 372.0199762100516) + // Recovery initial stroke starts here + rower.handleRotationImpulse(0.010769) + rower.handleRotationImpulse(0.010707554) + rower.handleRotationImpulse(0.010722165) + rower.handleRotationImpulse(0.01089567) + rower.handleRotationImpulse(0.010917504) + rower.handleRotationImpulse(0.010997969) + rower.handleRotationImpulse(0.011004655) + rower.handleRotationImpulse(0.011013618) + rower.handleRotationImpulse(0.011058193) + rower.handleRotationImpulse(0.010807149) + rower.handleRotationImpulse(0.0110626) + rower.handleRotationImpulse(0.011090787) + rower.handleRotationImpulse(0.011099509) + rower.handleRotationImpulse(0.011131862) + rower.handleRotationImpulse(0.011209919) + testStrokeState(rower, 'Recovery') + testTotalMovingTimeSinceStart(rower, 0.23894732900000007) + testTotalLinearDistanceSinceStart(rower, 0.7831822752262986) + testTotalNumberOfStrokes(rower, 1) + testCycleDuration(rower, 0.19636192600000005) + testCycleLinearDistance(rower, 0.7831822752262986) + testCycleLinearVelocity(rower, 3.2632879039515323) + testCyclePower(rower, 97.30254616461225) + testDriveDuration(rower, 0.19636192600000005) + testDriveLinearDistance(rower, 0.6407854979124261) + testDriveLength(rower, 0.2638937829015426) + testDriveAverageHandleForce(rower, 851.8820525641245) // This is the first stroke, which always leads to insane data like this + testDrivePeakHandleForce(rower, 10246.062011594136) + testRecoveryDuration(rower, 0) + testRecoveryDragFactor(rower, 110) + testInstantHandlePower(rower, 0) + // Drive seconds stroke starts here + rower.handleRotationImpulse(0.011221636) + rower.handleRotationImpulse(0.011175504) + rower.handleRotationImpulse(0.01116456) + rower.handleRotationImpulse(0.011130263) + rower.handleRotationImpulse(0.011082613) + rower.handleRotationImpulse(0.011081761) + rower.handleRotationImpulse(0.011062297) + rower.handleRotationImpulse(0.011051853) + rower.handleRotationImpulse(0.010973313) + rower.handleRotationImpulse(0.010919756) + rower.handleRotationImpulse(0.01086431) + rower.handleRotationImpulse(0.010800864) + rower.handleRotationImpulse(0.010956987) + rower.handleRotationImpulse(0.010653396) + rower.handleRotationImpulse(0.010648619) + rower.handleRotationImpulse(0.010536818) + rower.handleRotationImpulse(0.010526151) + rower.handleRotationImpulse(0.010511225) + rower.handleRotationImpulse(0.010386684) + testStrokeState(rower, 'Drive') + testTotalMovingTimeSinceStart(rower, 0.44915539800000004) + testTotalLinearDistanceSinceStart(rower, 1.5912564829320934) + testTotalNumberOfStrokes(rower, 2) + testCycleDuration(rower, 0.34889498300000005) + testCycleLinearDistance(rower, 0.9504709850196674) + testCycleLinearVelocity(rower, 3.2650920019419694) + testCyclePower(rower, 97.46401557792097) + testDriveDuration(rower, 0.19636192600000005) + testDriveLinearDistance(rower, 0.4520822644211139) + testDriveLength(rower, 0.2638937829015426) + testDriveAverageHandleForce(rower, 251.04336322997108) + testDrivePeakHandleForce(rower, 396.7011215867992) + testRecoveryDuration(rower, 0.152533057) + testRecoveryDragFactor(rower, 309.02744980039836) + testInstantHandlePower(rower, 526.5255378434941) + // Recovery second stroke starts here + rower.handleRotationImpulse(0.010769) + rower.handleRotationImpulse(0.010707554) + rower.handleRotationImpulse(0.010722165) + rower.handleRotationImpulse(0.01089567) + rower.handleRotationImpulse(0.010917504) + rower.handleRotationImpulse(0.010997969) + rower.handleRotationImpulse(0.011004655) + rower.handleRotationImpulse(0.011013618) + rower.handleRotationImpulse(0.011058193) + rower.handleRotationImpulse(0.010807149) + rower.handleRotationImpulse(0.0110626) + rower.handleRotationImpulse(0.011090787) + rower.handleRotationImpulse(0.011099509) + rower.handleRotationImpulse(0.011131862) + rower.handleRotationImpulse(0.011209919) + testStrokeState(rower, 'Recovery') + testTotalMovingTimeSinceStart(rower, 0.6101840930000001) + testTotalLinearDistanceSinceStart(rower, 2.3447269236339507) + testTotalNumberOfStrokes(rower, 2) + testCycleDuration(rower, 0.40310000200000007) + testCycleLinearDistance(rower, 1.2055527051229706) + testCycleLinearVelocity(rower, 4.6106683482425606) + testCyclePower(rower, 274.4414360493952) + testDriveDuration(rower, 0.25056694500000004) + testDriveLinearDistance(rower, 1.1553213424095137) + testDriveLength(rower, 0.3371976114853044) + testDriveAverageHandleForce(rower, 290.98159585708896) + testDrivePeakHandleForce(rower, 456.9929898648157) + testRecoveryDuration(rower, 0.152533057) + testRecoveryDragFactor(rower, 309.02744980039836) // As we decelerate the flywheel quite fast, this is expected + testInstantHandlePower(rower, 0) + // Drive third stroke starts here + rower.handleRotationImpulse(0.011221636) + rower.handleRotationImpulse(0.011175504) + rower.handleRotationImpulse(0.01116456) + rower.handleRotationImpulse(0.011130263) + rower.handleRotationImpulse(0.011082613) + rower.handleRotationImpulse(0.011081761) + rower.handleRotationImpulse(0.011062297) + rower.handleRotationImpulse(0.011051853) + rower.handleRotationImpulse(0.010973313) + rower.handleRotationImpulse(0.010919756) + rower.handleRotationImpulse(0.01086431) + rower.handleRotationImpulse(0.010800864) + rower.handleRotationImpulse(0.010956987) + rower.handleRotationImpulse(0.010653396) + rower.handleRotationImpulse(0.010648619) + rower.handleRotationImpulse(0.010536818) + rower.handleRotationImpulse(0.010526151) + rower.handleRotationImpulse(0.010511225) + rower.handleRotationImpulse(0.010386684) + testStrokeState(rower, 'Drive') + testTotalMovingTimeSinceStart(rower, 0.8203921620000004) + testTotalLinearDistanceSinceStart(rower, 3.2991228151896355) + testTotalNumberOfStrokes(rower, 3) + testCycleDuration(rower, 0.3490464680000002) + testCycleLinearDistance(rower, 1.004627254269142) + testCycleLinearVelocity(rower, 4.6051278388258226) + testCyclePower(rower, 273.453258990202) + testDriveDuration(rower, 0.25056694500000004) + testDriveLinearDistance(rower, 0.552544989848028) + testDriveLength(rower, 0.3371976114853044) + testDriveAverageHandleForce(rower, 223.750606354492) + testDrivePeakHandleForce(rower, 396.7011215854034) + testRecoveryDuration(rower, 0.09847952300000018) + testRecoveryDragFactor(rower, 309.02744980039836) + testInstantHandlePower(rower, 526.5255378417136) + // Recovery third stroke starts here + rower.handleRotationImpulse(0.010769) + rower.handleRotationImpulse(0.010707554) + rower.handleRotationImpulse(0.010722165) + rower.handleRotationImpulse(0.01089567) + rower.handleRotationImpulse(0.010917504) + rower.handleRotationImpulse(0.010997969) + rower.handleRotationImpulse(0.011004655) + rower.handleRotationImpulse(0.011013618) + rower.handleRotationImpulse(0.011058193) + rower.handleRotationImpulse(0.010807149) + rower.handleRotationImpulse(0.0110626) + rower.handleRotationImpulse(0.011090787) + rower.handleRotationImpulse(0.011099509) + rower.handleRotationImpulse(0.011131862) + rower.handleRotationImpulse(0.011209919) + testStrokeState(rower, 'Recovery') + testTotalMovingTimeSinceStart(rower, 0.9814208570000005) + testTotalLinearDistanceSinceStart(rower, 4.052593255891493) + testTotalNumberOfStrokes(rower, 3) + testCycleDuration(rower, 0.3712367640000004) + testCycleLinearDistance(rower, 1.3060154305498848) + testCycleLinearVelocity(rower, 4.600477371517923) + testCyclePower(rower, 272.62565872880714) + testDriveDuration(rower, 0.2727572410000002) + testDriveLinearDistance(rower, 1.2557840678364274) + testDriveLength(rower, 0.36651914291880905) + testDriveAverageHandleForce(rower, 272.7765993429924) + testDrivePeakHandleForce(rower, 456.99298986363897) + testRecoveryDuration(rower, 0.09847952300000018) + testRecoveryDragFactor(rower, 309.02744980039836) + testInstantHandlePower(rower, 0) + // Dwelling state starts here + rower.handleRotationImpulse(0.020769) + rower.handleRotationImpulse(0.020707554) + rower.handleRotationImpulse(0.020722165) + rower.handleRotationImpulse(0.02089567) + rower.handleRotationImpulse(0.020917504) + rower.handleRotationImpulse(0.020997969) + rower.handleRotationImpulse(0.021004655) + rower.handleRotationImpulse(0.021013618) + rower.handleRotationImpulse(0.021058193) + rower.handleRotationImpulse(0.020807149) + rower.handleRotationImpulse(0.0210626) + rower.handleRotationImpulse(0.021090787) + rower.handleRotationImpulse(0.021099509) + rower.handleRotationImpulse(0.021131862) + rower.handleRotationImpulse(0.021209919) + testStrokeState(rower, 'WaitingForDrive') + testTotalMovingTimeSinceStart(rower, 1.1137102920000004) + testTotalNumberOfStrokes(rower, 3) + testTotalLinearDistanceSinceStart(rower, 4.655369608452978) + testCycleDuration(rower, 0.4157688410000001) + testCycleLinearDistance(rower, 1.90879178311137) + testCycleLinearVelocity(rower, 4.590992866421583) + testCyclePower(rower, 270.94296880669305) + testDriveDuration(rower, 0.2727572410000002) + testDriveLinearDistance(rower, 1.2557840678364274) + testDriveLength(rower, 0.36651914291880905) + testDriveAverageHandleForce(rower, 272.7765993429924) + testDrivePeakHandleForce(rower, 456.99298986363897) + testRecoveryDuration(rower, 0.1430115999999999) + testRecoveryDragFactor(rower, 309.02744980039836) + testInstantHandlePower(rower, 0) +}) + +// Test behaviour for noisy upgoing flank + +// Test behaviour for noisy downgoing flank + +// Test behaviour for noisy stroke + +// Test behaviour after reset + +// Test behaviour for one datapoint + +// Test behaviour for noisy stroke + +// Test drag factor calculation + +// Test Dynamic stroke detection + +// Test behaviour after reset + +// Test behaviour with real-life data + +test('sample data for Sportstech WRX700 should produce plausible results', async () => { + const rower = createRower(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.Sportstech_WRX700)) + testTotalMovingTimeSinceStart(rower, 0) + testTotalLinearDistanceSinceStart(rower, 0) + testTotalNumberOfStrokes(rower, 0) + testRecoveryDragFactor(rower, rowerProfiles.Sportstech_WRX700.dragFactor) + + await replayRowingSession(rower.handleRotationImpulse, { filename: 'recordings/WRX700_2magnets.csv', realtime: false, loop: false }) + + testTotalMovingTimeSinceStart(rower, 46.302522627) + testTotalLinearDistanceSinceStart(rower, 166.29596716416734) + testTotalNumberOfStrokes(rower, 16) + // As dragFactor is static, it should remain in place + testRecoveryDragFactor(rower, rowerProfiles.Sportstech_WRX700.dragFactor) +}) + +test('sample data for DKN R-320 should produce plausible results', async () => { + const rower = createRower(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.DKN_R320)) + testTotalMovingTimeSinceStart(rower, 0) + testTotalLinearDistanceSinceStart(rower, 0) + testTotalNumberOfStrokes(rower, 0) + testRecoveryDragFactor(rower, rowerProfiles.DKN_R320.dragFactor) + + await replayRowingSession(rower.handleRotationImpulse, { filename: 'recordings/DKNR320.csv', realtime: false, loop: false }) + + testTotalMovingTimeSinceStart(rower, 21.701535821) + testTotalLinearDistanceSinceStart(rower, 70.11298001986664) + testTotalNumberOfStrokes(rower, 10) + // As dragFactor is static, it should remain in place + testRecoveryDragFactor(rower, rowerProfiles.DKN_R320.dragFactor) +}) + +test('sample data for NordicTrack RX800 should produce plausible results', async () => { + const rower = createRower(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.NordicTrack_RX800)) + testTotalMovingTimeSinceStart(rower, 0) + testTotalLinearDistanceSinceStart(rower, 0) + testTotalNumberOfStrokes(rower, 0) + testRecoveryDragFactor(rower, rowerProfiles.NordicTrack_RX800.dragFactor) + + await replayRowingSession(rower.handleRotationImpulse, { filename: 'recordings/RX800.csv', realtime: false, loop: false }) + + testTotalMovingTimeSinceStart(rower, 17.389910236000024) + testTotalLinearDistanceSinceStart(rower, 62.49982252262572) + testTotalNumberOfStrokes(rower, 8) + // As dragFactor is dynamic, it should have changed + testRecoveryDragFactor(rower, 493.1277530352103) +}) + +test('A full session for SportsTech WRX700 should produce plausible results', async () => { + const rower = createRower(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.Sportstech_WRX700)) + testTotalMovingTimeSinceStart(rower, 0) + testTotalLinearDistanceSinceStart(rower, 0) + testTotalNumberOfStrokes(rower, 0) + testRecoveryDragFactor(rower, rowerProfiles.Sportstech_WRX700.dragFactor) + + await replayRowingSession(rower.handleRotationImpulse, { filename: 'recordings/WRX700_2magnets_session.csv', realtime: false, loop: false }) + + testTotalMovingTimeSinceStart(rower, 2342.741183077012) + testTotalLinearDistanceSinceStart(rower, 8409.62244161274) + testTotalNumberOfStrokes(rower, 846) + // As dragFactor is static, it should remain in place + testRecoveryDragFactor(rower, rowerProfiles.Sportstech_WRX700.dragFactor) +}) + +test('A full session for a Concept2 RowErg should produce plausible results', async () => { + const rower = createRower(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.Concept2_RowErg)) + testTotalMovingTimeSinceStart(rower, 0) + testTotalLinearDistanceSinceStart(rower, 0) + testTotalNumberOfStrokes(rower, 0) + testRecoveryDragFactor(rower, rowerProfiles.Concept2_RowErg.dragFactor) + + await replayRowingSession(rower.handleRotationImpulse, { filename: 'recordings/Concept2_RowErg_Session_2000meters.csv', realtime: false, loop: false }) + + testTotalMovingTimeSinceStart(rower, 590.111937) + testTotalLinearDistanceSinceStart(rower, 2029.6932502534587) + testTotalNumberOfStrokes(rower, 206) + // As dragFactor isn't static, it should have changed + testRecoveryDragFactor(rower, 80.79039510767821) +}) + +function testStrokeState (rower, expectedValue) { + assert.ok(rower.strokeState() === expectedValue, `strokeState should be ${expectedValue} at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.strokeState()}`) +} + +function testTotalMovingTimeSinceStart (rower, expectedValue) { + assert.ok(rower.totalMovingTimeSinceStart() === expectedValue, `totalMovingTimeSinceStart should be ${expectedValue} sec at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.totalMovingTimeSinceStart()}`) +} + +function testTotalNumberOfStrokes (rower, expectedValue) { + // Please note there is a stroke 0 + assert.ok(rower.totalNumberOfStrokes() + 1 === expectedValue, `totalNumberOfStrokes should be ${expectedValue} at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.totalNumberOfStrokes() + 1}`) +} + +function testTotalLinearDistanceSinceStart (rower, expectedValue) { + assert.ok(rower.totalLinearDistanceSinceStart() === expectedValue, `totalLinearDistanceSinceStart should be ${expectedValue} meters at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.totalLinearDistanceSinceStart()}`) +} + +function testCycleDuration (rower, expectedValue) { + assert.ok(rower.cycleDuration() === expectedValue, `cycleDuration should be ${expectedValue} sec at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.cycleDuration()}`) +} + +function testCycleLinearDistance (rower, expectedValue) { + assert.ok(rower.cycleLinearDistance() === expectedValue, `cycleLinearDistance should be ${expectedValue} meters at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.cycleLinearDistance()}`) +} + +function testCycleLinearVelocity (rower, expectedValue) { + assert.ok(rower.cycleLinearVelocity() === expectedValue, `cycleLinearVelocity should be ${expectedValue} m/s at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.cycleLinearVelocity()}`) +} + +function testCyclePower (rower, expectedValue) { + assert.ok(rower.cyclePower() === expectedValue, `cyclePower should be ${expectedValue} Watt at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.cyclePower()}`) +} + +function testDriveDuration (rower, expectedValue) { + assert.ok(rower.driveDuration() === expectedValue, `driveDuration should be ${expectedValue} sec at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.driveDuration()}`) +} + +function testDriveLinearDistance (rower, expectedValue) { + assert.ok(rower.driveLinearDistance() === expectedValue, `driveLinearDistance should be ${expectedValue} meters at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.driveLinearDistance()}`) +} + +function testDriveLength (rower, expectedValue) { + assert.ok(rower.driveLength() === expectedValue, `driveLength should be ${expectedValue} meters at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.driveLength()}`) +} + +function testDriveAverageHandleForce (rower, expectedValue) { + assert.ok(rower.driveAverageHandleForce() === expectedValue, `driveAverageHandleForce should be ${expectedValue} N at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.driveAverageHandleForce()}`) +} + +function testDrivePeakHandleForce (rower, expectedValue) { + assert.ok(rower.drivePeakHandleForce() === expectedValue, `drivePeakHandleForce should be ${expectedValue} N at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.drivePeakHandleForce()}`) +} + +function testRecoveryDuration (rower, expectedValue) { + assert.ok(rower.recoveryDuration() === expectedValue, `recoveryDuration should be ${expectedValue} sec at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.recoveryDuration()}`) +} + +function testRecoveryDragFactor (rower, expectedValue) { + assert.ok(rower.recoveryDragFactor() === expectedValue, `recoveryDragFactor should be ${expectedValue} N*m*s^2 at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.recoveryDragFactor()}`) +} + +function testInstantHandlePower (rower, expectedValue) { + assert.ok(rower.instantHandlePower() === expectedValue, `instantHandlePower should be ${expectedValue} Watt at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.instantHandlePower()}`) +} + +function reportAll (rower) { // eslint-disable-line no-unused-vars + assert.ok(0, `time: ${rower.totalMovingTimeSinceStart()}, state ${rower.strokeState()}, No Strokes: ${rower.totalNumberOfStrokes() + 1}, Lin Distance: ${rower.totalLinearDistanceSinceStart()}, cycle dur: ${rower.cycleDuration()}, cycle Lin Dist: ${rower.cycleLinearDistance()}, Lin Velocity: ${rower.cycleLinearVelocity()}, Power: ${rower.cyclePower()}, Drive Dur: ${rower.driveDuration()}, Drive Lin. Dist. ${rower.driveLinearDistance()}, Drive Length: ${rower.driveLength()}, Av. Handle Force: ${rower.driveAverageHandleForce()}, Peak Handle Force: ${rower.drivePeakHandleForce()}, Rec. Dur: ${rower.recoveryDuration()}, Dragfactor: ${rower.recoveryDragFactor()}, Inst Handle Power: ${rower.instantHandlePower()}`) +} + +test.run() diff --git a/app/engine/RowingEngine.js b/app/engine/RowingEngine.js deleted file mode 100644 index d471abdecc..0000000000 --- a/app/engine/RowingEngine.js +++ /dev/null @@ -1,342 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - The Rowing Engine models the physics of a real rowing boat. - It takes impulses from the flywheel of a rowing machine and estimates - parameters such as energy, stroke rates and movement. - - This implementation uses concepts that are described here: - Physics of Rowing by Anu Dudhia: http://eodg.atm.ox.ac.uk/user/dudhia/rowing/physics - Also Dave Vernooy has some good explanations here: https://dvernooy.github.io/projects/ergware -*/ -import loglevel from 'loglevel' -import { createMovingAverager } from './averager/MovingAverager.js' -import { createMovingFlankDetector } from './MovingFlankDetector.js' - -const log = loglevel.getLogger('RowingEngine') - -function createRowingEngine (rowerSettings) { - let workoutHandler - const flankDetector = createMovingFlankDetector(rowerSettings) - const angularDisplacementPerImpulse = (2.0 * Math.PI) / rowerSettings.numOfImpulsesPerRevolution - const movingDragAverage = createMovingAverager(rowerSettings.dampingConstantSmoothing, rowerSettings.dragFactor / 1000000) - const dragFactorMaxUpwardChange = 1 + rowerSettings.dampingConstantMaxChange - const dragFactorMaxDownwardChange = 1 - rowerSettings.dampingConstantMaxChange - const minimumCycleLength = rowerSettings.minimumDriveTime + rowerSettings.minimumRecoveryTime - let cyclePhase - let totalTime - let totalNumberOfImpulses - let strokeNumber - let drivePhaseStartTime - let drivePhaseStartAngularDisplacement - let drivePhaseLength - let drivePhaseAngularDisplacement - let driveLinearDistance - let recoveryPhaseStartTime - let recoveryPhaseAngularDisplacement - let recoveryPhaseStartAngularDisplacement - let recoveryPhaseLength - let recoveryStartAngularVelocity - let recoveryEndAngularVelocity - let recoveryLinearDistance - let currentDragFactor - let dragFactor - let cycleLength - let linearCycleVelocity - let totalLinearDistance - let averagedCyclePower - let currentTorque - let previousAngularVelocity - let currentAngularVelocity - // we use the reset function to initialize the variables above - reset() - - // called if the sensor detected an impulse, currentDt is an interval in seconds - function handleRotationImpulse (currentDt) { - // impulses that take longer than maximumImpulseTimeBeforePause seconds are considered a pause - if (currentDt > rowerSettings.maximumImpulseTimeBeforePause) { - workoutHandler.handlePause(currentDt) - return - } - - totalTime += currentDt - totalNumberOfImpulses++ - - // detect where we are in the rowing phase (drive or recovery) - flankDetector.pushValue(currentDt) - - // we implement a finite state machine that goes between "Drive" and "Recovery" phases, - // which allows a phase-change if sufficient time has passed and there is a plausible flank - if (cyclePhase === 'Drive') { - // We currently are in the "Drive" phase, lets determine what the next phase is - if (flankDetector.isFlywheelUnpowered()) { - // The flank detector detects that the flywheel has no power exerted on it - drivePhaseLength = (totalTime - flankDetector.timeToBeginOfFlank()) - drivePhaseStartTime - if (drivePhaseLength >= rowerSettings.minimumDriveTime) { - // We change into the "Recovery" phase since we have been long enough in the Drive phase, and we see a clear lack of power - // exerted on the flywheel - startRecoveryPhase(currentDt) - cyclePhase = 'Recovery' - } else { - // We seem to have lost power to the flywheel, but it is too early according to the settings. We stay in the Drive Phase - log.debug(`Time: ${totalTime.toFixed(4)} sec, impulse ${totalNumberOfImpulses}: flank suggests no power (${flankDetector.accelerationAtBeginOfFlank().toFixed(1)} rad/s2), but waiting for for recoveryPhaseLength (${recoveryPhaseLength.toFixed(4)} sec) to exceed minimumRecoveryTime (${rowerSettings.minimumRecoveryTime} sec)`) - updateDrivePhase(currentDt) - } - } else { - // We stay in the "Drive" phase as the acceleration is lacking - updateDrivePhase(currentDt) - } - } else { - // We currently are in the "Recovery" phase, lets determine what the next phase is - if (flankDetector.isFlywheelPowered()) { - // The flank detector consistently detects some force on the flywheel - recoveryPhaseLength = (totalTime - flankDetector.timeToBeginOfFlank()) - recoveryPhaseStartTime - if (recoveryPhaseLength >= rowerSettings.minimumRecoveryTime) { - // We change into the "Drive" phase if we have been long enough in the "Recovery" phase, and we see a consistent force being - // exerted on the flywheel - startDrivePhase(currentDt) - cyclePhase = 'Drive' - } else { - // We see a force, but the "Recovery" phase has been too short, we stay in the "Recovery" phase - log.debug(`Time: ${totalTime.toFixed(4)} sec, impulse ${totalNumberOfImpulses}: flank suggests power (${flankDetector.accelerationAtBeginOfFlank().toFixed(1)} rad/s2), but waiting for recoveryPhaseLength (${recoveryPhaseLength.toFixed(4)} sec) to exceed minimumRecoveryTime (${rowerSettings.minimumRecoveryTime} sec)`) - updateRecoveryPhase(currentDt) - } - } else { - // No force on the flywheel, let's continue the "Drive" phase - updateRecoveryPhase(currentDt) - } - } - } - - function startDrivePhase (currentDt) { - // First, we conclude the "Recovery" phase - log.debug('*** recovery phase completed') - if (rowerSettings.minimumRecoveryTime <= recoveryPhaseLength && rowerSettings.minimumDriveTime <= drivePhaseLength) { - // We have a plausible cycle time - cycleLength = recoveryPhaseLength + drivePhaseLength - } else { - log.debug(`CycleLength isn't plausible: recoveryPhaseLength ${recoveryPhaseLength.toFixed(4)} sec, drivePhaseLength = ${drivePhaseLength.toFixed(4)} s, maximumImpulseTimeBeforePause ${rowerSettings.maximumImpulseTimeBeforePause} s`) - } - recoveryPhaseAngularDisplacement = ((totalNumberOfImpulses - flankDetector.noImpulsesToBeginFlank()) - recoveryPhaseStartAngularDisplacement) * angularDisplacementPerImpulse - - // Calculation of the drag-factor - if (flankDetector.impulseLengthAtBeginFlank() > 0) { - recoveryEndAngularVelocity = angularDisplacementPerImpulse / flankDetector.impulseLengthAtBeginFlank() - if (recoveryPhaseLength >= rowerSettings.minimumRecoveryTime && recoveryStartAngularVelocity > 0 && recoveryEndAngularVelocity > 0) { - // Prevent division by zero and keep useless data out of our calculations - currentDragFactor = -1 * rowerSettings.flywheelInertia * ((1 / recoveryStartAngularVelocity) - (1 / recoveryEndAngularVelocity)) / recoveryPhaseLength - if (rowerSettings.autoAdjustDragFactor) { - if (currentDragFactor > (movingDragAverage.getAverage() * dragFactorMaxDownwardChange) && currentDragFactor < (movingDragAverage.getAverage() * dragFactorMaxUpwardChange)) { - // If the calculated drag factor is close to what we expect - movingDragAverage.pushValue(currentDragFactor) - dragFactor = movingDragAverage.getAverage() - log.info(`*** Calculated drag factor: ${(currentDragFactor * 1000000).toFixed(2)}`) - } else { - // The calculated drag factor is outside the plausible range - log.info(`Calculated drag factor: ${(currentDragFactor * 1000000).toFixed(2)}, which is too far off the currently used dragfactor of ${movingDragAverage.getAverage() * 1000000}`) - log.debug(`Time: ${totalTime.toFixed(4)} sec, impulse ${totalNumberOfImpulses}: recoveryStartAngularVelocity = ${recoveryStartAngularVelocity.toFixed(2)} rad/sec, recoveryEndAngularVelocity = ${recoveryEndAngularVelocity.toFixed(2)} rad/sec, recoveryPhaseLength = ${recoveryPhaseLength.toFixed(4)} sec`) - if (currentDragFactor < (movingDragAverage.getAverage() * dragFactorMaxDownwardChange)) { - // The current calculated dragfactor makes an abrupt downward change, let's follow the direction, but limit it to the maximum allowed change - movingDragAverage.pushValue(movingDragAverage.getAverage() * dragFactorMaxDownwardChange) - } else { - // The current calculated dragfactor makes an abrupt upward change, let's follow the direction, but limit it to the maximum allowed change - movingDragAverage.pushValue(movingDragAverage.getAverage() * dragFactorMaxUpwardChange) - } - dragFactor = movingDragAverage.getAverage() - log.debug(`*** Applied drag factor: ${dragFactor * 1000000}`) - } - } else { - log.info(`*** Calculated drag factor: ${(currentDragFactor * 1000000).toFixed(2)}`) - } - } else { - log.error(`Time: ${totalTime.toFixed(4)} sec, impulse ${totalNumberOfImpulses}: division by 0 prevented, recoveryPhaseLength = ${recoveryPhaseLength} sec, recoveryStartAngularVelocity = ${recoveryStartAngularVelocity} rad/sec, recoveryEndAngularVelocity = ${recoveryEndAngularVelocity} rad/sec`) - } - } else { - log.error(`Time: ${totalTime.toFixed(4)} sec, impulse ${totalNumberOfImpulses}: division by 0 prevented, impulseLengthAtBeginFlank = ${flankDetector.impulseLengthAtBeginFlank()} sec`) - } - - // Calculate the key metrics - recoveryLinearDistance = Math.pow((dragFactor / rowerSettings.magicConstant), 1.0 / 3.0) * recoveryPhaseAngularDisplacement - totalLinearDistance += recoveryLinearDistance - currentTorque = calculateTorque(currentDt) - linearCycleVelocity = calculateLinearVelocity() - averagedCyclePower = calculateCyclePower() - - // Next, we start the "Drive" Phase - log.debug(`*** DRIVE phase started at time: ${totalTime.toFixed(4)} sec, impulse number ${totalNumberOfImpulses}`) - strokeNumber++ - drivePhaseStartTime = totalTime - flankDetector.timeToBeginOfFlank() - drivePhaseStartAngularDisplacement = totalNumberOfImpulses - flankDetector.noImpulsesToBeginFlank() - - // Update the metrics - if (workoutHandler) { - workoutHandler.handleRecoveryEnd({ - timeSinceStart: totalTime, - power: averagedCyclePower, - duration: cycleLength, - strokeDistance: driveLinearDistance + recoveryLinearDistance, - durationDrivePhase: drivePhaseLength, - speed: linearCycleVelocity, - distance: totalLinearDistance, - numberOfStrokes: strokeNumber, - instantaneousTorque: currentTorque, - strokeState: 'DRIVING' - }) - } - } - - function updateDrivePhase (currentDt) { - // Update the key metrics on each impulse - drivePhaseAngularDisplacement = ((totalNumberOfImpulses - flankDetector.noImpulsesToBeginFlank()) - drivePhaseStartAngularDisplacement) * angularDisplacementPerImpulse - driveLinearDistance = Math.pow((dragFactor / rowerSettings.magicConstant), 1.0 / 3.0) * drivePhaseAngularDisplacement - currentTorque = calculateTorque(currentDt) - if (workoutHandler) { - workoutHandler.updateKeyMetrics({ - timeSinceStart: totalTime, - distance: totalLinearDistance + driveLinearDistance, - instantaneousTorque: currentTorque - }) - } - } - - function startRecoveryPhase (currentDt) { - // First, we conclude the "Drive" Phase - log.debug('*** drive phase completed') - if (rowerSettings.minimumRecoveryTime <= recoveryPhaseLength && rowerSettings.minimumDriveTime <= drivePhaseLength) { - // We have a plausible cycle time - cycleLength = recoveryPhaseLength + drivePhaseLength - } else { - log.debug(`CycleLength wasn't plausible: recoveryPhaseLength ${recoveryPhaseLength.toFixed(4)} sec, drivePhaseLength = ${drivePhaseLength.toFixed(4)} s`) - } - drivePhaseAngularDisplacement = ((totalNumberOfImpulses - flankDetector.noImpulsesToBeginFlank()) - drivePhaseStartAngularDisplacement) * angularDisplacementPerImpulse - // driveEndAngularVelocity = angularDisplacementPerImpulse / flankDetector.impulseLengthAtBeginFlank() - driveLinearDistance = Math.pow((dragFactor / rowerSettings.magicConstant), 1.0 / 3.0) * drivePhaseAngularDisplacement - totalLinearDistance += driveLinearDistance - currentTorque = calculateTorque(currentDt) - // We display the AVERAGE speed in the display, NOT the top speed of the stroke - linearCycleVelocity = calculateLinearVelocity() - averagedCyclePower = calculateCyclePower() - - // Next, we start the "Recovery" Phase - log.debug(`*** RECOVERY phase started at time: ${totalTime.toFixed(4)} sec, impuls number ${totalNumberOfImpulses}`) - recoveryPhaseStartTime = totalTime - flankDetector.timeToBeginOfFlank() - recoveryPhaseStartAngularDisplacement = totalNumberOfImpulses - flankDetector.noImpulsesToBeginFlank() - if (flankDetector.impulseLengthAtBeginFlank() > 0) { - recoveryStartAngularVelocity = angularDisplacementPerImpulse / flankDetector.impulseLengthAtBeginFlank() - } else { - log.error(`Time: ${totalTime.toFixed(4)} sec, impuls ${totalNumberOfImpulses}: division by 0 prevented, flankDetector.impulseLengthAtBeginFlank() is ${flankDetector.impulseLengthAtBeginFlank()} sec`) - } - - // Update the metrics - if (workoutHandler) { - workoutHandler.handleDriveEnd({ - timeSinceStart: totalTime, - power: averagedCyclePower, - duration: cycleLength, - strokeDistance: driveLinearDistance + recoveryLinearDistance, - durationDrivePhase: drivePhaseLength, - speed: linearCycleVelocity, - distance: totalLinearDistance, - instantaneousTorque: currentTorque, - strokeState: 'RECOVERY' - }) - } - } - - function updateRecoveryPhase (currentDt) { - // Update the key metrics on each impulse - recoveryPhaseAngularDisplacement = ((totalNumberOfImpulses - flankDetector.noImpulsesToBeginFlank()) - recoveryPhaseStartAngularDisplacement) * angularDisplacementPerImpulse - recoveryLinearDistance = Math.pow((dragFactor / rowerSettings.magicConstant), 1.0 / 3.0) * recoveryPhaseAngularDisplacement - currentTorque = calculateTorque(currentDt) - if (workoutHandler) { - workoutHandler.updateKeyMetrics({ - timeSinceStart: totalTime, - distance: totalLinearDistance + recoveryLinearDistance, - instantaneousTorque: currentTorque - }) - } - } - - function calculateLinearVelocity () { - // Here we calculate the AVERAGE speed for the displays, NOT the topspeed of the stroke - let tempLinearVelocity = linearCycleVelocity - if (drivePhaseLength > rowerSettings.minimumDriveTime && cycleLength > minimumCycleLength) { - // There is no division by zero and the data data is plausible - tempLinearVelocity = Math.pow((dragFactor / rowerSettings.magicConstant), 1.0 / 3.0) * ((recoveryPhaseAngularDisplacement + drivePhaseAngularDisplacement) / cycleLength) - } else { - log.error(`Time: ${totalTime.toFixed(4)} sec, impuls ${totalNumberOfImpulses}: cycle length was not plausible, CycleLength = ${cycleLength} sec`) - } - return tempLinearVelocity - } - - function calculateCyclePower () { - // Here we calculate the AVERAGE power for the displays, NOT the top power of the stroke - let cyclePower = averagedCyclePower - if (drivePhaseLength > rowerSettings.minimumDriveTime && cycleLength > minimumCycleLength) { - // There is no division by zero and the data data is plausible - cyclePower = dragFactor * Math.pow((recoveryPhaseAngularDisplacement + drivePhaseAngularDisplacement) / cycleLength, 3.0) - } else { - log.error(`Time: ${totalTime.toFixed(4)} sec, impulse ${totalNumberOfImpulses}: cycle length was not plausible, CycleLength = ${cycleLength} sec`) - } - return cyclePower - } - - function calculateTorque (currentDt) { - let torque = currentTorque - if (currentDt > 0) { - previousAngularVelocity = currentAngularVelocity - currentAngularVelocity = angularDisplacementPerImpulse / currentDt - torque = rowerSettings.flywheelInertia * ((currentAngularVelocity - previousAngularVelocity) / currentDt) + dragFactor * Math.pow(currentAngularVelocity, 2) - } - return torque - } - - function reset () { - // to init displacements with plausible defaults we assume, that one rowing cycle transforms to nine meters of distance... - const defaultDisplacementForRowingCycle = 8.0 / Math.pow(((rowerSettings.dragFactor / 1000000) / rowerSettings.magicConstant), 1.0 / 3.0) - - movingDragAverage.reset() - cyclePhase = 'Recovery' - totalTime = 0.0 - totalNumberOfImpulses = 0.0 - strokeNumber = 0.0 - drivePhaseStartTime = 0.0 - drivePhaseStartAngularDisplacement = 0.0 - drivePhaseLength = 2.0 * rowerSettings.minimumDriveTime - // split defaultDisplacementForRowingCycle to aprox 1/3 for the drive phase - drivePhaseAngularDisplacement = (1.0 / 3.0) * defaultDisplacementForRowingCycle - driveLinearDistance = 0.0 - // Make sure that the first CurrentDt will trigger a detected stroke by faking a recovery phase that is long enough - recoveryPhaseStartTime = -2 * rowerSettings.minimumRecoveryTime - // and split defaultDisplacementForRowingCycle to aprox 2/3 for the recovery phase - recoveryPhaseAngularDisplacement = (2.0 / 3.0) * defaultDisplacementForRowingCycle - // set this to the number of impulses required to generate the angular displacement as assumed above - recoveryPhaseStartAngularDisplacement = Math.round(-1.0 * (2.0 / 3.0) * defaultDisplacementForRowingCycle / angularDisplacementPerImpulse) - recoveryPhaseLength = 2.0 * rowerSettings.minimumRecoveryTime - recoveryStartAngularVelocity = angularDisplacementPerImpulse / rowerSettings.minimumTimeBetweenImpulses - recoveryEndAngularVelocity = angularDisplacementPerImpulse / rowerSettings.maximumTimeBetweenImpulses - recoveryLinearDistance = 0.0 - currentDragFactor = rowerSettings.dragFactor / 1000000 - dragFactor = movingDragAverage.getAverage() - cycleLength = minimumCycleLength - linearCycleVelocity = 0.0 - totalLinearDistance = 0.0 - averagedCyclePower = 0.0 - currentTorque = 0.0 - previousAngularVelocity = 0.0 - currentAngularVelocity = 0.0 - } - - function notify (receiver) { - workoutHandler = receiver - } - - return { - handleRotationImpulse, - reset, - notify - } -} - -export { createRowingEngine } diff --git a/app/engine/RowingEngine.test.js b/app/engine/RowingEngine.test.js deleted file mode 100644 index 5c4d2240a5..0000000000 --- a/app/engine/RowingEngine.test.js +++ /dev/null @@ -1,103 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor -*/ -import { test } from 'uvu' -import * as assert from 'uvu/assert' -import loglevel from 'loglevel' - -import rowerProfiles from '../../config/rowerProfiles.js' -import { createRowingEngine } from './RowingEngine.js' -import { replayRowingSession } from '../tools/RowingRecorder.js' -import { deepMerge } from '../tools/Helper.js' - -const log = loglevel.getLogger('RowingEngine.test') -log.setLevel('warn') - -const createWorkoutEvaluator = function () { - const strokes = [] - - function handleDriveEnd (stroke) { - strokes.push(stroke) - log.info(`stroke: ${strokes.length}, power: ${Math.round(stroke.power)}w, duration: ${stroke.duration.toFixed(2)}s, ` + - ` drivePhase: ${stroke.durationDrivePhase.toFixed(2)}s, distance: ${stroke.distance.toFixed(2)}m`) - } - function updateKeyMetrics () {} - function handleRecoveryEnd () {} - function handlePause () {} - function getNumOfStrokes () { - return strokes.length - } - function getMaxStrokePower () { - return strokes.map((stroke) => stroke.power).reduce((acc, power) => Math.max(acc, power)) - } - function getMinStrokePower () { - return strokes.map((stroke) => stroke.power).reduce((acc, power) => Math.max(acc, power)) - } - function getDistanceSum () { - return strokes.map((stroke) => stroke.strokeDistance).reduce((acc, strokeDistance) => acc + strokeDistance) - } - function getDistanceTotal () { - return strokes[strokes.length - 1].distance - } - - return { - handleDriveEnd, - handleRecoveryEnd, - updateKeyMetrics, - handlePause, - getNumOfStrokes, - getMaxStrokePower, - getMinStrokePower, - getDistanceSum, - getDistanceTotal - } -} - -test('sample data for WRX700 should produce plausible results with rower profile', async () => { - const rowingEngine = createRowingEngine(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.WRX700)) - const workoutEvaluator = createWorkoutEvaluator() - rowingEngine.notify(workoutEvaluator) - await replayRowingSession(rowingEngine.handleRotationImpulse, { filename: 'recordings/WRX700_2magnets.csv' }) - assert.is(workoutEvaluator.getNumOfStrokes(), 16, 'number of strokes does not meet expectation') - assertPowerRange(workoutEvaluator, 50, 220) - assertDistanceRange(workoutEvaluator, 165, 168) - assertStrokeDistanceSumMatchesTotal(workoutEvaluator) -}) - -test('sample data for DKNR320 should produce plausible results with rower profile', async () => { - const rowingEngine = createRowingEngine(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.DKNR320)) - const workoutEvaluator = createWorkoutEvaluator() - rowingEngine.notify(workoutEvaluator) - await replayRowingSession(rowingEngine.handleRotationImpulse, { filename: 'recordings/DKNR320.csv' }) - assert.is(workoutEvaluator.getNumOfStrokes(), 10, 'number of strokes does not meet expectation') - assertPowerRange(workoutEvaluator, 75, 200) - assertDistanceRange(workoutEvaluator, 71, 73) - assertStrokeDistanceSumMatchesTotal(workoutEvaluator) -}) - -test('sample data for RX800 should produce plausible results with rower profile', async () => { - const rowingEngine = createRowingEngine(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.RX800)) - const workoutEvaluator = createWorkoutEvaluator() - rowingEngine.notify(workoutEvaluator) - await replayRowingSession(rowingEngine.handleRotationImpulse, { filename: 'recordings/RX800.csv' }) - assert.is(workoutEvaluator.getNumOfStrokes(), 10, 'number of strokes does not meet expectation') - assertPowerRange(workoutEvaluator, 80, 200) - assertDistanceRange(workoutEvaluator, 70, 80) - assertStrokeDistanceSumMatchesTotal(workoutEvaluator) -}) - -function assertPowerRange (evaluator, minPower, maxPower) { - assert.ok(evaluator.getMinStrokePower() > minPower, `minimum stroke power should be above ${minPower}w, but is ${evaluator.getMinStrokePower()}w`) - assert.ok(evaluator.getMaxStrokePower() < maxPower, `maximum stroke power should be below ${maxPower}w, but is ${evaluator.getMaxStrokePower()}w`) -} - -function assertDistanceRange (evaluator, minDistance, maxDistance) { - assert.ok(evaluator.getDistanceSum() >= minDistance && evaluator.getDistanceSum() <= maxDistance, `distance should be between ${minDistance}m and ${maxDistance}m, but is ${evaluator.getDistanceSum().toFixed(2)}m`) -} - -function assertStrokeDistanceSumMatchesTotal (evaluator) { - assert.ok(evaluator.getDistanceSum().toFixed(2) === evaluator.getDistanceTotal().toFixed(2), `sum of distance of all strokes is ${evaluator.getDistanceSum().toFixed(2)}m, but total in last stroke is ${evaluator.getDistanceTotal().toFixed(2)}m`) -} - -test.run() diff --git a/app/engine/RowingStatistics.js b/app/engine/RowingStatistics.js index f683ddd705..3bdb0e4b0b 100644 --- a/app/engine/RowingStatistics.js +++ b/app/engine/RowingStatistics.js @@ -5,223 +5,427 @@ This Module calculates the training specific metrics. */ import { EventEmitter } from 'events' -import { createMovingIntervalAverager } from './averager/MovingIntervalAverager.js' -import { createWeightedAverager } from './averager/WeightedAverager.js' +import { createRower } from './Rower.js' +import { createOLSLinearSeries } from './utils/OLSLinearSeries.js' +import { createStreamFilter } from './utils/StreamFilter.js' +import { createCurveAligner } from './utils/CurveAligner.js' import loglevel from 'loglevel' +import { secondsToTimeString } from '../tools/Helper.js' const log = loglevel.getLogger('RowingEngine') function createRowingStatistics (config) { const numOfDataPointsForAveraging = config.numOfPhasesForAveragingScreenData const webUpdateInterval = config.webUpdateInterval - const minimumStrokeTime = config.rowerSettings.minimumRecoveryTime + config.rowerSettings.minimumDriveTime - const maximumStrokeTime = config.maximumStrokeTime - const timeBetweenStrokesBeforePause = maximumStrokeTime * 1000 + const peripheralUpdateInterval = config.peripheralUpdateInterval const emitter = new EventEmitter() - const strokeAverager = createWeightedAverager(numOfDataPointsForAveraging) - const powerAverager = createWeightedAverager(numOfDataPointsForAveraging) - const speedAverager = createWeightedAverager(numOfDataPointsForAveraging) - const powerRatioAverager = createWeightedAverager(numOfDataPointsForAveraging) - const caloriesAveragerMinute = createMovingIntervalAverager(60) - const caloriesAveragerHour = createMovingIntervalAverager(60 * 60) - let sessionState = 'waitingForStart' - let rowingPausedTimer - let heartrateResetTimer - let distanceTotal = 0.0 - let durationTotal = 0 - let strokesTotal = 0 - let caloriesTotal = 0.0 + const rower = createRower(config.rowerSettings) + const minimumStrokeTime = config.rowerSettings.minimumRecoveryTime + config.rowerSettings.minimumDriveTime + const maximumStrokeTime = config.rowerSettings.maximumStrokeTimeBeforePause + const cycleDuration = createStreamFilter(numOfDataPointsForAveraging, (minimumStrokeTime + maximumStrokeTime) / 2) + const cycleDistance = createStreamFilter(numOfDataPointsForAveraging, 0) + const cyclePower = createStreamFilter(numOfDataPointsForAveraging, 0) + const cycleLinearVelocity = createStreamFilter(numOfDataPointsForAveraging, 0) + let sessionStatus = 'WaitingForStart' + let intervalSettings = [] + let currentIntervalNumber = -1 + let intervalTargetDistance = 0 + let intervalTargetTime = 0 + let intervalPrevAccumulatedDistance = 0 + let intervalPrevAccumulatedTime = 0 + let heartRateResetTimer + let totalLinearDistance = 0.0 + let totalMovingTime = 0 + let totalNumberOfStrokes = 0 + let driveLastStartTime = 0 + let strokeCalories = 0 + let strokeWork = 0 + const calories = createOLSLinearSeries() + const distanceOverTime = createOLSLinearSeries(Math.min(4, numOfDataPointsForAveraging)) + const driveDuration = createStreamFilter(numOfDataPointsForAveraging, config.rowerSettings.minimumDriveTime) + const driveLength = createStreamFilter(numOfDataPointsForAveraging, 1.1) + const driveDistance = createStreamFilter(numOfDataPointsForAveraging, 3) + const recoveryDuration = createStreamFilter(numOfDataPointsForAveraging, config.rowerSettings.minimumRecoveryTime) + const driveAverageHandleForce = createStreamFilter(numOfDataPointsForAveraging, 0.0) + const drivePeakHandleForce = createStreamFilter(numOfDataPointsForAveraging, 0.0) + const driveHandleForceCurve = createCurveAligner(config.rowerSettings.minumumForceBeforeStroke) + const driveHandleVelocityCurve = createCurveAligner(1.0) + const driveHandlePowerCurve = createCurveAligner(50) + let dragFactor = config.rowerSettings.dragFactor let heartrate = 0 - let heartrateBatteryLevel = 0 - let lastStrokeDuration = 0.0 - let instantaneousTorque = 0.0 - let lastStrokeDistance = 0.0 - let lastStrokeSpeed = 0.0 - let lastStrokeState = 'RECOVERY' - let lastWebMetrics = {} - - // send metrics to the web clients periodically (but only if the data has changed) + let heartRateBatteryLevel = 0 + const postExerciseHR = [] + let instantPower = 0.0 + let lastStrokeState = 'WaitingForDrive' + + // send metrics to the web clients periodically setInterval(emitWebMetrics, webUpdateInterval) - function emitWebMetrics () { - const currentWebMetrics = getMetrics() - if (Object.entries(currentWebMetrics).toString() !== Object.entries(lastWebMetrics).toString()) { - emitter.emit('webMetricsUpdate', currentWebMetrics) - lastWebMetrics = currentWebMetrics - } - } // notify bluetooth peripherall each second (even if data did not change) // todo: the FTMS protocol also supports that peripherals deliver a preferred update interval // we could respect this and set the update rate accordingly - setInterval(emitPeripheralMetrics, 1000) - function emitPeripheralMetrics () { - emitter.emit('peripheralMetricsUpdate', getMetrics()) + setInterval(emitPeripheralMetrics, peripheralUpdateInterval) + + function handleRotationImpulse (currentDt) { + // Provide the rower with new data + rower.handleRotationImpulse(currentDt) + + // This is the core of the finite state machine that defines all state transitions + switch (true) { + case (sessionStatus === 'WaitingForStart' && rower.strokeState() === 'Drive'): + sessionStatus = 'Rowing' + startTraining() + updateContinousMetrics() + emitMetrics('recoveryFinished') + break + case (sessionStatus === 'Paused' && rower.strokeState() === 'Drive'): + sessionStatus = 'Rowing' + resumeTraining() + updateContinousMetrics() + emitMetrics('recoveryFinished') + break + case (sessionStatus !== 'Stopped' && rower.strokeState() === 'Stopped'): + sessionStatus = 'Stopped' + // We need to emit the metrics AFTER the sessionstatus changes to anything other than "Rowing", which forces most merics to zero + // This is intended behaviour, as the rower/flywheel indicate the rower has stopped somehow + stopTraining() + break + case (sessionStatus === 'Rowing' && rower.strokeState() === 'WaitingForDrive'): + sessionStatus = 'Paused' + pauseTraining() + break + case (sessionStatus === 'Rowing' && lastStrokeState === 'Recovery' && rower.strokeState() === 'Drive' && isIntervalTargetReached() && isNextIntervalAvailable()): + updateContinousMetrics() + updateCycleMetrics() + handleRecoveryEnd() + activateNextIntervalParameters() + emitMetrics('intervalTargetReached') + break + case (sessionStatus === 'Rowing' && lastStrokeState === 'Recovery' && rower.strokeState() === 'Drive' && isIntervalTargetReached()): + updateContinousMetrics() + updateCycleMetrics() + handleRecoveryEnd() + stopTraining() + break + case (sessionStatus === 'Rowing' && lastStrokeState === 'Recovery' && rower.strokeState() === 'Drive'): + updateContinousMetrics() + updateCycleMetrics() + handleRecoveryEnd() + emitMetrics('recoveryFinished') + break + case (sessionStatus === 'Rowing' && lastStrokeState === 'Drive' && rower.strokeState() === 'Recovery' && isIntervalTargetReached() && isNextIntervalAvailable()): + updateContinousMetrics() + updateCycleMetrics() + handleDriveEnd() + activateNextIntervalParameters() + emitMetrics('intervalTargetReached') + break + case (sessionStatus === 'Rowing' && lastStrokeState === 'Drive' && rower.strokeState() === 'Recovery' && isIntervalTargetReached()): + updateContinousMetrics() + updateCycleMetrics() + handleDriveEnd() + stopTraining() + break + case (sessionStatus === 'Rowing' && lastStrokeState === 'Drive' && rower.strokeState() === 'Recovery'): + updateContinousMetrics() + updateCycleMetrics() + handleDriveEnd() + emitMetrics('driveFinished') + break + case (sessionStatus === 'Rowing' && isIntervalTargetReached() && isNextIntervalAvailable()): + updateContinousMetrics() + activateNextIntervalParameters() + emitMetrics('intervalTargetReached') + break + case (sessionStatus === 'Rowing' && isIntervalTargetReached()): + updateContinousMetrics() + stopTraining() + break + case (sessionStatus === 'Rowing'): + updateContinousMetrics() + break + case (sessionStatus === 'Paused'): + // We are in a paused state, we won't update any metrics + break + case (sessionStatus === 'WaitingForStart'): + // We can't change into the "Rowing" state since we are waiting for a drive phase that didn't come + break + case (sessionStatus === 'Stopped'): + // We are in a stopped state, so we won't update any metrics + break + default: + log.error(`Time: ${rower.totalMovingTimeSinceStart()}, state ${rower.strokeState()} found in the Rowing Statistics, which is not captured by Finite State Machine`) + } + lastStrokeState = rower.strokeState() + } + + function startTraining () { + rower.allowMovement() } - function handleDriveEnd (stroke) { - // if we do not get a drive for timeBetweenStrokesBeforePause milliseconds we treat this as a rowing pause - if (rowingPausedTimer)clearInterval(rowingPausedTimer) - rowingPausedTimer = setTimeout(() => pauseRowing(), timeBetweenStrokesBeforePause) + function allowResumeTraining () { + rower.allowMovement() + sessionStatus = 'WaitingForStart' + } - // based on: http://eodg.atm.ox.ac.uk/user/dudhia/rowing/physics/ergometer.html#section11 - const calories = (4 * powerAverager.getAverage() + 350) * (stroke.duration) / 4200 - durationTotal = stroke.timeSinceStart - powerAverager.pushValue(stroke.power) - speedAverager.pushValue(stroke.speed) - if (stroke.duration < timeBetweenStrokesBeforePause && stroke.duration > minimumStrokeTime) { - // stroke duration has to be plausible to be accepted - powerRatioAverager.pushValue(stroke.durationDrivePhase / stroke.duration) - strokeAverager.pushValue(stroke.duration) - caloriesAveragerMinute.pushValue(calories, stroke.duration) - caloriesAveragerHour.pushValue(calories, stroke.duration) + function resumeTraining () { + rower.allowMovement() + } + + function stopTraining () { + rower.stopMoving() + lastStrokeState = 'Stopped' + // Emitting the metrics BEFORE the sessionstatus changes to anything other than "Rowing" forces most merics to zero + // As there are more than one way to this method, we FIRST emit the metrics and then set them to zero + // If they need to be forced to zero (as the flywheel seems to have stopped), this status has to be set before the call + emitMetrics('rowingStopped') + sessionStatus = 'Stopped' + postExerciseHR.splice(0, postExerciseHR.length) + measureRecoveryHR() + } + + // clear the metrics in case the user pauses rowing + function pauseTraining () { + log.debug('*** Paused rowing ***') + rower.pauseMoving() + cycleDuration.reset() + cycleDistance.reset() + cyclePower.reset() + cycleLinearVelocity.reset() + lastStrokeState = 'WaitingForDrive' + // We need to emit the metrics BEFORE the sessionstatus changes to anything other than "Rowing", as it forces most merics to zero + emitMetrics('rowingPaused') + sessionStatus = 'Paused' + postExerciseHR.splice(0, postExerciseHR.length) + measureRecoveryHR() + } + + function resetTraining () { + stopTraining() + rower.reset() + calories.reset() + rower.allowMovement() + totalMovingTime = 0 + totalLinearDistance = 0.0 + intervalSettings = [] + currentIntervalNumber = -1 + intervalTargetDistance = 0 + intervalTargetTime = 0 + intervalPrevAccumulatedDistance = 0 + intervalPrevAccumulatedTime = 0 + totalNumberOfStrokes = -1 + driveLastStartTime = 0 + distanceOverTime.reset() + driveDuration.reset() + cycleDuration.reset() + cycleDistance.reset() + cyclePower.reset() + strokeCalories = 0 + strokeWork = 0 + postExerciseHR.splice(0, postExerciseHR.length) + cycleLinearVelocity.reset() + lastStrokeState = 'WaitingForDrive' + emitMetrics('rowingPaused') + sessionStatus = 'WaitingForStart' + } + + // initiated when updating key statistics + function updateContinousMetrics () { + totalMovingTime = rower.totalMovingTimeSinceStart() + totalLinearDistance = rower.totalLinearDistanceSinceStart() + instantPower = rower.instantHandlePower() + } + + function updateCycleMetrics () { + distanceOverTime.push(rower.totalMovingTimeSinceStart(), rower.totalLinearDistanceSinceStart()) + if (rower.cycleDuration() < maximumStrokeTime && rower.cycleDuration() > minimumStrokeTime) { + // stroke duration has to be credible to be accepted + cycleDuration.push(rower.cycleDuration()) + cycleDistance.push(rower.cycleLinearDistance()) + cycleLinearVelocity.push(rower.cycleLinearVelocity()) + cyclePower.push(rower.cyclePower()) } else { - log.debug(`*** Stroke duration of ${stroke.duration} sec is considered unreliable, skipped update stroke statistics`) + log.debug(`*** Stroke duration of ${rower.cycleDuration()} sec is considered unreliable, skipped update cycle statistics`) } - - caloriesTotal += calories - lastStrokeDuration = stroke.duration - distanceTotal = stroke.distance - lastStrokeDistance = stroke.strokeDistance - lastStrokeState = stroke.strokeState - lastStrokeSpeed = stroke.speed - instantaneousTorque = stroke.instantaneousTorque - emitter.emit('driveFinished', getMetrics()) } - // initiated by the rowing engine in case an impulse was not considered - // because it was too large - function handlePause (duration) { - sessionState = 'paused' - caloriesAveragerMinute.pushValue(0, duration) - caloriesAveragerHour.pushValue(0, duration) - emitter.emit('rowingPaused') + function handleDriveEnd () { + driveDuration.push(rower.driveDuration()) + driveLength.push(rower.driveLength()) + driveDistance.push(rower.driveLinearDistance()) + driveAverageHandleForce.push(rower.driveAverageHandleForce()) + drivePeakHandleForce.push(rower.drivePeakHandleForce()) + driveHandleForceCurve.push(rower.driveHandleForceCurve()) + driveHandleVelocityCurve.push(rower.driveHandleVelocityCurve()) + driveHandlePowerCurve.push(rower.driveHandlePowerCurve()) } // initiated when the stroke state changes - function handleRecoveryEnd (stroke) { - // todo: we need a better mechanism to communicate strokeState updates - // this is an initial hacky attempt to see if we can use it for the C2-pm5 protocol - if (sessionState !== 'rowing') startTraining() - durationTotal = stroke.timeSinceStart - powerAverager.pushValue(stroke.power) - speedAverager.pushValue(stroke.speed) - if (stroke.duration < timeBetweenStrokesBeforePause && stroke.duration > minimumStrokeTime) { - // stroke duration has to be plausible to be accepted - powerRatioAverager.pushValue(stroke.durationDrivePhase / stroke.duration) - strokeAverager.pushValue(stroke.duration) + function handleRecoveryEnd () { + totalNumberOfStrokes = rower.totalNumberOfStrokes() + driveLastStartTime = rower.driveLastStartTime() + recoveryDuration.push(rower.recoveryDuration()) + dragFactor = rower.recoveryDragFactor() + + // based on: http://eodg.atm.ox.ac.uk/user/dudhia/rowing/physics/ergometer.html#section11 + strokeCalories = (4 * cyclePower.clean() + 350) * (cycleDuration.clean()) / 4200 + strokeWork = cyclePower.clean() * cycleDuration.clean() + const totalCalories = calories.yAtSeriesEnd() + strokeCalories + calories.push(totalMovingTime, totalCalories) + } + + function setIntervalParameters (intervalParameters) { + intervalSettings = intervalParameters + currentIntervalNumber = -1 + if (intervalSettings.length > 0) { + log.info(`Workout recieved with ${intervalSettings.length} interval(s)`) + activateNextIntervalParameters() } else { - log.debug(`*** Stroke duration of ${stroke.duration} sec is considered unreliable, skipped update stroke statistics`) + // intervalParameters were empty, lets log this odd situation + log.error('Recieved workout containing no intervals') } - distanceTotal = stroke.distance - strokesTotal = stroke.numberOfStrokes - lastStrokeDistance = stroke.strokeDistance - lastStrokeState = stroke.strokeState - lastStrokeSpeed = stroke.speed - instantaneousTorque = stroke.instantaneousTorque - emitter.emit('recoveryFinished', getMetrics()) } - // initiated when updating key statistics - function updateKeyMetrics (stroke) { - durationTotal = stroke.timeSinceStart - distanceTotal = stroke.distance - instantaneousTorque = stroke.instantaneousTorque + function isIntervalTargetReached () { + // This tests wether the end of the current interval is reached + if ((intervalTargetDistance > 0 && rower.totalLinearDistanceSinceStart() >= intervalTargetDistance) || (intervalTargetTime > 0 && rower.totalMovingTimeSinceStart() >= intervalTargetTime)) { + return true + } else { + return false + } + } + + function isNextIntervalAvailable () { + // This function tests whether there is a next interval available + if (currentIntervalNumber > -1 && intervalSettings.length > 0 && intervalSettings.length > (currentIntervalNumber + 1)) { + return true + } else { + return false + } + } + + function activateNextIntervalParameters () { + if (intervalSettings.length > 0 && intervalSettings.length > (currentIntervalNumber + 1)) { + // This function sets the interval parameters in absolute distances/times + // Thus the interval target always is a projected "finishline" from the current position + intervalPrevAccumulatedTime = rower.totalMovingTimeSinceStart() + intervalPrevAccumulatedDistance = rower.totalLinearDistanceSinceStart() + + currentIntervalNumber++ + if (intervalSettings[currentIntervalNumber].targetDistance > 0) { + // A target distance is set + intervalTargetTime = 0 + intervalTargetDistance = intervalPrevAccumulatedDistance + intervalSettings[currentIntervalNumber].targetDistance + log.info(`Interval settings for interval ${currentIntervalNumber + 1} of ${intervalSettings.length}: Distance target ${intervalSettings[currentIntervalNumber].targetDistance} meters`) + } else { + // A target time is set + intervalTargetTime = intervalPrevAccumulatedTime + intervalSettings[currentIntervalNumber].targetTime + intervalTargetDistance = 0 + log.info(`Interval settings for interval ${currentIntervalNumber + 1} of ${intervalSettings.length}: time target ${secondsToTimeString(intervalSettings[currentIntervalNumber].targetTime)} minutes`) + } + } else { + log.error('Interval error: there is no next interval!') + } } // initiated when a new heart rate value is received from heart rate sensor - function handleHeartrateMeasurement (value) { + function handleHeartRateMeasurement (value) { // set the heart rate to zero if we did not receive a value for some time - if (heartrateResetTimer)clearInterval(heartrateResetTimer) - heartrateResetTimer = setTimeout(() => { + if (heartRateResetTimer)clearInterval(heartRateResetTimer) + heartRateResetTimer = setTimeout(() => { heartrate = 0 - heartrateBatteryLevel = 0 + heartRateBatteryLevel = 0 }, 6000) heartrate = value.heartrate - heartrateBatteryLevel = value.batteryLevel + heartRateBatteryLevel = value.batteryLevel } - function getMetrics () { - const splitTime = speedAverager.getAverage() !== 0 && lastStrokeSpeed > 0 ? (500.0 / speedAverager.getAverage()) : Infinity - // todo: due to sanitization we currently do not use a consistent time throughout the engine - // We will rework this section to use both absolute and sanitized time in the appropriate places. - // We will also polish up the events for the recovery and drive phase, so we get clean complete strokes from the first stroke onwards. - const averagedStrokeTime = strokeAverager.getAverage() > minimumStrokeTime && strokeAverager.getAverage() < maximumStrokeTime && lastStrokeSpeed > 0 && sessionState === 'rowing' ? strokeAverager.getAverage() : 0 // seconds - return { - sessionState, - durationTotal, - durationTotalFormatted: secondsToTimeString(durationTotal), - strokesTotal, - distanceTotal: distanceTotal > 0 ? distanceTotal : 0, // meters - caloriesTotal, // kcal - caloriesPerMinute: caloriesAveragerMinute.getAverage() > 0 ? caloriesAveragerMinute.getAverage() : 0, - caloriesPerHour: caloriesAveragerHour.getAverage() > 0 ? caloriesAveragerHour.getAverage() : 0, - strokeTime: lastStrokeDuration, // seconds - distance: lastStrokeDistance > 0 && lastStrokeSpeed > 0 && sessionState === 'rowing' ? lastStrokeDistance : 0, // meters - power: powerAverager.getAverage() > 0 && lastStrokeSpeed > 0 && sessionState === 'rowing' ? powerAverager.getAverage() : 0, // watts - split: splitTime, // seconds/500m - splitFormatted: secondsToTimeString(splitTime), - powerRatio: powerRatioAverager.getAverage() > 0 && lastStrokeSpeed > 0 && sessionState === 'rowing' ? powerRatioAverager.getAverage() : 0, - instantaneousTorque, - strokesPerMinute: averagedStrokeTime !== 0 && sessionState === 'rowing' ? (60.0 / averagedStrokeTime) : 0, - speed: speedAverager.getAverage() > 0 && lastStrokeSpeed > 0 && sessionState === 'rowing' ? (speedAverager.getAverage() * 3.6) : 0, // km/h - strokeState: lastStrokeState, - heartrate, - heartrateBatteryLevel + function measureRecoveryHR () { + // This function is called when the rowing session is stopped. postExerciseHR[0] is the last measured excercise HR + // Thus postExerciseHR[1] is Recovery HR after 1 min, etc.. + if (heartrate !== undefined && heartrate > config.userSettings.restingHR && sessionStatus !== 'Rowing') { + log.debug(`*** HRR-${postExerciseHR.length}: ${heartrate}`) + postExerciseHR.push(heartrate) + if ((postExerciseHR.length > 1) && (postExerciseHR.length <= 4)) { + // We skip reporting postExerciseHR[0] and only report measuring postExerciseHR[1], postExerciseHR[2], postExerciseHR[3] + emitter.emit('HRRecoveryUpdate', postExerciseHR) + } + if (postExerciseHR.length < 4) { + // We haven't got three post-exercise HR measurements yet, let's schedule the next measurement + setTimeout(measureRecoveryHR, 60000) + } } } - function startTraining () { - sessionState = 'rowing' + function emitWebMetrics () { + emitMetrics('webMetricsUpdate') } - function stopTraining () { - sessionState = 'stopped' - if (rowingPausedTimer)clearInterval(rowingPausedTimer) + function emitPeripheralMetrics () { + emitMetrics('peripheralMetricsUpdate') } - function resetTraining () { - stopTraining() - distanceTotal = 0.0 - strokesTotal = 0 - caloriesTotal = 0.0 - durationTotal = 0 - caloriesAveragerMinute.reset() - caloriesAveragerHour.reset() - strokeAverager.reset() - powerAverager.reset() - speedAverager.reset() - powerRatioAverager.reset() - sessionState = 'waitingForStart' + function emitMetrics (emitType = 'webMetricsUpdate') { + emitter.emit(emitType, getMetrics()) } - // clear the metrics in case the user pauses rowing - function pauseRowing () { - strokeAverager.reset() - powerAverager.reset() - speedAverager.reset() - powerRatioAverager.reset() - lastStrokeState = 'RECOVERY' - sessionState = 'paused' - emitter.emit('rowingPaused') - } - - // converts a timestamp in seconds to a human readable hh:mm:ss format - function secondsToTimeString (secondsTimeStamp) { - if (secondsTimeStamp === Infinity) return '∞' - const hours = Math.floor(secondsTimeStamp / 60 / 60) - const minutes = Math.floor(secondsTimeStamp / 60) - (hours * 60) - const seconds = Math.floor(secondsTimeStamp % 60) - let timeString = hours > 0 ? ` ${hours.toString().padStart(2, '0')}:` : '' - timeString += `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}` - return timeString + function getMetrics () { + const cyclePace = cycleLinearVelocity.clean() !== 0 && sessionStatus === 'Rowing' ? (500.0 / cycleLinearVelocity.clean()) : Infinity + return { + sessiontype: intervalTargetDistance > 0 ? 'Distance' : (intervalTargetTime > 0 ? 'Time' : 'JustRow'), + sessionStatus, + strokeState: rower.strokeState(), + totalMovingTime: totalMovingTime > 0 ? totalMovingTime : 0, + totalNumberOfStrokes: totalNumberOfStrokes > 0 ? totalNumberOfStrokes : 0, + totalLinearDistance: totalLinearDistance > 0 ? totalLinearDistance : 0, // meters + intervalNumber: Math.max(currentIntervalNumber + 1, 0), // Interval number + intervalMovingTime: totalMovingTime - intervalPrevAccumulatedTime, + intervalTargetTime: intervalTargetTime > intervalPrevAccumulatedTime ? intervalTargetTime - intervalPrevAccumulatedTime : 0, + intervalLinearDistance: totalLinearDistance - intervalPrevAccumulatedDistance, + intervalTargetDistance: intervalTargetDistance > intervalPrevAccumulatedDistance ? intervalTargetDistance - intervalPrevAccumulatedDistance : 0, + strokeCalories: strokeCalories > 0 ? strokeCalories : 0, // kCal + strokeWork: strokeWork > 0 ? strokeWork : 0, // Joules + totalCalories: calories.yAtSeriesEnd() > 0 ? calories.yAtSeriesEnd() : 0, // kcal + totalCaloriesPerMinute: totalMovingTime > 60 ? caloriesPerPeriod(totalMovingTime - 60, totalMovingTime) : caloriesPerPeriod(0, 60), + totalCaloriesPerHour: totalMovingTime > 3600 ? caloriesPerPeriod(totalMovingTime - 3600, totalMovingTime) : caloriesPerPeriod(0, 3600), + cycleDuration: cycleDuration.clean() > minimumStrokeTime && cycleDuration.clean() < maximumStrokeTime && cycleLinearVelocity.raw() > 0 && sessionStatus === 'Rowing' ? cycleDuration.clean() : NaN, // seconds + cycleStrokeRate: cycleDuration.clean() > minimumStrokeTime && cycleLinearVelocity.raw() > 0 && sessionStatus === 'Rowing' ? (60.0 / cycleDuration.clean()) : 0, // strokeRate in SPM + cycleDistance: cycleDistance.raw() > 0 && cycleLinearVelocity.raw() > 0 && sessionStatus === 'Rowing' ? cycleDistance.clean() : 0, // meters + cycleLinearVelocity: cycleLinearVelocity.clean() > 0 && sessionStatus === 'Rowing' ? cycleLinearVelocity.clean() : 0, // m/s + cyclePace: cycleLinearVelocity.clean() > 0 && sessionStatus === 'Rowing' ? cyclePace : Infinity, // seconds/500m + cyclePower: cyclePower.clean() > 0 && cycleLinearVelocity.raw() > 0 && sessionStatus === 'Rowing' ? cyclePower.clean() : 0, // watts + cycleProjectedEndTime: intervalTargetDistance > 0 ? distanceOverTime.projectY(intervalTargetDistance) : intervalTargetTime, + cycleProjectedEndLinearDistance: intervalTargetTime > 0 ? distanceOverTime.projectX(intervalTargetTime) : intervalTargetDistance, + driveLastStartTime: driveLastStartTime > 0 ? driveLastStartTime : 0, + driveDuration: driveDuration.clean() >= config.rowerSettings.minimumDriveTime && totalNumberOfStrokes > 0 && sessionStatus === 'Rowing' ? driveDuration.clean() : NaN, // seconds + driveLength: driveLength.clean() > 0 && sessionStatus === 'Rowing' ? driveLength.clean() : NaN, // meters of chain movement + driveDistance: driveDistance.clean() >= 0 && sessionStatus === 'Rowing' ? driveDistance.clean() : NaN, // meters + driveAverageHandleForce: driveAverageHandleForce.clean() > 0 && sessionStatus === 'Rowing' ? driveAverageHandleForce.clean() : NaN, + drivePeakHandleForce: drivePeakHandleForce.clean() > 0 && sessionStatus === 'Rowing' ? drivePeakHandleForce.clean() : NaN, + driveHandleForceCurve: drivePeakHandleForce.clean() > 0 && sessionStatus === 'Rowing' ? driveHandleForceCurve.lastCompleteCurve() : [], + driveHandleVelocityCurve: drivePeakHandleForce.clean() > 0 && sessionStatus === 'Rowing' ? driveHandleVelocityCurve.lastCompleteCurve() : [], + driveHandlePowerCurve: drivePeakHandleForce.clean() > 0 && sessionStatus === 'Rowing' ? driveHandlePowerCurve.lastCompleteCurve() : [], + recoveryDuration: recoveryDuration.clean() >= config.rowerSettings.minimumRecoveryTime && totalNumberOfStrokes > 0 && sessionStatus === 'Rowing' ? recoveryDuration.clean() : NaN, // seconds + dragFactor: dragFactor > 0 ? dragFactor : config.rowerSettings.dragFactor, // Dragfactor + instantPower: instantPower > 0 && rower.strokeState() === 'Drive' ? instantPower : 0, + heartrate: heartrate > 30 ? heartrate : 0, + heartRateBatteryLevel + } + } + + function caloriesPerPeriod (periodBegin, periodEnd) { + const beginCalories = calories.projectX(periodBegin) + const endCalories = calories.projectX(periodEnd) + return (endCalories - beginCalories) } return Object.assign(emitter, { - handleDriveEnd, - handlePause, - handleHeartrateMeasurement, - handleRecoveryEnd, - updateKeyMetrics, + handleHeartRateMeasurement, + handleRotationImpulse, + setIntervalParameters, + pause: pauseTraining, + stop: stopTraining, + resume: allowResumeTraining, reset: resetTraining }) } diff --git a/app/engine/Timer.js b/app/engine/Timer.js deleted file mode 100644 index 3f6cffcfe2..0000000000 --- a/app/engine/Timer.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - Stopwatch used to measure multiple time intervals -*/ -function createTimer () { - const timerMap = new Map() - - function start (key) { - timerMap.set(key, 0.0) - } - - function stop (key) { - timerMap.delete(key) - } - - function getValue (key) { - return timerMap.get(key) || 0.0 - } - - function updateTimers (currentDt) { - timerMap.forEach((value, key) => { - timerMap.set(key, value + currentDt) - }) - } - - return { - start, - stop, - getValue, - updateTimers - } -} - -export { createTimer } diff --git a/app/engine/VO2max.js b/app/engine/VO2max.js new file mode 100644 index 0000000000..ee196f2e01 --- /dev/null +++ b/app/engine/VO2max.js @@ -0,0 +1,160 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + This Module calculates the training specific VO2Max metrics. It is based on formula's found on the web (see function definitions). +*/ + +import { createBucketedLinearSeries } from './utils/BucketedLinearSeries.js' + +import loglevel from 'loglevel' +const log = loglevel.getLogger('RowingEngine') + +function createVO2max (config) { + const bucketedLinearSeries = createBucketedLinearSeries(config) + const minimumValidBrackets = 5.0 + const offset = 90 + + function calculateVO2max (metrics) { + let projectedVO2max = 0 + let interpolatedVO2max = 0 + + if (metrics[0].heartrate !== undefined && metrics[metrics.length - 1].heartrate !== undefined && metrics[metrics.length - 1].heartrate >= config.userSettings.restingHR) { + projectedVO2max = extrapolatedVO2max(metrics) + } + + interpolatedVO2max = calculateInterpolatedVO2max(metrics) + + if (projectedVO2max >= 10 && projectedVO2max <= 60 && interpolatedVO2max >= 10 && interpolatedVO2max <= 60) { + // Both VO2Max calculations have delivered a valid and credible result + log.debug(`--- VO2Max calculation delivered two credible results Extrapolated VO2Max: ${projectedVO2max.toFixed(1)} and Interpolated VO2Max: ${interpolatedVO2max.toFixed(1)}`) + return ((projectedVO2max + interpolatedVO2max) / 2) + } else { + // One of the calculations has delivered an invalid result + if (interpolatedVO2max >= 10 && interpolatedVO2max <= 60) { + // Interpolation has delivered a credible result + log.debug(`--- VO2Max calculation delivered one credible result, the Interpolated VO2Max: ${interpolatedVO2max.toFixed(1)}. The Extrapolated VO2Max: ${projectedVO2max.toFixed(1)} was unreliable`) + return interpolatedVO2max + } else { + // Interpolation hasn't delivered a credible result + if (projectedVO2max >= 10 && projectedVO2max <= 60) { + // Extrapolation did deliver a credible result + log.debug(`--- VO2Max calculation delivered one credible result, the Extrapolated VO2Max: ${projectedVO2max.toFixed(1)}. Interpolated VO2Max: ${interpolatedVO2max.toFixed(1)} was unreliable`) + return projectedVO2max + } else { + // No credible results at all! + log.debug(`--- VO2Max calculation did not deliver any credible results Extrapolated VO2Max: ${projectedVO2max.toFixed(1)}, Interpolated VO2Max: ${interpolatedVO2max.toFixed(1)}`) + return 0 + } + } + } + } + + function extrapolatedVO2max (metrics) { + // This implements the extrapolation-based VO2Max determination + // Which is based on the extrapolated maximum power output based on the correlation between heartrate and power, + // Underlying formula's can be found here: https://sportcoaching.co.nz/how-does-garmin-calculate-vo2-max/ + let ProjectedVO2max + let i = 0 + while (i < metrics.length && metrics[i].totalMovingTime < offset) { + // We skip the first timeperiod as it only depicts the change from a resting HR to a working HR + i++ + } + while (i < metrics.length) { + if (metrics[i].heartrate !== undefined && metrics[i].heartrate >= config.userSettings.restingHR && metrics[i].heartrate <= config.userSettings.maxHR && metrics[i].cyclePower !== undefined && metrics[i].cyclePower >= config.userSettings.minPower && metrics[i].cyclePower <= config.userSettings.maxPower) { + // The data looks credible, lets add it + bucketedLinearSeries.push(metrics[i].heartrate, metrics[i].cyclePower) + } + i++ + } + + // All Datapoints have been added, now we determine the projected power + if (bucketedLinearSeries.numberOfSamples() >= minimumValidBrackets) { + const projectedPower = bucketedLinearSeries.projectX(config.userSettings.maxHR) + if (projectedPower <= config.userSettings.maxPower && projectedPower >= bucketedLinearSeries.maxEncounteredY()) { + ProjectedVO2max = ((14.72 * projectedPower) + 250.39) / config.userSettings.weight + log.debug(`--- VO2Max Goodness of Fit: ${bucketedLinearSeries.goodnessOfFit().toFixed(6)}, projected power ${projectedPower.toFixed(1)} Watt, extrapolated VO2Max: ${ProjectedVO2max.toFixed(1)}`) + } else { + ProjectedVO2max = ((14.72 * bucketedLinearSeries.maxEncounteredY()) + 250.39) / config.userSettings.weight + log.debug(`--- VO2Max maximum encountered power: ${bucketedLinearSeries.maxEncounteredY().toFixed(1)} Watt, extrapolated VO2Max: ${ProjectedVO2max.toFixed(1)}`) + } + } else { + log.debug(`--- VO2Max extrapolation failed as there were not enough valid brackets: ${bucketedLinearSeries.numberOfSamples()}`) + ProjectedVO2max = 0 + } + return ProjectedVO2max + } + + function calculateInterpolatedVO2max (metrics) { + // This is based on research done by concept2, https://www.concept2.com/indoor-rowers/training/calculators/vo2max-calculator, + // which determines the VO2Max based on the 2K speed + const distance = metrics[metrics.length - 1].totalLinearDistance + const time = metrics[metrics.length - 1].totalMovingTime + const projectedTwoKPace = interpolatePace(time, distance, 2000) + const projectedTwoKTimeInMinutes = (4 * projectedTwoKPace) / 60 + let Y = 0 + + log.debug(`--- VO2Max Interpolated 2K pace: ${Math.floor(projectedTwoKPace / 60)}:${(projectedTwoKPace % 60).toFixed(1)}`) + // This implements the table with formulas found at https://www.concept2.com/indoor-rowers/training/calculators/vo2max-calculator + switch (true) { + case (config.userSettings.sex === 'male' && config.userSettings.highlyTrained && config.userSettings.weight > 75): + // Highly trained male, above 75 Kg + Y = 15.7 - (1.5 * projectedTwoKTimeInMinutes) + break + case (config.userSettings.sex === 'male' && config.userSettings.highlyTrained): + // Highly trained male, equal or below 75 Kg + Y = 15.1 - (1.5 * projectedTwoKTimeInMinutes) + break + case (config.userSettings.sex === 'male'): + // Not highly trained male + Y = 10.7 - (0.9 * projectedTwoKTimeInMinutes) + break + case (config.userSettings.sex === 'female' && config.userSettings.highlyTrained && config.userSettings.weight > 61.36): + // Highly trained female, above 61.36 Kg + Y = 14.9 - (1.5 * projectedTwoKTimeInMinutes) + break + case (config.userSettings.sex === 'female' && config.userSettings.highlyTrained): + // Highly trained female, equal or below 61.36 Kg + Y = 14.6 - (1.5 * projectedTwoKTimeInMinutes) + break + case (config.userSettings.sex === 'female'): + // Not highly trained female + Y = 10.26 - (0.93 * projectedTwoKTimeInMinutes) + break + } + return (Y * 1000) / config.userSettings.weight + } + + function interpolatePace (origintime, origindistance, targetdistance) { + // We interpolate the 2K speed based on Paul's Law: https://paulergs.weebly.com/blog/a-quick-explainer-on-pauls-law + let originpace = 0 + + if (origintime > 0 && origindistance > 0 && targetdistance > 0) { + originpace = (500 * origintime) / origindistance + return (originpace + (config.userSettings.distanceCorrectionFactor * Math.log2(targetdistance / origindistance))) + } else { + return 0 + } + } + + function averageObservedHR () { + bucketedLinearSeries.averageEncounteredX() + } + + function maxObservedHR () { + bucketedLinearSeries.maxEncounteredX() + } + + function reset () { + bucketedLinearSeries.reset() + } + + return { + calculateVO2max, + averageObservedHR, + maxObservedHR, + reset + } +} + +export { createVO2max } diff --git a/app/engine/WorkoutRecorder.js b/app/engine/WorkoutRecorder.js index 192b6efecd..4a68ab1d2c 100644 --- a/app/engine/WorkoutRecorder.js +++ b/app/engine/WorkoutRecorder.js @@ -11,12 +11,14 @@ import zlib from 'zlib' import fs from 'fs/promises' import xml2js from 'xml2js' import config from '../tools/ConfigManager.js' +import { createVO2max } from './VO2max.js' import { promisify } from 'util' const gzip = promisify(zlib.gzip) function createWorkoutRecorder () { let strokes = [] let rotationImpulses = [] + let postExerciseHR = [] let startTime function recordRotationImpulse (impulse) { @@ -53,6 +55,49 @@ function createWorkoutRecorder () { await createFile(rotationImpulses.join('\n'), filename, config.gzipRawDataFiles) } + async function createRowingDataFile () { + const stringifiedStartTime = startTime.toISOString().replace(/T/, '_').replace(/:/g, '-').replace(/\..+/, '') + const directory = `${config.dataDirectory}/recordings/${startTime.getFullYear()}/${(startTime.getMonth() + 1).toString().padStart(2, '0')}` + const filename = `${directory}/${stringifiedStartTime}_rowingData.csv` + let currentstroke + let trackPointTime + let timestamp + let i + + log.info(`saving session as RowingData file ${filename}...`) + + // Required file header, please note this includes a typo and odd spaces as the specification demands it! + let RowingData = ',index, Stroke Number, lapIdx,TimeStamp (sec), ElapsedTime (sec), HRCur (bpm),DistanceMeters, Cadence (stokes/min), Stroke500mPace (sec/500m), Power (watts), StrokeDistance (meters),' + + ' DriveTime (ms), DriveLength (meters), StrokeRecoveryTime (ms),Speed, Horizontal (meters), Calories (kCal), DragFactor, PeakDriveForce (N), AverageDriveForce (N),' + + 'Handle_Force_(N),Handle_Velocity_(m/s),Handle_Power_(W)\n' + + // Add the strokes + i = 0 + while (i < strokes.length) { + currentstroke = strokes[i] + trackPointTime = new Date(startTime.getTime() + currentstroke.totalMovingTime * 1000) + timestamp = trackPointTime.getTime() / 1000 + + RowingData += `${currentstroke.totalNumberOfStrokes.toFixed(0)},${currentstroke.totalNumberOfStrokes.toFixed(0)},${currentstroke.totalNumberOfStrokes.toFixed(0)},${currentstroke.intervalNumber.toFixed(0)},${timestamp.toFixed(5)},` + + `${currentstroke.totalMovingTime.toFixed(5)},${(currentstroke.heartrate > 30 ? currentstroke.heartrate.toFixed(0) : NaN)},${currentstroke.totalLinearDistance.toFixed(1)},` + + `${currentstroke.cycleStrokeRate.toFixed(1)},${(currentstroke.totalNumberOfStrokes > 0 ? currentstroke.cyclePace.toFixed(2) : NaN)},${(currentstroke.totalNumberOfStrokes > 0 ? currentstroke.cyclePower.toFixed(0) : NaN)},` + + `${currentstroke.cycleDistance.toFixed(2)},${(currentstroke.driveDuration * 1000).toFixed(0)},${(currentstroke.totalNumberOfStrokes > 0 ? currentstroke.driveLength.toFixed(2) : NaN)},${(currentstroke.recoveryDuration * 1000).toFixed(0)},` + + `${(currentstroke.totalNumberOfStrokes > 0 ? currentstroke.cycleLinearVelocity.toFixed(2) : 0)},${currentstroke.totalLinearDistance.toFixed(1)},${currentstroke.totalCalories.toFixed(1)},${currentstroke.dragFactor.toFixed(1)},` + + `${(currentstroke.totalNumberOfStrokes > 0 ? currentstroke.drivePeakHandleForce.toFixed(1) : NaN)},${(currentstroke.totalNumberOfStrokes > 0 ? currentstroke.driveAverageHandleForce.toFixed(1) : 0)},"${currentstroke.driveHandleForceCurve.map(value => value.toFixed(2))}",` + + `"${currentstroke.driveHandleVelocityCurve.map(value => value.toFixed(3))}","${currentstroke.driveHandlePowerCurve.map(value => value.toFixed(1))}"\n` + i++ + } + + try { + await fs.mkdir(directory, { recursive: true }) + } catch (error) { + if (error.code !== 'EEXIST') { + log.error(`can not create directory ${directory}`, error) + } + } + await createFile(RowingData, `${filename}`, false) + } + async function createTcxFile () { const tcxRecord = await activeWorkoutToTcx() if (tcxRecord === undefined) { @@ -76,7 +121,7 @@ function createWorkoutRecorder () { async function activeWorkoutToTcx () { // we need at least two strokes to generate a valid tcx file - if (strokes.length < 2) return + if (strokes.length < 5) return const stringifiedStartTime = startTime.toISOString().replace(/T/, '_').replace(/:/g, '-').replace(/\..+/, '') const filename = `${stringifiedStartTime}_rowing.tcx` @@ -94,8 +139,32 @@ function createWorkoutRecorder () { async function workoutToTcx (workout) { let versionArray = process.env.npm_package_version.split('.') - if (versionArray.length < 3) versionArray = [0, 0, 0] + if (versionArray.length < 3) versionArray = ['0', '0', '0'] const lastStroke = workout.strokes[strokes.length - 1] + const drag = workout.strokes.reduce((sum, s) => sum + s.dragFactor, 0) / strokes.length + + // VO2Max calculation for the remarks section + let VO2maxoutput = 'UNDEFINED' + const VO2max = createVO2max(config) + const VO2maxResult = VO2max.calculateVO2max(strokes) + if (VO2maxResult > 10 && VO2maxResult < 60) { + VO2maxoutput = `${VO2maxResult.toFixed(1)} mL/(kg*min)` + } + + // Addition of HRR data + let hrrAdittion = '' + if (postExerciseHR.length > 1 && (postExerciseHR[0] > (0.7 * config.userSettings.maxHR))) { + // Recovery Heartrate is only defined when the last excercise HR is above 70% of the maximum Heartrate + if (postExerciseHR.length === 2) { + hrrAdittion = `, HRR1: ${postExerciseHR[1] - postExerciseHR[0]} (${postExerciseHR[1]} BPM)` + } + if (postExerciseHR.length === 3) { + hrrAdittion = `, HRR1: ${postExerciseHR[1] - postExerciseHR[0]} (${postExerciseHR[1]} BPM), HRR2: ${postExerciseHR[2] - postExerciseHR[0]} (${postExerciseHR[2]} BPM)` + } + if (postExerciseHR.length >= 4) { + hrrAdittion = `, HRR1: ${postExerciseHR[1] - postExerciseHR[0]} (${postExerciseHR[1]} BPM), HRR2: ${postExerciseHR[2] - postExerciseHR[0]} (${postExerciseHR[2]} BPM), HRR3: ${postExerciseHR[3] - postExerciseHR[0]} (${postExerciseHR[3]} BPM)` + } + } const tcxObject = { TrainingCenterDatabase: { @@ -107,38 +176,37 @@ function createWorkoutRecorder () { Lap: [ { $: { StartTime: workout.startTime.toISOString() }, - TotalTimeSeconds: workout.strokes.reduce((acc, stroke) => acc + stroke.strokeTime, 0).toFixed(1), - DistanceMeters: lastStroke.distanceTotal.toFixed(1), - // tcx uses meters per second as unit for speed - MaximumSpeed: (workout.strokes.map((stroke) => stroke.speed).reduce((acc, speed) => Math.max(acc, speed)) / 3.6).toFixed(2), - Calories: Math.round(lastStroke.caloriesTotal), - /* todo: calculate heart rate metrics... - AverageHeartRateBpm: { Value: 76 }, - MaximumHeartRateBpm: { Value: 76 }, + TotalTimeSeconds: lastStroke.totalMovingTime.toFixed(1), + DistanceMeters: lastStroke.totalLinearDistance.toFixed(1), + MaximumSpeed: (workout.strokes.map((stroke) => stroke.cycleLinearVelocity).reduce((acc, cycleLinearVelocity) => Math.max(acc, cycleLinearVelocity))).toFixed(2), + Calories: Math.round(lastStroke.totalCalories), + /* ToDo Fix issue with IF-statement not being accepted here? + if (lastStroke.heartrate !== undefined && lastStroke.heartrate > 30) { + AverageHeartRateBpm: VO2max.averageObservedHR(), + MaximumHeartRateBpm: VO2max.maxObservedHR, + //AverageHeartRateBpm: { Value: (workout.strokes.reduce((sum, s) => sum + s.heartrate, 0) / workout.strokes.length).toFixed(2) }, + //MaximumHeartRateBpm: { Value: Math.round(workout.strokes.map((stroke) => stroke.power).reduce((acc, heartrate) => Math.max(acc, heartrate))) }, + } */ Intensity: 'Active', - // todo: calculate average SPM - // Cadence: 20, + Cadence: Math.round(workout.strokes.reduce((sum, s) => sum + s.cycleStrokeRate, 0) / (workout.strokes.length - 1)), TriggerMethod: 'Manual', Track: { Trackpoint: (() => { - let trackPointTime = workout.startTime - return workout.strokes.map((stroke) => { - trackPointTime = new Date(trackPointTime.getTime() + stroke.strokeTime * 1000) + const trackPointTime = new Date(workout.startTime.getTime() + stroke.totalMovingTime * 1000) const trackpoint = { Time: trackPointTime.toISOString(), - DistanceMeters: stroke.distanceTotal.toFixed(2), - Cadence: Math.round(stroke.strokesPerMinute), + DistanceMeters: stroke.totalLinearDistance.toFixed(2), + Cadence: Math.round(stroke.cycleStrokeRate), Extensions: { 'ns2:TPX': { - // tcx uses meters per second as unit for speed - 'ns2:Speed': (stroke.speed / 3.6).toFixed(2), - 'ns2:Watts': Math.round(stroke.power) + 'ns2:Speed': stroke.cycleLinearVelocity.toFixed(2), + 'ns2:Watts': Math.round(stroke.cyclePower) } } } - if (stroke.heartrate !== undefined) { + if (stroke.heartrate !== undefined && stroke.heartrate > 30) { trackpoint.HeartRateBpm = { Value: stroke.heartrate } } return trackpoint @@ -147,16 +215,16 @@ function createWorkoutRecorder () { }, Extensions: { 'ns2:LX': { - /* todo: calculate these metrics... - 'ns2:AvgSpeed': 12, - 'ns2:AvgWatts': 133, - */ - 'ns2:MaxWatts': Math.round(workout.strokes.map((stroke) => stroke.power).reduce((acc, power) => Math.max(acc, power))) + 'ns2:Steps': lastStroke.totalNumberOfStrokes.toFixed(0), + // please note, the -1 is needed as we have a stroke 0, with a speed and power of 0. The - 1 corrects this. + 'ns2:AvgSpeed': (workout.strokes.reduce((sum, s) => sum + s.cycleLinearVelocity, 0) / (workout.strokes.length - 1)).toFixed(2), + 'ns2:AvgWatts': (workout.strokes.reduce((sum, s) => sum + s.cyclePower, 0) / (workout.strokes.length - 1)).toFixed(0), + 'ns2:MaxWatts': Math.round(workout.strokes.map((stroke) => stroke.cyclePower).reduce((acc, cyclePower) => Math.max(acc, cyclePower))) } } } ], - Notes: 'Rowing Session' + Notes: `Indoor Rowing, Drag factor: ${drag.toFixed(1)} 10-6 N*m*s2, Estimated VO2Max: ${VO2maxoutput}${hrrAdittion}` } }, Author: { @@ -184,15 +252,24 @@ function createWorkoutRecorder () { await createRecordings() strokes = [] rotationImpulses = [] + postExerciseHR = [] startTime = undefined } async function createFile (content, filename, compress = false) { if (compress) { const gzipContent = await gzip(content) - await fs.writeFile(filename, gzipContent, (err) => { if (err) log.error(err) }) + try { + await fs.writeFile(filename, gzipContent) + } catch (err) { + log.error(err) + } } else { - await fs.writeFile(filename, content, (err) => { if (err) log.error(err) }) + try { + await fs.writeFile(filename, content) + } catch (err) { + log.error(err) + } } } @@ -201,7 +278,7 @@ function createWorkoutRecorder () { } async function createRecordings () { - if (!config.createRawDataFiles && !config.createTcxFiles) { + if (!config.createRawDataFiles && !config.createTcxFiles && !config.createRowingDataFiles) { return } @@ -210,6 +287,8 @@ function createWorkoutRecorder () { return } + postExerciseHR = [] + const parallelCalls = [] if (config.createRawDataFiles) { @@ -218,14 +297,26 @@ function createWorkoutRecorder () { if (config.createTcxFiles) { parallelCalls.push(createTcxFile()) } + if (config.createRowingDataFiles) { + parallelCalls.push(createRowingDataFile()) + } await Promise.all(parallelCalls) } + async function updateHRRecovery (hrmetrics) { + postExerciseHR = hrmetrics + createTcxFile() + } + function minimumRecordingTimeHasPassed () { const minimumRecordingTimeInSeconds = 10 const rotationImpulseTimeTotal = rotationImpulses.reduce((acc, impulse) => acc + impulse, 0) - const strokeTimeTotal = strokes.reduce((acc, stroke) => acc + stroke.strokeTime, 0) - return (Math.max(rotationImpulseTimeTotal, strokeTimeTotal) > minimumRecordingTimeInSeconds) + if (strokes.length > 0) { + const strokeTimeTotal = strokes[strokes.length - 1].totalMovingTime + return (Math.max(rotationImpulseTimeTotal, strokeTimeTotal) > minimumRecordingTimeInSeconds) + } else { + return (rotationImpulseTimeTotal > minimumRecordingTimeInSeconds) + } } return { @@ -233,6 +324,8 @@ function createWorkoutRecorder () { recordRotationImpulse, handlePause, activeWorkoutToTcx, + writeRecordings: createRecordings, + updateHRRecovery, reset } } diff --git a/app/engine/averager/MovingAverager.js b/app/engine/averager/MovingAverager.js deleted file mode 100644 index cbdcf3beb9..0000000000 --- a/app/engine/averager/MovingAverager.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - This Averager can calculate the moving average of a continuous flow of data points - - Please note: The array contains flankLength + 1 measured currentDt's, thus flankLength number - of flanks between them. - They are arranged that dataPoints[0] is the youngest, and dataPoints[flankLength] the oldest -*/ -function createMovingAverager (length, initValue) { - let dataPoints - reset() - - function pushValue (dataPoint) { - // add the new dataPoint to the array, we have to move data points starting at the oldest ones - let i = length - 1 - while (i > 0) { - // older data points are moved towards the higher numbers - dataPoints[i] = dataPoints[i - 1] - i = i - 1 - } - dataPoints[0] = dataPoint - } - - function replaceLastPushedValue (dataPoint) { - // replace the newest dataPoint in the array, as it was faulty - dataPoints[0] = dataPoint - } - - function getAverage () { - let i = length - 1 - let arrayTotal = 0.0 - while (i >= 0) { - // summarize the value of the moving average - arrayTotal = arrayTotal + dataPoints[i] - i = i - 1 - } - const arrayAverage = arrayTotal / length - return arrayAverage - } - - function reset () { - dataPoints = new Array(length) - dataPoints.fill(initValue) - } - - return { - pushValue, - replaceLastPushedValue, - getAverage, - reset - } -} - -export { createMovingAverager } diff --git a/app/engine/averager/MovingAverager.test.js b/app/engine/averager/MovingAverager.test.js deleted file mode 100644 index 748d720a9c..0000000000 --- a/app/engine/averager/MovingAverager.test.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor -*/ -import { test } from 'uvu' -import * as assert from 'uvu/assert' - -import { createMovingAverager } from './MovingAverager.js' - -test('average should be initValue on empty dataset', () => { - const movingAverager = createMovingAverager(10, 5.5) - assert.is(movingAverager.getAverage(), 5.5) -}) - -test('an averager of length 1 should return the last added value', () => { - const movingAverager = createMovingAverager(1, 3) - movingAverager.pushValue(9) - assert.is(movingAverager.getAverage(), 9) -}) - -test('an averager of length 2 should return average of last 2 added elements', () => { - const movingAverager = createMovingAverager(2, 3) - movingAverager.pushValue(9) - movingAverager.pushValue(4) - assert.is(movingAverager.getAverage(), 6.5) -}) - -test('elements outside of range should not be considered', () => { - const movingAverager = createMovingAverager(2, 3) - movingAverager.pushValue(9) - movingAverager.pushValue(4) - movingAverager.pushValue(3) - assert.is(movingAverager.getAverage(), 3.5) -}) - -test('replacing the last element should work as expected', () => { - const movingAverager = createMovingAverager(2, 3) - movingAverager.pushValue(9) - movingAverager.pushValue(5) - movingAverager.replaceLastPushedValue(12) - assert.is(movingAverager.getAverage(), 10.5) -}) - -test.run() diff --git a/app/engine/averager/MovingIntervalAverager.js b/app/engine/averager/MovingIntervalAverager.js deleted file mode 100644 index c224ed5175..0000000000 --- a/app/engine/averager/MovingIntervalAverager.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - This Averager calculates the average forecast for a moving interval of a continuous flow - of data points for a certain (time) interval -*/ -function createMovingIntervalAverager (movingDuration) { - let dataPoints - let duration - let sum - reset() - - function pushValue (dataValue, dataDuration) { - // add the new data point to the front of the array - dataPoints.unshift({ value: dataValue, duration: dataDuration }) - duration += dataDuration - sum += dataValue - while (duration > movingDuration) { - const removedDataPoint = dataPoints.pop() - duration -= removedDataPoint.duration - sum -= removedDataPoint.value - } - } - - function getAverage () { - if (duration > 0) { - return sum / duration * movingDuration - } else { - return 0 - } - } - - function reset () { - dataPoints = [] - duration = 0.0 - sum = 0.0 - } - - return { - pushValue, - getAverage, - reset - } -} - -export { createMovingIntervalAverager } diff --git a/app/engine/averager/MovingIntervalAverager.test.js b/app/engine/averager/MovingIntervalAverager.test.js deleted file mode 100644 index 35b7013f90..0000000000 --- a/app/engine/averager/MovingIntervalAverager.test.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor -*/ -import { test } from 'uvu' -import * as assert from 'uvu/assert' - -import { createMovingIntervalAverager } from './MovingIntervalAverager.js' - -test('average of a data point with duration of averager is equal to datapoint', () => { - const movingAverager = createMovingIntervalAverager(10) - movingAverager.pushValue(5, 10) - assert.is(movingAverager.getAverage(), 5) -}) - -test('average of a data point with half duration of averager is double to datapoint', () => { - const movingAverager = createMovingIntervalAverager(20) - movingAverager.pushValue(5, 10) - assert.is(movingAverager.getAverage(), 10) -}) - -test('average of two identical data points with half duration of averager is equal to datapoint sum', () => { - const movingAverager = createMovingIntervalAverager(20) - movingAverager.pushValue(5, 10) - movingAverager.pushValue(5, 10) - assert.is(movingAverager.getAverage(), 10) -}) - -test('average does not consider data points that are outside of duration', () => { - const movingAverager = createMovingIntervalAverager(20) - movingAverager.pushValue(10, 10) - movingAverager.pushValue(5, 10) - movingAverager.pushValue(5, 10) - assert.is(movingAverager.getAverage(), 10) -}) - -test('average works with lots of values', () => { - // one hour - const movingAverager = createMovingIntervalAverager(3000) - for (let i = 0; i < 1000; i++) { - movingAverager.pushValue(10, 1) - } - for (let i = 0; i < 1000; i++) { - movingAverager.pushValue(20, 1) - } - for (let i = 0; i < 1000; i++) { - movingAverager.pushValue(30, 2) - } - assert.is(movingAverager.getAverage(), 50000) -}) - -test('average should return 0 on empty dataset', () => { - const movingAverager = createMovingIntervalAverager(10) - assert.is(movingAverager.getAverage(), 0) -}) - -test.run() diff --git a/app/engine/averager/WeightedAverager.js b/app/engine/averager/WeightedAverager.js deleted file mode 100644 index 4b266b96b2..0000000000 --- a/app/engine/averager/WeightedAverager.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - This Averager can calculate the weighted average of a continuous flow of data points -*/ -function createWeightedAverager (maxNumOfDataPoints) { - let dataPoints = [] - - function pushValue (dataPoint) { - // add the new data point to the front of the array - dataPoints.unshift(dataPoint) - // ensure that the array does not get longer than maxNumOfDataPoints - if (dataPoints.length > maxNumOfDataPoints) { - dataPoints.pop() - } - } - - function getAverage () { - const numOfDataPoints = dataPoints.length - if (numOfDataPoints > 0) { - const sum = dataPoints - .map((dataPoint, index) => Math.pow(2, numOfDataPoints - index - 1) * dataPoint) - .reduce((acc, dataPoint) => acc + dataPoint, 0) - const weight = Math.pow(2, numOfDataPoints) - 1 - return sum / weight - } else { - return 0 - } - } - - function reset () { - dataPoints = [] - } - - return { - pushValue, - getAverage, - reset - } -} - -export { createWeightedAverager } diff --git a/app/engine/averager/WeightedAverager.test.js b/app/engine/averager/WeightedAverager.test.js deleted file mode 100644 index 28e1583e9b..0000000000 --- a/app/engine/averager/WeightedAverager.test.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor -*/ -import { test } from 'uvu' -import * as assert from 'uvu/assert' - -import { createWeightedAverager } from './WeightedAverager.js' - -test('average should be 0 on empty dataset', () => { - const weightedAverager = createWeightedAverager(10) - assert.is(weightedAverager.getAverage(), 0) -}) - -test('average of one value is value', () => { - const weightedAverager = createWeightedAverager(10) - weightedAverager.pushValue(13.78) - assert.is(weightedAverager.getAverage(), 13.78) -}) - -test('average of a and b is (2*b + a) / 3', () => { - const weightedAverager = createWeightedAverager(10) - weightedAverager.pushValue(5) // a - weightedAverager.pushValue(2) // b - assert.is(weightedAverager.getAverage(), 3) -}) - -test('average should be 0 after reset', () => { - const weightedAverager = createWeightedAverager(10) - weightedAverager.pushValue(5) - weightedAverager.pushValue(2) - weightedAverager.reset() - assert.is(weightedAverager.getAverage(), 0) -}) - -test('average should be a after pushing a after a reset', () => { - const weightedAverager = createWeightedAverager(10) - weightedAverager.pushValue(5) - weightedAverager.pushValue(2) - weightedAverager.reset() - weightedAverager.pushValue(7) - assert.is(weightedAverager.getAverage(), 7) -}) - -test.run() diff --git a/app/engine/utils/BinarySearchTree.js b/app/engine/utils/BinarySearchTree.js new file mode 100644 index 0000000000..7a181df3bd --- /dev/null +++ b/app/engine/utils/BinarySearchTree.js @@ -0,0 +1,303 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/jaapvanekris/openrowingmonitor + + This creates an ordered series with labels + It allows for efficient determining the Median, Number of Above and Below +*/ + +function createLabelledBinarySearchTree () { + let tree = null + + function push (label, value) { + if (tree === null) { + tree = newNode(label, value) + } else { + // pushInTree(tree, label, value) + tree = pushInTree(tree, label, value) + } + } + + function pushInTree (currentTree, label, value) { + if (value <= currentTree.value) { + // The value should be on the left side of currentTree + if (currentTree.leftNode === null) { + currentTree.leftNode = newNode(label, value) + } else { + currentTree.leftNode = pushInTree(currentTree.leftNode, label, value) + } + } else { + // The value should be on the right side of currentTree + if (currentTree.rightNode === null) { + currentTree.rightNode = newNode(label, value) + } else { + currentTree.rightNode = pushInTree(currentTree.rightNode, label, value) + } + } + currentTree.numberOfLeafsAndNodes = currentTree.numberOfLeafsAndNodes + 1 + return currentTree + } + + function newNode (label, value) { + return { + label, + value, + leftNode: null, + rightNode: null, + numberOfLeafsAndNodes: 1 + } + } + + function size () { + if (tree !== null) { + return tree.numberOfLeafsAndNodes + } else { + return 0 + } + } + + function numberOfValuesAbove (testedValue) { + return countNumberOfValuesAboveInTree(tree, testedValue) + } + + function countNumberOfValuesAboveInTree (currentTree, testedValue) { + if (currentTree === null) { + return 0 + } else { + // We encounter a filled node + if (currentTree.value > testedValue) { + // testedValue < currentTree.value, so we can find the tested value in the left and right branch + return (countNumberOfValuesAboveInTree(currentTree.leftNode, testedValue) + countNumberOfValuesAboveInTree(currentTree.rightNode, testedValue) + 1) + } else { + // currentTree.value < testedValue, so we need to find values from the right branch + return countNumberOfValuesAboveInTree(currentTree.rightNode, testedValue) + } + } + } + + function numberOfValuesEqualOrBelow (testedValue) { + return countNumberOfValuesEqualOrBelowInTree(tree, testedValue) + } + + function countNumberOfValuesEqualOrBelowInTree (currentTree, testedValue) { + if (currentTree === null) { + return 0 + } else { + // We encounter a filled node + if (currentTree.value <= testedValue) { + // testedValue <= currentTree.value, so we can only find the tested value in the left branch + return (countNumberOfValuesEqualOrBelowInTree(currentTree.leftNode, testedValue) + countNumberOfValuesEqualOrBelowInTree(currentTree.rightNode, testedValue) + 1) + } else { + // currentTree.value > testedValue, so we only need to look at the left branch + return countNumberOfValuesEqualOrBelowInTree(currentTree.leftNode, testedValue) + } + } + } + + function remove (label) { + if (tree !== null) { + tree = removeFromTree(tree, label) + } + } + + function removeFromTree (currentTree, label) { + // Clean up the underlying sub-trees first + if (currentTree.leftNode !== null) { + currentTree.leftNode = removeFromTree(currentTree.leftNode, label) + } + if (currentTree.rightNode !== null) { + currentTree.rightNode = removeFromTree(currentTree.rightNode, label) + } + + // Next, handle the situation when we need to remove the node itself + if (currentTree.label === label) { + // We need to remove the current node, the underlying sub-trees determin how it is resolved + switch (true) { + case (currentTree.leftNode === null && currentTree.rightNode === null): + // As the underlying sub-trees are empty as well, we return an empty tree + currentTree = null + break + case (currentTree.leftNode !== null && currentTree.rightNode === null): + // As only the left node contains data, we can simply replace the removed node with the left sub-tree + currentTree = currentTree.leftNode + break + case (currentTree.leftNode === null && currentTree.rightNode !== null): + // As only the right node contains data, we can simply replace the removed node with the right sub-tree + currentTree = currentTree.rightNode + break + case (currentTree.leftNode !== null && currentTree.rightNode !== null): + // As all underlying sub-trees are filled, we need to move a leaf to the now empty node. Here, we can be a bit smarter + // as there are two potential nodes to use, we try to balance the tree a bit more as this increases performance + if (currentTree.leftNode.numberOfLeafsAndNodes > currentTree.rightNode.numberOfLeafsAndNodes) { + // The left sub-tree is bigger then the right one, lets use the closest predecessor to restore some balance + currentTree.value = clostestPredecessor(currentTree.leftNode).value + currentTree.label = clostestPredecessor(currentTree.leftNode).label + currentTree.leftNode = destroyClostestPredecessor(currentTree.leftNode) + } else { + // The right sub-tree is smaller then the right one, lets use the closest successor to restore some balance + currentTree.value = clostestSuccesor(currentTree.rightNode).value + currentTree.label = clostestSuccesor(currentTree.rightNode).label + currentTree.rightNode = destroyClostestSuccessor(currentTree.rightNode) + } + break + } + } + + // Recalculate the tree size + switch (true) { + case (currentTree === null): + // We are now an empty leaf, nothing to do here + break + case (currentTree.leftNode === null && currentTree.rightNode === null): + // This is a filled leaf + currentTree.numberOfLeafsAndNodes = 1 + break + case (currentTree.leftNode !== null && currentTree.rightNode === null): + currentTree.numberOfLeafsAndNodes = currentTree.leftNode.numberOfLeafsAndNodes + 1 + break + case (currentTree.leftNode === null && currentTree.rightNode !== null): + currentTree.numberOfLeafsAndNodes = currentTree.rightNode.numberOfLeafsAndNodes + 1 + break + case (currentTree.leftNode !== null && currentTree.rightNode !== null): + currentTree.numberOfLeafsAndNodes = currentTree.leftNode.numberOfLeafsAndNodes + currentTree.rightNode.numberOfLeafsAndNodes + 1 + break + } + return currentTree + } + + function clostestPredecessor (currentTree) { + // This function finds the maximum value in a tree + if (currentTree.rightNode !== null) { + // We haven't reached the end of the tree yet + return clostestPredecessor(currentTree.rightNode) + } else { + // We reached the largest value in the tree + return { + label: currentTree.label, + value: currentTree.value + } + } + } + + function destroyClostestPredecessor (currentTree) { + // This function finds the maximum value in a tree + if (currentTree.rightNode !== null) { + // We haven't reached the end of the tree yet + currentTree.rightNode = destroyClostestPredecessor(currentTree.rightNode) + currentTree.numberOfLeafsAndNodes = currentTree.numberOfLeafsAndNodes - 1 + return currentTree + } else { + // We reached the largest value in the tree + return currentTree.leftNode + } + } + + function clostestSuccesor (currentTree) { + // This function finds the maximum value in a tree + if (currentTree.leftNode !== null) { + // We haven't reached the end of the tree yet + return clostestSuccesor(currentTree.leftNode) + } else { + // We reached the smallest value in the tree + return { + label: currentTree.label, + value: currentTree.value + } + } + } + + function destroyClostestSuccessor (currentTree) { + // This function finds the maximum value in a tree + if (currentTree.leftNode !== null) { + // We haven't reached the end of the tree yet + currentTree.leftNode = destroyClostestSuccessor(currentTree.leftNode) + currentTree.numberOfLeafsAndNodes = currentTree.numberOfLeafsAndNodes - 1 + return currentTree + } else { + // We reached the smallest value in the tree + return currentTree.rightNode + } + } + + function median () { + if (tree !== null && tree.numberOfLeafsAndNodes > 0) { + // BE AWARE, UNLIKE WITH ARRAYS, THE COUNTING OF THE ELEMENTS STARTS WITH 1 !!!!!!! + // THIS LOGIC THUS WORKS DIFFERENT THAN MOST ARRAYS FOUND IN ORM!!!!!!! + const mid = Math.floor(tree.numberOfLeafsAndNodes / 2) + return tree.numberOfLeafsAndNodes % 2 !== 0 ? valueAtInorderPosition(tree, mid + 1) : (valueAtInorderPosition(tree, mid) + valueAtInorderPosition(tree, mid + 1)) / 2 + } else { + return 0 + } + } + + function valueAtInorderPos (position) { // BE AWARE TESTING PURPOSSES ONLY + if (tree !== null && position >= 1) { + return valueAtInorderPosition(tree, position) + } else { + return undefined + } + } + + function valueAtInorderPosition (currentTree, position) { + let currentNodePosition + if (currentTree === null) { + // We are now an empty tree, this shouldn't happen + return undefined + } + + // First we need to find out what the InOrder Postion we currently are at + if (currentTree.leftNode !== null) { + currentNodePosition = currentTree.leftNode.numberOfLeafsAndNodes + 1 + } else { + currentNodePosition = 1 + } + + switch (true) { + case (position === currentNodePosition): + // The current position is the one we are looking for + return currentTree.value + case (currentTree.leftNode === null): + // The current node's left side is empty, but position <> currentNodePosition, so we have no choice but to move downwards + return valueAtInorderPosition(currentTree.rightNode, (position - 1)) + case (currentTree.leftNode !== null && currentNodePosition > position): + // The position we look for is in the left side of the currentTree + return valueAtInorderPosition(currentTree.leftNode, position) + case (currentTree.leftNode !== null && currentNodePosition < position && currentTree.rightNode !== null): + // The position we look for is in the right side of the currentTree + return valueAtInorderPosition(currentTree.rightNode, (position - currentNodePosition)) + default: + return undefined + } + } + + function orderedSeries () { + return orderedTree(tree) + } + + function orderedTree (currentTree) { + if (currentTree === null) { + return [] + } else { + // We encounter a filled node + return [...orderedTree(currentTree.leftNode), currentTree.value, ...orderedTree(currentTree.rightNode)] + } + } + + function reset () { + tree = null + } + + return { + push, + remove, + size, + numberOfValuesAbove, + numberOfValuesEqualOrBelow, + median, + valueAtInorderPos, // BE AWARE TESTING PURPOSSES ONLY + orderedSeries, + reset + } +} + +export { createLabelledBinarySearchTree } diff --git a/app/engine/utils/BinarySearchTree.test.js b/app/engine/utils/BinarySearchTree.test.js new file mode 100644 index 0000000000..c56feb5f4e --- /dev/null +++ b/app/engine/utils/BinarySearchTree.test.js @@ -0,0 +1,207 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + As this object is fundamental for most other utility objects, we must test its behaviour quite thoroughly +*/ +import { test } from 'uvu' +import * as assert from 'uvu/assert' + +import { createLabelledBinarySearchTree } from './BinarySearchTree.js' + +test('Series behaviour with an empty tree', () => { + const dataTree = createLabelledBinarySearchTree() + testSize(dataTree, 0) + testNumberOfValuesAbove(dataTree, 0, 0) + testNumberOfValuesEqualOrBelow(dataTree, 0, 0) + testNumberOfValuesAbove(dataTree, 10, 0) + testNumberOfValuesEqualOrBelow(dataTree, 10, 0) + testMedian(dataTree, 0) +}) + +test('Tree behaviour with a single pushed value. Tree = [9]', () => { + const dataTree = createLabelledBinarySearchTree() + dataTree.push(1, 9) + testOrderedSeries(dataTree, [9]) + testSize(dataTree, 1) + testValueAtInorderPos(dataTree, 1, 9) + testNumberOfValuesAbove(dataTree, 0, 1) + testNumberOfValuesEqualOrBelow(dataTree, 0, 0) + testNumberOfValuesAbove(dataTree, 10, 0) + testNumberOfValuesEqualOrBelow(dataTree, 10, 1) + testMedian(dataTree, 9) +}) + +test('Tree behaviour with a second pushed value. Tree = [9, 3]', () => { + const dataTree = createLabelledBinarySearchTree() + dataTree.push(1, 9) + dataTree.push(2, 3) + testOrderedSeries(dataTree, [3, 9]) + testSize(dataTree, 2) + testValueAtInorderPos(dataTree, 1, 3) + testValueAtInorderPos(dataTree, 2, 9) + testNumberOfValuesAbove(dataTree, 0, 2) + testNumberOfValuesEqualOrBelow(dataTree, 0, 0) + testNumberOfValuesAbove(dataTree, 10, 0) + testNumberOfValuesEqualOrBelow(dataTree, 10, 2) + testMedian(dataTree, 6) +}) + +test('Tree behaviour with a third pushed value. Tree = [9, 3, 6]', () => { + const dataTree = createLabelledBinarySearchTree() + dataTree.push(1, 9) + dataTree.push(2, 3) + dataTree.push(3, 6) + testOrderedSeries(dataTree, [3, 6, 9]) + testSize(dataTree, 3) + testValueAtInorderPos(dataTree, 1, 3) + testValueAtInorderPos(dataTree, 2, 6) + testValueAtInorderPos(dataTree, 3, 9) + testNumberOfValuesAbove(dataTree, 0, 3) + testNumberOfValuesEqualOrBelow(dataTree, 0, 0) + testNumberOfValuesAbove(dataTree, 10, 0) + testNumberOfValuesEqualOrBelow(dataTree, 10, 3) + testMedian(dataTree, 6) +}) + +test('Tree behaviour with a fourth pushed value. Tree = [3, 6, 12]', () => { + const dataTree = createLabelledBinarySearchTree() + dataTree.push(1, 9) + dataTree.push(2, 3) + dataTree.push(3, 6) + dataTree.remove(1) + dataTree.push(4, 12) + testOrderedSeries(dataTree, [3, 6, 12]) + testSize(dataTree, 3) + testValueAtInorderPos(dataTree, 1, 3) + testValueAtInorderPos(dataTree, 2, 6) + testValueAtInorderPos(dataTree, 3, 12) + testNumberOfValuesAbove(dataTree, 0, 3) + testNumberOfValuesEqualOrBelow(dataTree, 0, 0) + testNumberOfValuesAbove(dataTree, 10, 1) + testNumberOfValuesEqualOrBelow(dataTree, 10, 2) + testMedian(dataTree, 6) +}) + +test('Tree behaviour with a fifth pushed value. Series = [6, 12, -3]', () => { + const dataTree = createLabelledBinarySearchTree() + dataTree.push(1, 9) + dataTree.push(2, 3) + dataTree.push(3, 6) + dataTree.remove(1) + dataTree.push(4, 12) + dataTree.remove(2) + dataTree.push(5, -3) + testOrderedSeries(dataTree, [-3, 6, 12]) + testSize(dataTree, 3) + testValueAtInorderPos(dataTree, 1, -3) + testValueAtInorderPos(dataTree, 2, 6) + testValueAtInorderPos(dataTree, 3, 12) + testNumberOfValuesAbove(dataTree, 0, 2) + testNumberOfValuesEqualOrBelow(dataTree, 0, 1) + testNumberOfValuesAbove(dataTree, 10, 1) + testNumberOfValuesEqualOrBelow(dataTree, 10, 2) + testMedian(dataTree, 6) +}) + +test('Tree behaviour with complex removals. Series = [9, 6, 5, 8, 7, 9, 12, 10, 11]', () => { + const dataTree = createLabelledBinarySearchTree() + dataTree.push(1, 9) + dataTree.push(2, 6) + dataTree.push(3, 5) + dataTree.push(4, 8) + dataTree.push(5, 7) + dataTree.push(6, 9) + dataTree.push(7, 12) + dataTree.push(8, 10) + dataTree.push(9, 11) + testOrderedSeries(dataTree, [5, 6, 7, 8, 9, 9, 10, 11, 12]) + testSize(dataTree, 9) + testValueAtInorderPos(dataTree, 5, 9) + testMedian(dataTree, 9) + dataTree.remove(1) + testOrderedSeries(dataTree, [5, 6, 7, 8, 9, 10, 11, 12]) + testSize(dataTree, 8) + testValueAtInorderPos(dataTree, 4, 8) + testValueAtInorderPos(dataTree, 5, 9) + testMedian(dataTree, 8.5) + dataTree.remove(3) + testOrderedSeries(dataTree, [6, 7, 8, 9, 10, 11, 12]) + testSize(dataTree, 7) + testValueAtInorderPos(dataTree, 4, 9) + testMedian(dataTree, 9) +}) + +// Test based on https://levelup.gitconnected.com/deletion-in-binary-search-tree-with-javascript-fded82e1791c +test('Tree behaviour with complex removals. Series = [50, 30, 70, 20, 40, 60, 80]', () => { + const dataTree = createLabelledBinarySearchTree() + dataTree.push(1, 50) + dataTree.push(2, 30) + dataTree.push(3, 70) + dataTree.push(4, 20) + dataTree.push(5, 40) + dataTree.push(6, 60) + dataTree.push(7, 80) + testOrderedSeries(dataTree, [20, 30, 40, 50, 60, 70, 80]) + testSize(dataTree, 7) + testValueAtInorderPos(dataTree, 4, 50) + dataTree.remove(4) + testOrderedSeries(dataTree, [30, 40, 50, 60, 70, 80]) + testSize(dataTree, 6) + testValueAtInorderPos(dataTree, 3, 50) + testValueAtInorderPos(dataTree, 4, 60) + testMedian(dataTree, 55) + dataTree.remove(2) + testOrderedSeries(dataTree, [40, 50, 60, 70, 80]) + testSize(dataTree, 5) + testValueAtInorderPos(dataTree, 3, 60) + testMedian(dataTree, 60) + dataTree.remove(1) + testOrderedSeries(dataTree, [40, 60, 70, 80]) + testSize(dataTree, 4) + testValueAtInorderPos(dataTree, 2, 60) + testValueAtInorderPos(dataTree, 3, 70) + testMedian(dataTree, 65) +}) + +test('Tree behaviour with a five pushed values followed by a reset, Tree = []', () => { + const dataTree = createLabelledBinarySearchTree() + dataTree.push(1, 9) + dataTree.push(2, 3) + dataTree.push(3, 6) + dataTree.push(4, 12) + dataTree.push(5, -3) + dataTree.reset() + testSize(dataTree, 0) + testNumberOfValuesAbove(dataTree, 0, 0) + testNumberOfValuesEqualOrBelow(dataTree, 0, 0) + testNumberOfValuesAbove(dataTree, 10, 0) + testNumberOfValuesEqualOrBelow(dataTree, 10, 0) + testMedian(dataTree, 0) +}) + +function testSize (tree, expectedValue) { + assert.ok(tree.size() === expectedValue, `Expected size should be ${expectedValue}, encountered ${tree.size()}`) +} + +function testNumberOfValuesAbove (tree, cutoff, expectedValue) { + assert.ok(tree.numberOfValuesAbove(cutoff) === expectedValue, `Expected numberOfValuesAbove(${cutoff}) to be ${expectedValue}, encountered ${tree.numberOfValuesAbove(cutoff)}`) +} + +function testNumberOfValuesEqualOrBelow (tree, cutoff, expectedValue) { + assert.ok(tree.numberOfValuesEqualOrBelow(cutoff) === expectedValue, `Expected numberOfValuesEqualOrBelow(${cutoff}) to be ${expectedValue}, encountered ${tree.numberOfValuesEqualOrBelow(cutoff)}`) +} + +function testOrderedSeries (tree, expectedValue) { + assert.ok(tree.orderedSeries().toString() === expectedValue.toString(), `Expected ordered series to be ${expectedValue}, encountered ${tree.orderedSeries()}`) +} + +function testValueAtInorderPos (tree, position, expectedValue) { + assert.ok(tree.valueAtInorderPos(position) === expectedValue, `Expected valueAtInorderPos(${position}) to be ${expectedValue}, encountered ${tree.valueAtInorderPos(position)}`) +} + +function testMedian (tree, expectedValue) { + assert.ok(tree.median() === expectedValue, `Expected median to be ${expectedValue}, encountered ${tree.median()}`) +} + +test.run() diff --git a/app/engine/utils/BucketedLinearSeries.js b/app/engine/utils/BucketedLinearSeries.js new file mode 100644 index 0000000000..d9352c1ad1 --- /dev/null +++ b/app/engine/utils/BucketedLinearSeries.js @@ -0,0 +1,163 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + This Module calculates a bucketed Linear Regression. It assumes a rising line. +*/ + +import { createOLSLinearSeries } from './OLSLinearSeries.js' + +function createBucketedLinearSeries (config) { + const linearSeries = createOLSLinearSeries() + const xCutOffInterval = 5.0 + const yCutOffInterval = 7.0 + const minimumValuesInBracket = 6.0 + + let xBracketStart = 0.0 + let xBracketEnd = 0.0 + let yBracketStart = 0.0 + let yBracketEnd = 0.0 + + let xTotal = 0.0 + let yTotal = 0.0 + let xSum = 0.0 + let ySum = 0.0 + let numberOfValuesInBracket = 0.0 + let numberOfValues = 0.0 + let maxX = 0.0 + let maxY = 0.0 + + function push (x, y) { + maxX = Math.max(maxX, x) + maxY = Math.max(maxY, y) + if ((yBracketStart <= y) && (y <= yBracketEnd) && (xBracketStart <= x) && (x <= xBracketEnd)) { + // We are still in the same bracket + xTotal += x + yTotal += y + xSum += x + ySum += y + numberOfValuesInBracket += 1 + numberOfValues += 1 + } else { + // We are outside the current bracket + if (numberOfValuesInBracket >= minimumValuesInBracket) { + // The latest bracket isn't empty or too shallow, so let's add the average to the dataset + linearSeries.push((xTotal / numberOfValuesInBracket), (yTotal / numberOfValuesInBracket)) + } + + // Let's determine the position of the next bracket + // First, we determine the x Position + if (xBracketStart > x) { + // Looks like we bottomed out below the bracket, so lets make the bracket completely below it + xBracketStart = x - xCutOffInterval + xBracketEnd = x + } else { + // The heartrate probably went up + xBracketStart = x + xBracketEnd = x + xCutOffInterval + } + + // Next, we determine the y position + if (yBracketStart > y) { + // Looks like we bottomed out below the bracket, so lets make the bracket completely below it + yBracketStart = y - yCutOffInterval + yBracketEnd = y + } else { + // The power is most likely to go up + yBracketStart = y + yBracketEnd = y + yCutOffInterval + } + + // Let's fill the first datapoint in the new bracket + xTotal = x + yTotal = y + xSum += x + ySum += y + numberOfValuesInBracket = 1 + numberOfValues += 1 + } + } + + function slope () { + return linearSeries.slope() + } + + function intercept () { + return linearSeries.slope() + } + + function numberOfSamples () { + return linearSeries.length() + } + + function goodnessOfFit () { + // This function returns the R^2 as a goodness of fit indicator + return linearSeries.goodnessOfFit() + } + + function projectX (x) { + return linearSeries.projectX(x) + } + + function projectY (y) { + return linearSeries.projectY(y) + } + + function maxEncounteredX () { + return maxX + } + + function maxEncounteredY () { + return maxY + } + + function averageEncounteredX () { + if (numberOfValues > 0) { + return xSum / numberOfValues + } else { + return 0 + } + } + + function averageEncounteredY () { + if (numberOfValues > 0) { + return ySum / numberOfValues + } else { + return 0 + } + } + + function reset () { + // Nothing to do yet + linearSeries.reset() + xBracketStart = 0.0 + xBracketEnd = 0.0 + yBracketStart = 0.0 + yBracketEnd = 0.0 + xTotal = 0.0 + yTotal = 0.0 + xSum = 0.0 + ySum = 0.0 + numberOfValuesInBracket = 0.0 + numberOfValues = 0.0 + maxX = 0.0 + maxY = 0.0 + } + + return { + push, + slope, + intercept, + numberOfSamples, + goodnessOfFit, + projectX, + projectY, + maxEncounteredX, + maxEncounteredY, + averageEncounteredX, + averageEncounteredY, + reset + } +} + +export { createBucketedLinearSeries } diff --git a/app/engine/utils/CurveAligner.js b/app/engine/utils/CurveAligner.js new file mode 100644 index 0000000000..adff69c346 --- /dev/null +++ b/app/engine/utils/CurveAligner.js @@ -0,0 +1,43 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + This keeps an array, for ForceMetrics, and cleans it up +*/ + +function createCurveAligner (minimumValue) { + let _lastCompleteCurve = [] + + function push (curve) { + // First, remove all unneccessary leading zero's + while (curve.length > 5 && (curve[0] < minimumValue || curve[1] < minimumValue || curve[2] < minimumValue || curve[3] < minimumValue || curve[4] < minimumValue)) { + curve.shift() + } + + // Next, we clean up the trailing noise in the tail of the array + while (curve.length > 5 && (curve[curve.length - 1] < minimumValue || curve[curve.length - 2] < minimumValue || curve[curve.length - 3] < minimumValue || curve[curve.length - 4] < minimumValue || curve[curve.length - 5] < minimumValue)) { + curve.pop() + } + _lastCompleteCurve = Array.from(curve) + } + + function lastCompleteCurve () { + if (_lastCompleteCurve.length > 0) { + return _lastCompleteCurve + } else { + return [] + } + } + + function reset () { + _lastCompleteCurve.splice(0, _lastCompleteCurve.length) + } + + return { + push, + lastCompleteCurve, + reset + } +} + +export { createCurveAligner } diff --git a/app/engine/utils/FullTSLinearSeries.js b/app/engine/utils/FullTSLinearSeries.js new file mode 100644 index 0000000000..ec2f2c1838 --- /dev/null +++ b/app/engine/utils/FullTSLinearSeries.js @@ -0,0 +1,240 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + The TSLinearSeries is a datatype that represents a Linear Series. It allows + values to be retrieved (like a FiFo buffer, or Queue) but it also includes + a Theil-Sen estimator Linear Regressor to determine the slope of this timeseries. + + At creation its length is determined. After it is filled, the oldest will be pushed + out of the queue) automatically. + + A key constraint is to prevent heavy calculations at the end (due to large + array based curve fitting), which might happen on a Pi zero + + This implementation uses concepts that are described here: + https://en.wikipedia.org/wiki/Theil%E2%80%93Sen_estimator + + The array is ordered such that x[0] is the oldest, and x[x.length-1] is the youngest +*/ + +import { createSeries } from './Series.js' +import { createLabelledBinarySearchTree } from './BinarySearchTree.js' + +import loglevel from 'loglevel' +const log = loglevel.getLogger('RowingEngine') + +function createTSLinearSeries (maxSeriesLength = 0) { + const X = createSeries(maxSeriesLength) + const Y = createSeries(maxSeriesLength) + const A = createLabelledBinarySearchTree() + + let _A = 0 + let _B = 0 + + function push (x, y) { + // Invariant: A contains all a's (as in the general formula y = a * x^2 + b * x + c) + // Where the a's are labeled in the Binary Search Tree with their xi when they BEGIN in the point (xi, yi) + if (maxSeriesLength > 0 && X.length() >= maxSeriesLength) { + // The maximum of the array has been reached, so when pushing the x,y the array gets shifted, + // thus we have to remove the a's belonging to the current position X0 as well before this value is trashed + A.remove(X.get(0)) + } + + X.push(x) + Y.push(y) + + // Calculate all the slopes of the newly added point + if (X.length() > 1) { + // There are at least two points in the X and Y arrays, so let's add the new datapoint + let i = 0 + while (i < X.length() - 1) { + A.push(X.get(i), calculateSlope(i, X.length() - 1)) + i++ + } + } + + // Calculate the median of the slopes + if (X.length() > 1) { + _A = A.median() + } else { + _A = 0 + } + + // Calculate all the intercepts for the newly added point and the newly calculated A + const B = createLabelledBinarySearchTree() + if (X.length() > 1) { + // There are at least two points in the X and Y arrays, so let's calculate the intercept + let i = 0 + while (i < X.length() - 1) { + // Please note , as we need to recreate the B-tree for each newly added datapoint anyway, the label i isn't relevant + B.push(i, (Y.get(i) - (_A * X.get(i)))) + i++ + } + } + + _B = B.median() + } + + function slope () { + return _A + } + + function intercept () { + return _B + } + + function coefficientA () { + // For testing purposses only! + return _A + } + + function coefficientB () { + // For testing purposses only! + return _B + } + + function length () { + return X.length() + } + + function goodnessOfFit () { + // This function returns the R^2 as a goodness of fit indicator + let i = 0 + let ssr = 0 + let sst = 0 + if (X.length() >= 2) { + while (i < X.length() - 1) { + ssr += Math.pow((Y.get(i) - projectX(X.get(i))), 2) + sst += Math.pow((Y.get(i) - Y.average()), 2) + i++ + } + if (sst !== 0) { + const _goodnessOfFit = 1 - (ssr / sst) + return _goodnessOfFit + } else { + return 0 + } + } else { + return 0 + } + } + + function projectX (x) { + if (X.length() >= 2) { + return (_A * x) + _B + } else { + return 0 + } + } + + function projectY (y) { + if (X.length() >= 2 && _A !== 0) { + return ((y - _B) / _A) + } else { + return 0 + } + } + + function numberOfXValuesAbove (testedValue) { + return X.numberOfValuesAbove(testedValue) + } + + function numberOfXValuesEqualOrBelow (testedValue) { + return X.numberOfValuesEqualOrBelow(testedValue) + } + + function numberOfYValuesAbove (testedValue) { + return Y.numberOfValuesAbove(testedValue) + } + + function numberOfYValuesEqualOrBelow (testedValue) { + return Y.numberOfValuesEqualOrBelow(testedValue) + } + + function xAtSeriesBegin () { + return X.atSeriesBegin() + } + + function xAtSeriesEnd () { + return X.atSeriesEnd() + } + + function yAtSeriesBegin () { + return Y.atSeriesBegin() + } + + function yAtSeriesEnd () { + return Y.atSeriesEnd() + } + + function xSum () { + return X.sum() + } + + function ySum () { + return Y.sum() + } + + function xAverage () { + return X.average() + } + + function yAverage () { + return Y.average() + } + + function xSeries () { + return X.series() + } + + function ySeries () { + return Y.series() + } + + function calculateSlope (pointOne, pointTwo) { + if (pointOne !== pointTwo && X.get(pointOne) !== X.get(pointTwo)) { + return ((Y.get(pointTwo) - Y.get(pointOne)) / (X.get(pointTwo) - X.get(pointOne))) + } else { + log.error('TS Linear Regressor, Division by zero prevented!') + return 0 + } + } + + function reset () { + X.reset() + Y.reset() + A.reset() + _A = 0 + _B = 0 + } + + return { + push, + slope, + intercept, + coefficientA, + coefficientB, + length, + goodnessOfFit, + projectX, + projectY, + numberOfXValuesAbove, + numberOfXValuesEqualOrBelow, + numberOfYValuesAbove, + numberOfYValuesEqualOrBelow, + xAtSeriesBegin, + xAtSeriesEnd, + yAtSeriesBegin, + yAtSeriesEnd, + xSum, + ySum, + xAverage, + yAverage, + xSeries, + ySeries, + reset + } +} + +export { createTSLinearSeries } diff --git a/app/engine/utils/FullTSLinearSeries.test.js b/app/engine/utils/FullTSLinearSeries.test.js new file mode 100644 index 0000000000..8b0153e548 --- /dev/null +++ b/app/engine/utils/FullTSLinearSeries.test.js @@ -0,0 +1,268 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor +*/ +import { test } from 'uvu' +import * as assert from 'uvu/assert' + +import { createTSLinearSeries } from './FullTSLinearSeries.js' + +test('Correct behaviour of a series after initialisation', () => { + const dataSeries = createTSLinearSeries(3) + testLength(dataSeries, 0) + testXAtSeriesBegin(dataSeries, 0) + testYAtSeriesBegin(dataSeries, 0) + testXAtSeriesEnd(dataSeries, 0) + testYAtSeriesEnd(dataSeries, 0) + testNumberOfXValuesAbove(dataSeries, 0, 0) + testNumberOfYValuesAbove(dataSeries, 0, 0) + testNumberOfXValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfXValuesAbove(dataSeries, 10, 0) + testNumberOfYValuesAbove(dataSeries, 10, 0) + testNumberOfXValuesEqualOrBelow(dataSeries, 10, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 10, 0) + testXSum(dataSeries, 0) + testYSum(dataSeries, 0) + testSlopeEquals(dataSeries, 0) + testInterceptEquals(dataSeries, 0) + testGoodnessOfFitEquals(dataSeries, 0) +}) + +test('Correct behaviour of a series after several puhed values, function y = 3x + 6, noisefree, 1 datapoint', () => { + const dataSeries = createTSLinearSeries(3) + testLength(dataSeries, 0) + dataSeries.push(5, 9) + testLength(dataSeries, 1) + testXAtSeriesBegin(dataSeries, 5) + testYAtSeriesBegin(dataSeries, 9) + testXAtSeriesEnd(dataSeries, 5) + testYAtSeriesEnd(dataSeries, 9) + testNumberOfXValuesAbove(dataSeries, 0, 1) + testNumberOfYValuesAbove(dataSeries, 0, 1) + testNumberOfXValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfXValuesAbove(dataSeries, 10, 0) + testNumberOfYValuesAbove(dataSeries, 10, 0) + testNumberOfXValuesEqualOrBelow(dataSeries, 10, 1) + testNumberOfYValuesEqualOrBelow(dataSeries, 10, 1) + testXSum(dataSeries, 5) + testYSum(dataSeries, 9) + testSlopeEquals(dataSeries, 0) + testInterceptEquals(dataSeries, 0) + testGoodnessOfFitEquals(dataSeries, 0) +}) + +test('Correct behaviour of a series after several puhed values, function y = 3x + 6, noisefree, 2 datapoints', () => { + const dataSeries = createTSLinearSeries(3) + dataSeries.push(5, 9) + dataSeries.push(3, 3) + testLength(dataSeries, 2) + testXAtSeriesBegin(dataSeries, 5) + testYAtSeriesBegin(dataSeries, 9) + testXAtSeriesEnd(dataSeries, 3) + testYAtSeriesEnd(dataSeries, 3) + testNumberOfXValuesAbove(dataSeries, 0, 2) + testNumberOfYValuesAbove(dataSeries, 0, 2) + testNumberOfXValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfXValuesAbove(dataSeries, 10, 0) + testNumberOfYValuesAbove(dataSeries, 10, 0) + testNumberOfXValuesEqualOrBelow(dataSeries, 10, 2) + testNumberOfYValuesEqualOrBelow(dataSeries, 10, 2) + testXSum(dataSeries, 8) + testYSum(dataSeries, 12) + testSlopeEquals(dataSeries, 3) + testInterceptEquals(dataSeries, -6) + testGoodnessOfFitEquals(dataSeries, 1) +}) + +test('Correct behaviour of a series after several puhed values, function y = 3x + 6, noisefree, 3 datapoints', () => { + const dataSeries = createTSLinearSeries(3) + dataSeries.push(5, 9) + dataSeries.push(3, 3) + dataSeries.push(4, 6) + testLength(dataSeries, 3) + testXAtSeriesBegin(dataSeries, 5) + testYAtSeriesBegin(dataSeries, 9) + testXAtSeriesEnd(dataSeries, 4) + testYAtSeriesEnd(dataSeries, 6) + testNumberOfXValuesAbove(dataSeries, 0, 3) + testNumberOfYValuesAbove(dataSeries, 0, 3) + testNumberOfXValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfXValuesAbove(dataSeries, 10, 0) + testNumberOfYValuesAbove(dataSeries, 10, 0) + testNumberOfXValuesEqualOrBelow(dataSeries, 10, 3) + testNumberOfYValuesEqualOrBelow(dataSeries, 10, 3) + testXSum(dataSeries, 12) + testYSum(dataSeries, 18) + testSlopeEquals(dataSeries, 3) + testInterceptEquals(dataSeries, -6) + testGoodnessOfFitEquals(dataSeries, 1) +}) + +test('Correct behaviour of a series after several puhed values, function y = 3x + 6, noisefree, 4 datapoints', () => { + const dataSeries = createTSLinearSeries(3) + dataSeries.push(5, 9) + dataSeries.push(3, 3) + dataSeries.push(4, 6) + dataSeries.push(6, 12) + testLength(dataSeries, 3) + testXAtSeriesBegin(dataSeries, 3) + testYAtSeriesBegin(dataSeries, 3) + testXAtSeriesEnd(dataSeries, 6) + testYAtSeriesEnd(dataSeries, 12) + testNumberOfXValuesAbove(dataSeries, 0, 3) + testNumberOfYValuesAbove(dataSeries, 0, 3) + testNumberOfXValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfXValuesAbove(dataSeries, 10, 0) + testNumberOfYValuesAbove(dataSeries, 10, 1) + testNumberOfXValuesEqualOrBelow(dataSeries, 10, 3) + testNumberOfYValuesEqualOrBelow(dataSeries, 10, 2) + testXSum(dataSeries, 13) + testYSum(dataSeries, 21) + testSlopeEquals(dataSeries, 3) + testInterceptEquals(dataSeries, -6) + testGoodnessOfFitEquals(dataSeries, 1) +}) + +test('Correct behaviour of a series after several puhed values, function y = 3x + 6, noisefree, 5 datapoints', () => { + const dataSeries = createTSLinearSeries(3) + dataSeries.push(5, 9) + dataSeries.push(3, 3) + dataSeries.push(4, 6) + dataSeries.push(6, 12) + dataSeries.push(1, -3) + testLength(dataSeries, 3) + testXAtSeriesBegin(dataSeries, 4) + testYAtSeriesBegin(dataSeries, 6) + testXAtSeriesEnd(dataSeries, 1) + testYAtSeriesEnd(dataSeries, -3) + testNumberOfXValuesAbove(dataSeries, 0, 3) + testNumberOfYValuesAbove(dataSeries, 0, 2) + testNumberOfXValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 0, 1) + testNumberOfXValuesAbove(dataSeries, 10, 0) + testNumberOfYValuesAbove(dataSeries, 10, 1) + testNumberOfXValuesEqualOrBelow(dataSeries, 10, 3) + testNumberOfYValuesEqualOrBelow(dataSeries, 10, 2) + testXSum(dataSeries, 11) + testYSum(dataSeries, 15) + testSlopeEquals(dataSeries, 3) + testInterceptEquals(dataSeries, -6) + testGoodnessOfFitEquals(dataSeries, 1) +}) + +test('Correct behaviour of a series after several puhed values, function y = 3x + 6, noisefree, 4 datapoints and a reset', () => { + const dataSeries = createTSLinearSeries(3) + dataSeries.push(5, 9) + dataSeries.push(3, 3) + dataSeries.push(4, 6) + dataSeries.push(6, 12) + dataSeries.reset() + testLength(dataSeries, 0) + testXAtSeriesBegin(dataSeries, 0) + testYAtSeriesBegin(dataSeries, 0) + testXAtSeriesEnd(dataSeries, 0) + testYAtSeriesEnd(dataSeries, 0) + testNumberOfXValuesAbove(dataSeries, 0, 0) + testNumberOfYValuesAbove(dataSeries, 0, 0) + testNumberOfXValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfXValuesAbove(dataSeries, 10, 0) + testNumberOfYValuesAbove(dataSeries, 10, 0) + testNumberOfXValuesEqualOrBelow(dataSeries, 10, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 10, 0) + testXSum(dataSeries, 0) + testYSum(dataSeries, 0) + testSlopeEquals(dataSeries, 0) + testInterceptEquals(dataSeries, 0) + testGoodnessOfFitEquals(dataSeries, 0) +}) + +test('Series with 5 elements, with 2 noisy datapoints', () => { + const dataSeries = createTSLinearSeries(5) + dataSeries.push(5, 9) + dataSeries.push(3, 2) + dataSeries.push(4, 7) + dataSeries.push(6, 12) + dataSeries.push(1, -3) + testSlopeBetween(dataSeries, 2.9, 3.1) + testInterceptBetween(dataSeries, -6.3, -5.8) + testGoodnessOfFitBetween(dataSeries, 0.9, 1.0) +}) + +function testLength (series, expectedValue) { + assert.ok(series.length() === expectedValue, `Expected length should be ${expectedValue}, encountered a ${series.length()}`) +} + +function testXAtSeriesBegin (series, expectedValue) { + assert.ok(series.xAtSeriesBegin() === expectedValue, `Expected xAtSeriesBegin to be ${expectedValue}, encountered a ${series.xAtSeriesBegin()}`) +} + +function testYAtSeriesBegin (series, expectedValue) { + assert.ok(series.yAtSeriesBegin() === expectedValue, `Expected yAtSeriesBegin to be ${expectedValue}, encountered a ${series.yAtSeriesBegin()}`) +} + +function testXAtSeriesEnd (series, expectedValue) { + assert.ok(series.xAtSeriesEnd() === expectedValue, `Expected xAtSeriesEnd to be ${expectedValue}, encountered a ${series.xAtSeriesEnd()}`) +} + +function testYAtSeriesEnd (series, expectedValue) { + assert.ok(series.yAtSeriesEnd() === expectedValue, `Expected yAtSeriesEnd to be ${expectedValue}, encountered a ${series.yAtSeriesEnd()}`) +} + +function testNumberOfXValuesAbove (series, cutoff, expectedValue) { + assert.ok(series.numberOfXValuesAbove(cutoff) === expectedValue, `Expected numberOfXValuesAbove(${cutoff}) to be ${expectedValue}, encountered a ${series.numberOfXValuesAbove(cutoff)}`) +} + +function testNumberOfYValuesAbove (series, cutoff, expectedValue) { + assert.ok(series.numberOfYValuesAbove(cutoff) === expectedValue, `Expected numberOfYValuesAbove(${cutoff}) to be ${expectedValue}, encountered a ${series.numberOfYValuesAbove(cutoff)}`) +} + +function testNumberOfXValuesEqualOrBelow (series, cutoff, expectedValue) { + assert.ok(series.numberOfXValuesEqualOrBelow(cutoff) === expectedValue, `Expected numberOfXValuesEqualOrBelow(${cutoff}) to be ${expectedValue}, encountered a ${series.numberOfXValuesEqualOrBelow(cutoff)}`) +} + +function testNumberOfYValuesEqualOrBelow (series, cutoff, expectedValue) { + assert.ok(series.numberOfYValuesEqualOrBelow(cutoff) === expectedValue, `Expected numberOfYValuesEqualOrBelow(${cutoff}) to be ${expectedValue}, encountered a ${series.numberOfYValuesEqualOrBelow(cutoff)}`) +} + +function testXSum (series, expectedValue) { + assert.ok(series.xSum() === expectedValue, `Expected xSum to be ${expectedValue}, encountered a ${series.xSum()}`) +} + +function testYSum (series, expectedValue) { + assert.ok(series.ySum() === expectedValue, `Expected ySum to be ${expectedValue}, encountered a ${series.ySum()}`) +} + +function testSlopeEquals (series, expectedValue) { + assert.ok(series.slope() === expectedValue, `Expected slope to be ${expectedValue}, encountered a ${series.slope()}`) +} + +function testSlopeBetween (series, expectedValueAbove, expectedValueBelow) { + assert.ok(series.slope() > expectedValueAbove, `Expected slope to be above ${expectedValueAbove}, encountered a ${series.slope()}`) + assert.ok(series.slope() < expectedValueBelow, `Expected slope to be below ${expectedValueBelow}, encountered a ${series.slope()}`) +} + +function testInterceptEquals (series, expectedValue) { + assert.ok(series.intercept() === expectedValue, `Expected intercept to be ${expectedValue}, encountered ${series.intercept()}`) +} + +function testInterceptBetween (series, expectedValueAbove, expectedValueBelow) { + assert.ok(series.intercept() > expectedValueAbove, `Expected intercept to be above ${expectedValueAbove}, encountered ${series.intercept()}`) + assert.ok(series.intercept() < expectedValueBelow, `Expected intercept to be below ${expectedValueBelow}, encountered ${series.intercept()}`) +} + +function testGoodnessOfFitEquals (series, expectedValue) { + assert.ok(series.goodnessOfFit() === expectedValue, `Expected goodnessOfFit to be ${expectedValue}, encountered ${series.goodnessOfFit()}`) +} + +function testGoodnessOfFitBetween (series, expectedValueAbove, expectedValueBelow) { + assert.ok(series.goodnessOfFit() > expectedValueAbove, `Expected goodnessOfFit to be above ${expectedValueAbove}, encountered ${series.goodnessOfFit()}`) + assert.ok(series.goodnessOfFit() < expectedValueBelow, `Expected goodnessOfFit to be below ${expectedValueBelow}, encountered ${series.goodnessOfFit()}`) +} + +test.run() diff --git a/app/engine/utils/FullTSQuadraticSeries.js b/app/engine/utils/FullTSQuadraticSeries.js new file mode 100644 index 0000000000..0329af29a8 --- /dev/null +++ b/app/engine/utils/FullTSQuadraticSeries.js @@ -0,0 +1,308 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + The FullTSQuadraticSeries is a datatype that represents a Quadratic Series. It allows + values to be retrieved (like a FiFo buffer, or Queue) but it also includes + a Theil-Sen Quadratic Regressor to determine the coefficients of this dataseries. + + At creation its length is determined. After it is filled, the oldest will be pushed + out of the queue) automatically. + + A key constraint is to prevent heavy calculations at the end (due to large + array based curve fitting), which might be performed on a Pi zero + + The Theil-Senn implementation uses concepts that are described here: + https://stats.stackexchange.com/questions/317777/theil-sen-estimator-for-polynomial, + + The determination of the coefficients is based on the math descirbed here: + https://www.quora.com/How-do-I-find-a-quadratic-equation-from-points/answer/Robert-Paxson, + https://www.physicsforums.com/threads/quadratic-equation-from-3-points.404174/ +*/ + +import { createSeries } from './Series.js' +import { createTSLinearSeries } from './FullTSLinearSeries.js' +import { createLabelledBinarySearchTree } from './BinarySearchTree.js' + +import loglevel from 'loglevel' +const log = loglevel.getLogger('RowingEngine') + +function createTSQuadraticSeries (maxSeriesLength = 0) { + const X = createSeries(maxSeriesLength) + const Y = createSeries(maxSeriesLength) + const A = createLabelledBinarySearchTree() + let _A = 0 + let _B = 0 + let _C = 0 + + function push (x, y) { + // Invariant: A contains all a's (as in the general formula y = a * x^2 + b * x + c) + // Where the a's are labeled in the Binary Search Tree with their Xi when they BEGIN in the point (Xi, Yi) + + if (maxSeriesLength > 0 && X.length() >= maxSeriesLength) { + // The maximum of the array has been reached, so when pushing the new datapoint (x,y), the array will get shifted, + // thus we have to remove all the A's that start with the old position X0 BEFORE this value gets thrown away + A.remove(X.get(0)) + } + + X.push(x) + Y.push(y) + + // Calculate the coefficient a for the new interval by adding the newly added datapoint + let i = 0 + let j = 0 + const linearResidu = createTSLinearSeries(maxSeriesLength) + + switch (true) { + case (X.length() > 2): + // There are now at least three datapoints in the X and Y arrays, so let's calculate the A portion belonging for the new datapoint via Quadratic Theil-Sen regression + // First we calculate the A for the formula + while (i < X.length() - 2) { + j = i + 1 + while (j < X.length() - 1) { + A.push(X.get(i), calculateA(i, j, X.length() - 1)) + j++ + } + i++ + } + _A = A.median() + + // Next, we calculate the B and C via Linear regression over the residu + i = 0 + while (i < X.length() - 1) { + linearResidu.push(X.get(i), Y.get(i) - (_A * Math.pow(X.get(i), 2))) + i++ + } + _B = linearResidu.coefficientA() + _C = linearResidu.coefficientB() + break + case (X.length() === 2 && X.get(1) - X.get(0) !== 0): + // There are only two datapoints, so we need to be creative to get to a quadratic solution + // As we know this is part of a 'linear' acceleration, we know that the second derivative should obey 2 * _A = angular acceleration = 2 * angular distance / (delta t)^2 + _A = (Y.get(1) - Y.get(0)) / Math.pow(X.get(1) - X.get(0), 2) + // As the first derivative should match angular velocity (= angular acceleration * (delta t)) + _B = -2 * _A * X.get(0) + _C = 0 + break + default: + _A = 0 + _B = 0 + _C = 0 + } + } + + function firstDerivativeAtPosition (position) { + if (X.length() > 1 && position < X.length()) { + return ((_A * 2 * X.get(position)) + _B) + } else { + return 0 + } + } + + function secondDerivativeAtPosition (position) { + if (X.length() > 1 && position < X.length()) { + return (_A * 2) + } else { + return 0 + } + } + + function slope (x) { + if (X.length() > 2) { + return ((_A * 2 * x) + _B) + } else { + return 0 + } + } + + function coefficientA () { + // For testing purposses only! + return _A + } + + function coefficientB () { + // For testing purposses only! + return _B + } + + function coefficientC () { + // For testing purposses only! + return _C + } + + function intercept () { + return coefficientC() + } + + function length () { + return X.length() + } + + function goodnessOfFit () { + // This function returns the R^2 as a goodness of fit indicator + let i = 0 + let ssr = 0 + let sst = 0 + if (X.length() >= 2) { + while (i < X.length() - 1) { + ssr += Math.pow((Y.get(i) - projectX(X.get(i))), 2) + sst += Math.pow((Y.get(i) - Y.average()), 2) + i++ + } + if (sst !== 0) { + const _goodnessOfFit = 1 - (ssr / sst) + return _goodnessOfFit + } else { + return 0 + } + } else { + return 0 + } + } + + function projectX (x) { + if (X.length() > 2) { + return ((_A * x * x) + (_B * x) + _C) + } else { + return 0 + } + } + + function numberOfXValuesAbove (testedValue) { + return X.numberOfValuesAbove(testedValue) + } + + function numberOfXValuesEqualOrBelow (testedValue) { + return X.numberOfValuesEqualOrBelow(testedValue) + } + + function numberOfYValuesAbove (testedValue) { + return Y.numberOfValuesAbove(testedValue) + } + + function numberOfYValuesEqualOrBelow (testedValue) { + return Y.numberOfValuesEqualOrBelow(testedValue) + } + + function xAtSeriesBegin () { + return X.atSeriesBegin() + } + + function xAtSeriesEnd () { + return X.atSeriesEnd() + } + + function xAtPosition (position) { + return X.get(position) + } + + function yAtSeriesBegin () { + return Y.atSeriesBegin() + } + + function yAtSeriesEnd () { + return Y.atSeriesEnd() + } + + function yAtPosition (position) { + return Y.get(position) + } + + function xSum () { + return X.sum() + } + + function ySum () { + return Y.sum() + } + + function minimumX () { + return X.minimum() + } + + function minimumY () { + return Y.minimum() + } + + function maximumX () { + return X.maximum() + } + + function maximumY () { + return Y.maximum() + } + + function xAverage () { + return X.average() + } + + function yAverage () { + return Y.average() + } + + function xSeries () { + return X.series() + } + + function ySeries () { + return Y.series() + } + + function calculateA (pointOne, pointTwo, pointThree) { + let result = 0 + if (X.get(pointOne) !== X.get(pointTwo) && X.get(pointOne) !== X.get(pointThree) && X.get(pointTwo) !== X.get(pointThree)) { + // For the underlying math, see https://www.quora.com/How-do-I-find-a-quadratic-equation-from-points/answer/Robert-Paxson + result = (X.get(pointOne) * (Y.get(pointThree) - Y.get(pointTwo)) + Y.get(pointOne) * (X.get(pointTwo) - X.get(pointThree)) + (X.get(pointThree) * Y.get(pointTwo) - X.get(pointTwo) * Y.get(pointThree))) / ((X.get(pointOne) - X.get(pointTwo)) * (X.get(pointOne) - X.get(pointThree)) * (X.get(pointTwo) - X.get(pointThree))) + return result + } else { + log.error('TS Quadratic Regressor, Division by zero prevented in CalculateA!') + return 0 + } + } + + function reset () { + X.reset() + Y.reset() + A.reset() + _A = 0 + _B = 0 + _C = 0 + } + + return { + push, + firstDerivativeAtPosition, + secondDerivativeAtPosition, + slope, + coefficientA, + coefficientB, + coefficientC, + intercept, + length, + goodnessOfFit, + projectX, + numberOfXValuesAbove, + numberOfXValuesEqualOrBelow, + numberOfYValuesAbove, + numberOfYValuesEqualOrBelow, + xAtSeriesBegin, + xAtSeriesEnd, + xAtPosition, + yAtSeriesBegin, + yAtSeriesEnd, + yAtPosition, + minimumX, + minimumY, + maximumX, + maximumY, + xAverage, + yAverage, + xSum, + ySum, + xSeries, + ySeries, + reset + } +} + +export { createTSQuadraticSeries } diff --git a/app/engine/utils/FullTSQuadraticSeries.test.js b/app/engine/utils/FullTSQuadraticSeries.test.js new file mode 100644 index 0000000000..effb7aa975 --- /dev/null +++ b/app/engine/utils/FullTSQuadraticSeries.test.js @@ -0,0 +1,546 @@ +'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, 3) + dataSeries.push(10, 444) + testCoefficientA(dataSeries, 4) + testCoefficientB(dataSeries, 4) + testCoefficientC(dataSeries, 5) +}) + +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, 3.476190476190478) // This is quite acceptable as ORM ignores the C + dataSeries.push(9, 363) + testCoefficientA(dataSeries, 4) + testCoefficientB(dataSeries, 4) + testCoefficientC(dataSeries, 3) // 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, 5) +}) + +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() diff --git a/app/engine/utils/OLSLinearSeries.js b/app/engine/utils/OLSLinearSeries.js new file mode 100644 index 0000000000..ebc381be6e --- /dev/null +++ b/app/engine/utils/OLSLinearSeries.js @@ -0,0 +1,223 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + The LinearSeries is a datatype that represents a Linear Series. It allows + values to be retrieved (like a FiFo buffer, or Queue) but it also includes + a Linear Regressor to determine the slope, intercept and R^2 of this timeseries + of x any y coordinates through Simple Linear Regression. + + At creation it can be determined that the Time Series is limited (i.e. after it + is filled, the oldest will be pushed out of the queue) or that the the time series + is unlimited (will only expand). The latter is activated by calling the creation with + an empty argument. + + please note that for unlimited series it is up to the calling function to handle resetting + the Linear Series when needed through the reset() call. + + A key constraint is to prevent heavy calculations at the end (due to large + array based curve fitting) as this function is also used to calculate + drag at the end of the recovery phase, which might happen on a Pi zero + + This implementation uses concepts that are described here: + https://www.colorado.edu/amath/sites/default/files/attached-files/ch12_0.pdf +*/ + +import { createSeries } from './Series.js' + +import loglevel from 'loglevel' +const log = loglevel.getLogger('RowingEngine') + +function createOLSLinearSeries (maxSeriesLength = 0) { + const X = createSeries(maxSeriesLength) + const XX = createSeries(maxSeriesLength) + const Y = createSeries(maxSeriesLength) + const YY = createSeries(maxSeriesLength) + const XY = createSeries(maxSeriesLength) + const trend = createSeries(maxSeriesLength) + let _slope = 0 + let _intercept = 0 + let _goodnessOfFit = 0 + + function push (x, y) { + X.push(x) + XX.push(x * x) + Y.push(y) + YY.push(y * y) + XY.push(x * y) + + // Let's approximate the line through OLS + if (X.length() >= 2 && X.sum() > 0) { + _slope = (X.length() * XY.sum() - X.sum() * Y.sum()) / (X.length() * XX.sum() - X.sum() * X.sum()) + _intercept = (Y.sum() - (_slope * X.sum())) / X.length() + const sse = YY.sum() - (_intercept * Y.sum()) - (_slope * XY.sum()) + const sst = YY.sum() - (Math.pow(Y.sum(), 2) / X.length()) + _goodnessOfFit = 1 - (sse / sst) + trend.push(determineTrend(X.length() - 2, X.length() - 1)) + } else { + _slope = 0 + _intercept = 0 + _goodnessOfFit = 0 + } + } + + function slope () { + return _slope + } + + function intercept () { + return _intercept + } + + function length () { + return X.length() + } + + function goodnessOfFit () { + // This function returns the R^2 as a goodness of fit indicator + if (X.length() >= 2) { + return _goodnessOfFit + } else { + return 0 + } + } + + function projectX (x) { + if (X.length() >= 2) { + return (_slope * x) + _intercept + } else { + return 0 + } + } + + function projectY (y) { + if (X.length() >= 2 && _slope !== 0) { + return ((y - _intercept) / _slope) + } else { + return 0 + } + } + + function numberOfXValuesAbove (testedValue) { + return X.numberOfValuesAbove(testedValue) + } + + function numberOfXValuesEqualOrBelow (testedValue) { + return X.numberOfValuesEqualOrBelow(testedValue) + } + + function numberOfYValuesAbove (testedValue) { + return Y.numberOfValuesAbove(testedValue) + } + + function numberOfYValuesEqualOrBelow (testedValue) { + return Y.numberOfValuesEqualOrBelow(testedValue) + } + + function numberOfUpwardTrend () { + return trend.numberOfValuesAbove(0) + } + + function numberOfFlatOrDownwardTrend () { + return trend.numberOfValuesEqualOrBelow(0) + } + + function xAtSeriesBegin () { + return X.atSeriesBegin() + } + + function xAtSeriesEnd () { + return X.atSeriesEnd() + } + + function yAtSeriesBegin () { + return Y.atSeriesBegin() + } + + function yAtSeriesEnd () { + return Y.atSeriesEnd() + } + + function xSum () { + return X.sum() + } + + function ySum () { + return Y.sum() + } + + function minimumX () { + return X.minimum() + } + + function minimumY () { + return Y.minimum() + } + + function maximumX () { + return X.maximum() + } + + function maximumY () { + return Y.maximum() + } + + function xSeries () { + return X.series() + } + + function ySeries () { + return Y.series() + } + + function determineTrend (pointOne, pointTwo) { + if (pointOne !== pointTwo) { + return (Y.get(pointTwo) - Y.get(pointOne)) + } else { + log.error('OLS Linear Regressor, trend determination, trend can not be applied to one point!') + return 0 + } + } + + function reset () { + X.reset() + XX.reset() + Y.reset() + YY.reset() + XY.reset() + _slope = 0 + _intercept = 0 + _goodnessOfFit = 0 + } + + return { + push, + slope, + intercept, + length, + goodnessOfFit, + projectX, + projectY, + numberOfXValuesAbove, + numberOfXValuesEqualOrBelow, + numberOfYValuesAbove, + numberOfYValuesEqualOrBelow, + numberOfUpwardTrend, + numberOfFlatOrDownwardTrend, + xAtSeriesBegin, + xAtSeriesEnd, + yAtSeriesBegin, + yAtSeriesEnd, + xSum, + ySum, + minimumX, + minimumY, + maximumX, + maximumY, + xSeries, + ySeries, + reset + } +} + +export { createOLSLinearSeries } diff --git a/app/engine/utils/OLSLinearSeries.test.js b/app/engine/utils/OLSLinearSeries.test.js new file mode 100644 index 0000000000..92e6445eb0 --- /dev/null +++ b/app/engine/utils/OLSLinearSeries.test.js @@ -0,0 +1,268 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor +*/ +import { test } from 'uvu' +import * as assert from 'uvu/assert' + +import { createOLSLinearSeries } from './OLSLinearSeries.js' + +test('Correct behaviour of a series after initialisation', () => { + const dataSeries = createOLSLinearSeries(3) + testLength(dataSeries, 0) + testXAtSeriesBegin(dataSeries, 0) + testYAtSeriesBegin(dataSeries, 0) + testXAtSeriesEnd(dataSeries, 0) + testYAtSeriesEnd(dataSeries, 0) + testNumberOfXValuesAbove(dataSeries, 0, 0) + testNumberOfYValuesAbove(dataSeries, 0, 0) + testNumberOfXValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfXValuesAbove(dataSeries, 10, 0) + testNumberOfYValuesAbove(dataSeries, 10, 0) + testNumberOfXValuesEqualOrBelow(dataSeries, 10, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 10, 0) + testXSum(dataSeries, 0) + testYSum(dataSeries, 0) + testSlopeEquals(dataSeries, 0) + testInterceptEquals(dataSeries, 0) + testGoodnessOfFitEquals(dataSeries, 0) +}) + +test('Correct behaviour of a series after several puhed values, function y = 3x + 6, noisefree, 1 datapoint', () => { + const dataSeries = createOLSLinearSeries(3) + testLength(dataSeries, 0) + dataSeries.push(5, 9) + testLength(dataSeries, 1) + testXAtSeriesBegin(dataSeries, 5) + testYAtSeriesBegin(dataSeries, 9) + testXAtSeriesEnd(dataSeries, 5) + testYAtSeriesEnd(dataSeries, 9) + testNumberOfXValuesAbove(dataSeries, 0, 1) + testNumberOfYValuesAbove(dataSeries, 0, 1) + testNumberOfXValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfXValuesAbove(dataSeries, 10, 0) + testNumberOfYValuesAbove(dataSeries, 10, 0) + testNumberOfXValuesEqualOrBelow(dataSeries, 10, 1) + testNumberOfYValuesEqualOrBelow(dataSeries, 10, 1) + testXSum(dataSeries, 5) + testYSum(dataSeries, 9) + testSlopeEquals(dataSeries, 0) + testInterceptEquals(dataSeries, 0) + testGoodnessOfFitEquals(dataSeries, 0) +}) + +test('Correct behaviour of a series after several puhed values, function y = 3x + 6, noisefree, 2 datapoints', () => { + const dataSeries = createOLSLinearSeries(3) + dataSeries.push(5, 9) + dataSeries.push(3, 3) + testLength(dataSeries, 2) + testXAtSeriesBegin(dataSeries, 5) + testYAtSeriesBegin(dataSeries, 9) + testXAtSeriesEnd(dataSeries, 3) + testYAtSeriesEnd(dataSeries, 3) + testNumberOfXValuesAbove(dataSeries, 0, 2) + testNumberOfYValuesAbove(dataSeries, 0, 2) + testNumberOfXValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfXValuesAbove(dataSeries, 10, 0) + testNumberOfYValuesAbove(dataSeries, 10, 0) + testNumberOfXValuesEqualOrBelow(dataSeries, 10, 2) + testNumberOfYValuesEqualOrBelow(dataSeries, 10, 2) + testXSum(dataSeries, 8) + testYSum(dataSeries, 12) + testSlopeEquals(dataSeries, 3) + testInterceptEquals(dataSeries, -6) + testGoodnessOfFitEquals(dataSeries, 1) +}) + +test('Correct behaviour of a series after several puhed values, function y = 3x + 6, noisefree, 3 datapoints', () => { + const dataSeries = createOLSLinearSeries(3) + dataSeries.push(5, 9) + dataSeries.push(3, 3) + dataSeries.push(4, 6) + testLength(dataSeries, 3) + testXAtSeriesBegin(dataSeries, 5) + testYAtSeriesBegin(dataSeries, 9) + testXAtSeriesEnd(dataSeries, 4) + testYAtSeriesEnd(dataSeries, 6) + testNumberOfXValuesAbove(dataSeries, 0, 3) + testNumberOfYValuesAbove(dataSeries, 0, 3) + testNumberOfXValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfXValuesAbove(dataSeries, 10, 0) + testNumberOfYValuesAbove(dataSeries, 10, 0) + testNumberOfXValuesEqualOrBelow(dataSeries, 10, 3) + testNumberOfYValuesEqualOrBelow(dataSeries, 10, 3) + testXSum(dataSeries, 12) + testYSum(dataSeries, 18) + testSlopeEquals(dataSeries, 3) + testInterceptEquals(dataSeries, -6) + testGoodnessOfFitEquals(dataSeries, 1) +}) + +test('Correct behaviour of a series after several puhed values, function y = 3x + 6, noisefree, 4 datapoints', () => { + const dataSeries = createOLSLinearSeries(3) + dataSeries.push(5, 9) + dataSeries.push(3, 3) + dataSeries.push(4, 6) + dataSeries.push(6, 12) + testLength(dataSeries, 3) + testXAtSeriesBegin(dataSeries, 3) + testYAtSeriesBegin(dataSeries, 3) + testXAtSeriesEnd(dataSeries, 6) + testYAtSeriesEnd(dataSeries, 12) + testNumberOfXValuesAbove(dataSeries, 0, 3) + testNumberOfYValuesAbove(dataSeries, 0, 3) + testNumberOfXValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfXValuesAbove(dataSeries, 10, 0) + testNumberOfYValuesAbove(dataSeries, 10, 1) + testNumberOfXValuesEqualOrBelow(dataSeries, 10, 3) + testNumberOfYValuesEqualOrBelow(dataSeries, 10, 2) + testXSum(dataSeries, 13) + testYSum(dataSeries, 21) + testSlopeEquals(dataSeries, 3) + testInterceptEquals(dataSeries, -6) + testGoodnessOfFitEquals(dataSeries, 1) +}) + +test('Correct behaviour of a series after several puhed values, function y = 3x + 6, noisefree, 5 datapoints', () => { + const dataSeries = createOLSLinearSeries(3) + dataSeries.push(5, 9) + dataSeries.push(3, 3) + dataSeries.push(4, 6) + dataSeries.push(6, 12) + dataSeries.push(1, -3) + testLength(dataSeries, 3) + testXAtSeriesBegin(dataSeries, 4) + testYAtSeriesBegin(dataSeries, 6) + testXAtSeriesEnd(dataSeries, 1) + testYAtSeriesEnd(dataSeries, -3) + testNumberOfXValuesAbove(dataSeries, 0, 3) + testNumberOfYValuesAbove(dataSeries, 0, 2) + testNumberOfXValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 0, 1) + testNumberOfXValuesAbove(dataSeries, 10, 0) + testNumberOfYValuesAbove(dataSeries, 10, 1) + testNumberOfXValuesEqualOrBelow(dataSeries, 10, 3) + testNumberOfYValuesEqualOrBelow(dataSeries, 10, 2) + testXSum(dataSeries, 11) + testYSum(dataSeries, 15) + testSlopeEquals(dataSeries, 3) + testInterceptEquals(dataSeries, -6) + testGoodnessOfFitEquals(dataSeries, 1) +}) + +test('Correct behaviour of a series after several puhed values, function y = 3x + 6, noisefree, 4 datapoints and a reset', () => { + const dataSeries = createOLSLinearSeries(3) + dataSeries.push(5, 9) + dataSeries.push(3, 3) + dataSeries.push(4, 6) + dataSeries.push(6, 12) + dataSeries.reset() + testLength(dataSeries, 0) + testXAtSeriesBegin(dataSeries, 0) + testYAtSeriesBegin(dataSeries, 0) + testXAtSeriesEnd(dataSeries, 0) + testYAtSeriesEnd(dataSeries, 0) + testNumberOfXValuesAbove(dataSeries, 0, 0) + testNumberOfYValuesAbove(dataSeries, 0, 0) + testNumberOfXValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfXValuesAbove(dataSeries, 10, 0) + testNumberOfYValuesAbove(dataSeries, 10, 0) + testNumberOfXValuesEqualOrBelow(dataSeries, 10, 0) + testNumberOfYValuesEqualOrBelow(dataSeries, 10, 0) + testXSum(dataSeries, 0) + testYSum(dataSeries, 0) + testSlopeEquals(dataSeries, 0) + testInterceptEquals(dataSeries, 0) + testGoodnessOfFitEquals(dataSeries, 0) +}) + +test('Series with 5 elements, with 2 noisy datapoints', () => { + const dataSeries = createOLSLinearSeries(5) + dataSeries.push(5, 9) + dataSeries.push(3, 2) + dataSeries.push(4, 7) + dataSeries.push(6, 12) + dataSeries.push(1, -3) + testSlopeBetween(dataSeries, 2.9, 3.1) + testInterceptBetween(dataSeries, -6.3, -5.8) + testGoodnessOfFitBetween(dataSeries, 0.9, 1.0) +}) + +function testLength (series, expectedValue) { + assert.ok(series.length() === expectedValue, `Expected length should be ${expectedValue}, encountered a ${series.length()}`) +} + +function testXAtSeriesBegin (series, expectedValue) { + assert.ok(series.xAtSeriesBegin() === expectedValue, `Expected xAtSeriesBegin to be ${expectedValue}, encountered a ${series.xAtSeriesBegin()}`) +} + +function testYAtSeriesBegin (series, expectedValue) { + assert.ok(series.yAtSeriesBegin() === expectedValue, `Expected yAtSeriesBegin to be ${expectedValue}, encountered a ${series.yAtSeriesBegin()}`) +} + +function testXAtSeriesEnd (series, expectedValue) { + assert.ok(series.xAtSeriesEnd() === expectedValue, `Expected xAtSeriesEnd to be ${expectedValue}, encountered a ${series.xAtSeriesEnd()}`) +} + +function testYAtSeriesEnd (series, expectedValue) { + assert.ok(series.yAtSeriesEnd() === expectedValue, `Expected yAtSeriesEnd to be ${expectedValue}, encountered a ${series.yAtSeriesEnd()}`) +} + +function testNumberOfXValuesAbove (series, cutoff, expectedValue) { + assert.ok(series.numberOfXValuesAbove(cutoff) === expectedValue, `Expected numberOfXValuesAbove(${cutoff}) to be ${expectedValue}, encountered a ${series.numberOfXValuesAbove(cutoff)}`) +} + +function testNumberOfYValuesAbove (series, cutoff, expectedValue) { + assert.ok(series.numberOfYValuesAbove(cutoff) === expectedValue, `Expected numberOfYValuesAbove(${cutoff}) to be ${expectedValue}, encountered a ${series.numberOfYValuesAbove(cutoff)}`) +} + +function testNumberOfXValuesEqualOrBelow (series, cutoff, expectedValue) { + assert.ok(series.numberOfXValuesEqualOrBelow(cutoff) === expectedValue, `Expected numberOfXValuesEqualOrBelow(${cutoff}) to be ${expectedValue}, encountered a ${series.numberOfXValuesEqualOrBelow(cutoff)}`) +} + +function testNumberOfYValuesEqualOrBelow (series, cutoff, expectedValue) { + assert.ok(series.numberOfYValuesEqualOrBelow(cutoff) === expectedValue, `Expected numberOfYValuesEqualOrBelow(${cutoff}) to be ${expectedValue}, encountered a ${series.numberOfYValuesEqualOrBelow(cutoff)}`) +} + +function testXSum (series, expectedValue) { + assert.ok(series.xSum() === expectedValue, `Expected xSum to be ${expectedValue}, encountered a ${series.xSum()}`) +} + +function testYSum (series, expectedValue) { + assert.ok(series.ySum() === expectedValue, `Expected ySum to be ${expectedValue}, encountered a ${series.ySum()}`) +} + +function testSlopeEquals (series, expectedValue) { + assert.ok(series.slope() === expectedValue, `Expected slope to be ${expectedValue}, encountered a ${series.slope()}`) +} + +function testSlopeBetween (series, expectedValueAbove, expectedValueBelow) { + assert.ok(series.slope() > expectedValueAbove, `Expected slope to be above ${expectedValueAbove}, encountered a ${series.slope()}`) + assert.ok(series.slope() < expectedValueBelow, `Expected slope to be below ${expectedValueBelow}, encountered a ${series.slope()}`) +} + +function testInterceptEquals (series, expectedValue) { + assert.ok(series.intercept() === expectedValue, `Expected intercept to be ${expectedValue}, encountered ${series.intercept()}`) +} + +function testInterceptBetween (series, expectedValueAbove, expectedValueBelow) { + assert.ok(series.intercept() > expectedValueAbove, `Expected intercept to be above ${expectedValueAbove}, encountered ${series.intercept()}`) + assert.ok(series.intercept() < expectedValueBelow, `Expected intercept to be below ${expectedValueBelow}, encountered ${series.intercept()}`) +} + +function testGoodnessOfFitEquals (series, expectedValue) { + assert.ok(series.goodnessOfFit() === expectedValue, `Expected goodnessOfFit to be ${expectedValue}, encountered ${series.goodnessOfFit()}`) +} + +function testGoodnessOfFitBetween (series, expectedValueAbove, expectedValueBelow) { + assert.ok(series.goodnessOfFit() > expectedValueAbove, `Expected goodnessOfFit to be above ${expectedValueAbove}, encountered ${series.goodnessOfFit()}`) + assert.ok(series.goodnessOfFit() < expectedValueBelow, `Expected goodnessOfFit to be below ${expectedValueBelow}, encountered ${series.goodnessOfFit()}`) +} + +test.run() diff --git a/app/engine/utils/Series.js b/app/engine/utils/Series.js new file mode 100644 index 0000000000..51e1a93db8 --- /dev/null +++ b/app/engine/utils/Series.js @@ -0,0 +1,167 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + This creates a series with a maximum number of values + It allows for determining the Average, Median, Number of Positive, number of Negative +*/ + +function createSeries (maxSeriesLength) { + const seriesArray = [] + let seriesSum = 0 + let numPos = 0 + let numNeg = 0 + + function push (value) { + if (maxSeriesLength > 0 && seriesArray.length >= maxSeriesLength) { + // The maximum of the array has been reached, we have to create room by removing the first + // value from the array + seriesSum -= seriesArray[0] + if (seriesArray[0] > 0) { + numPos-- + } else { + numNeg-- + } + seriesArray.shift() + } + seriesArray.push(value) + seriesSum += value + if (value > 0) { + numPos++ + } else { + numNeg++ + } + } + + function length () { + return seriesArray.length + } + + function atSeriesBegin () { + if (seriesArray.length > 0) { + return seriesArray[0] + } else { + return 0 + } + } + + function atSeriesEnd () { + if (seriesArray.length > 0) { + return seriesArray[seriesArray.length - 1] + } else { + return 0 + } + } + + function get (position) { + if (position >= 0 && position < seriesArray.length) { + return seriesArray[position] + } else { + return undefined + } + } + + function numberOfValuesAbove (testedValue) { + if (testedValue === 0) { + return numPos + } else { + let i = seriesArray.length - 1 + let count = 0 + while (i >= 0) { + if (seriesArray[i] > testedValue) { + count++ + } + i-- + } + return count + } + } + + function numberOfValuesEqualOrBelow (testedValue) { + if (testedValue === 0) { + return numNeg + } else { + let i = seriesArray.length - 1 + let count = 0 + while (i >= 0) { + if (seriesArray[i] <= testedValue) { + count++ + } + i-- + } + return count + } + } + + function sum () { + return seriesSum + } + + function average () { + if (seriesArray.length > 0) { + return seriesSum / seriesArray.length + } else { + return 0 + } + } + + function minimum () { + if (seriesArray.length > 0) { + return Math.min(...seriesArray) + } else { + return 0 + } + } + + function maximum () { + if (seriesArray.length > 0) { + return Math.max(...seriesArray) + } else { + return 0 + } + } + + function median () { + if (seriesArray.length > 0) { + const mid = Math.floor(seriesArray.length / 2) + const sortedArray = [...seriesArray].sort((a, b) => a - b) + return seriesArray.length % 2 !== 0 ? sortedArray[mid] : (sortedArray[mid - 1] + sortedArray[mid]) / 2 + } else { + return 0 + } + } + + function series () { + if (seriesArray.length > 0) { + return seriesArray + } else { + return [] + } + } + + function reset () { + seriesArray.splice(0, seriesArray.length) + seriesSum = 0 + numPos = 0 + numNeg = 0 + } + + return { + push, + length, + atSeriesBegin, + atSeriesEnd, + get, + numberOfValuesAbove, + numberOfValuesEqualOrBelow, + sum, + average, + minimum, + maximum, + median, + series, + reset + } +} + +export { createSeries } diff --git a/app/engine/utils/Series.test.js b/app/engine/utils/Series.test.js new file mode 100644 index 0000000000..8df93334a7 --- /dev/null +++ b/app/engine/utils/Series.test.js @@ -0,0 +1,163 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + As this object is fundamental for most other utility objects, we must test its behaviour quite thoroughly +*/ +import { test } from 'uvu' +import * as assert from 'uvu/assert' + +import { createSeries } from './Series.js' + +test('Series behaviour with an empty series', () => { + const dataSeries = createSeries(3) + testLength(dataSeries, 0) + testatSeriesBegin(dataSeries, 0) + testAtSeriesEnd(dataSeries, 0) + testNumberOfValuesAbove(dataSeries, 0, 0) + testNumberOfValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfValuesAbove(dataSeries, 10, 0) + testNumberOfValuesEqualOrBelow(dataSeries, 10, 0) + testSum(dataSeries, 0) + testAverage(dataSeries, 0) + testMedian(dataSeries, 0) +}) + +test('Series behaviour with a single pushed value. Series = [9]', () => { + const dataSeries = createSeries(3) + dataSeries.push(9) + testLength(dataSeries, 1) + testatSeriesBegin(dataSeries, 9) + testAtSeriesEnd(dataSeries, 9) + testNumberOfValuesAbove(dataSeries, 0, 1) + testNumberOfValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfValuesAbove(dataSeries, 10, 0) + testNumberOfValuesEqualOrBelow(dataSeries, 10, 1) + testSum(dataSeries, 9) + testAverage(dataSeries, 9) + testMedian(dataSeries, 9) +}) + +test('Series behaviour with a second pushed value. Series = [9, 3]', () => { + const dataSeries = createSeries(3) + dataSeries.push(9) + dataSeries.push(3) + testLength(dataSeries, 2) + testatSeriesBegin(dataSeries, 9) + testAtSeriesEnd(dataSeries, 3) + testNumberOfValuesAbove(dataSeries, 0, 2) + testNumberOfValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfValuesAbove(dataSeries, 10, 0) + testNumberOfValuesEqualOrBelow(dataSeries, 10, 2) + testSum(dataSeries, 12) + testAverage(dataSeries, 6) + testMedian(dataSeries, 6) +}) + +test('Series behaviour with a third pushed value. Series = [9, 3, 6]', () => { + const dataSeries = createSeries(3) + dataSeries.push(9) + dataSeries.push(3) + dataSeries.push(6) + testLength(dataSeries, 3) + testatSeriesBegin(dataSeries, 9) + testAtSeriesEnd(dataSeries, 6) + testNumberOfValuesAbove(dataSeries, 0, 3) + testNumberOfValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfValuesAbove(dataSeries, 10, 0) + testNumberOfValuesEqualOrBelow(dataSeries, 10, 3) + testSum(dataSeries, 18) + testAverage(dataSeries, 6) + testMedian(dataSeries, 6) +}) + +test('Series behaviour with a fourth pushed value. Series = [3, 6, 12]', () => { + const dataSeries = createSeries(3) + dataSeries.push(9) + dataSeries.push(3) + dataSeries.push(6) + dataSeries.push(12) + testLength(dataSeries, 3) + testatSeriesBegin(dataSeries, 3) + testAtSeriesEnd(dataSeries, 12) + testNumberOfValuesAbove(dataSeries, 0, 3) + testNumberOfValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfValuesAbove(dataSeries, 10, 1) + testNumberOfValuesEqualOrBelow(dataSeries, 10, 2) + testSum(dataSeries, 21) + testAverage(dataSeries, 7) + testMedian(dataSeries, 6) +}) + +test('Series behaviour with a fifth pushed value. Series = [6, 12, -3]', () => { + const dataSeries = createSeries(3) + dataSeries.push(9) + dataSeries.push(3) + dataSeries.push(6) + dataSeries.push(12) + dataSeries.push(-3) + testLength(dataSeries, 3) + testatSeriesBegin(dataSeries, 6) + testAtSeriesEnd(dataSeries, -3) + testNumberOfValuesAbove(dataSeries, 0, 2) + testNumberOfValuesEqualOrBelow(dataSeries, 0, 1) + testNumberOfValuesAbove(dataSeries, 10, 1) + testNumberOfValuesEqualOrBelow(dataSeries, 10, 2) + testSum(dataSeries, 15) + testAverage(dataSeries, 5) + testMedian(dataSeries, 6) +}) + +test('Series behaviour with a five pushed values followed by a reset, Series = []', () => { + const dataSeries = createSeries(3) + dataSeries.push(9) + dataSeries.push(3) + dataSeries.push(6) + dataSeries.push(12) + dataSeries.push(-3) + dataSeries.reset() + testLength(dataSeries, 0) + testatSeriesBegin(dataSeries, 0) + testAtSeriesEnd(dataSeries, 0) + testNumberOfValuesAbove(dataSeries, 0, 0) + testNumberOfValuesEqualOrBelow(dataSeries, 0, 0) + testNumberOfValuesAbove(dataSeries, 10, 0) + testNumberOfValuesEqualOrBelow(dataSeries, 10, 0) + testSum(dataSeries, 0) + testAverage(dataSeries, 0) + testMedian(dataSeries, 0) +}) + +function testLength (series, expectedValue) { + assert.ok(series.length() === expectedValue, `Expected length should be ${expectedValue}, encountered ${series.length()}`) +} + +function testatSeriesBegin (series, expectedValue) { + assert.ok(series.atSeriesBegin() === expectedValue, `Expected atSeriesBegin to be ${expectedValue}, encountered ${series.atSeriesBegin()}`) +} + +function testAtSeriesEnd (series, expectedValue) { + assert.ok(series.atSeriesEnd() === expectedValue, `Expected atSeriesEnd to be ${expectedValue}, encountered ${series.atSeriesEnd()}`) +} + +function testNumberOfValuesAbove (series, cutoff, expectedValue) { + assert.ok(series.numberOfValuesAbove(cutoff) === expectedValue, `Expected numberOfValuesAbove(${cutoff}) to be ${expectedValue}, encountered ${series.numberOfValuesAbove(cutoff)}`) +} + +function testNumberOfValuesEqualOrBelow (series, cutoff, expectedValue) { + assert.ok(series.numberOfValuesEqualOrBelow(cutoff) === expectedValue, `Expected numberOfValuesEqualOrBelow(${cutoff}) to be ${expectedValue}, encountered ${series.numberOfValuesEqualOrBelow(cutoff)}`) +} + +function testSum (series, expectedValue) { + assert.ok(series.sum() === expectedValue, `Expected sum to be ${expectedValue}, encountered ${series.sum()}`) +} + +function testAverage (series, expectedValue) { + assert.ok(series.average() === expectedValue, `Expected average to be ${expectedValue}, encountered ${series.average()}`) +} + +function testMedian (series, expectedValue) { + assert.ok(series.median() === expectedValue, `Expected median to be ${expectedValue}, encountered ${series.median()}`) +} + +test.run() diff --git a/app/engine/utils/StreamFilter.js b/app/engine/utils/StreamFilter.js new file mode 100644 index 0000000000..f22aea991b --- /dev/null +++ b/app/engine/utils/StreamFilter.js @@ -0,0 +1,56 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + This keeps an array, which we can ask for an moving average + + Please note: The array contains maxLength values +*/ + +import { createSeries } from './Series.js' + +function createStreamFilter (maxLength, defaultValue) { + const dataPoints = createSeries(maxLength) + let lastRawDatapoint = defaultValue + let cleanDatapoint = defaultValue + + function push (dataPoint) { + lastRawDatapoint = dataPoint + dataPoints.push(dataPoint) + cleanDatapoint = dataPoints.median() + } + + function raw () { + return lastRawDatapoint + } + + function clean () { + if (dataPoints.length() > 0) { + // The series contains sufficient values to be valid + return cleanDatapoint + } else { + // The array isn't sufficiently filled + return defaultValue + } + } + + function reliable () { + return dataPoints.length() > 0 + } + + function reset () { + dataPoints.reset() + lastRawDatapoint = defaultValue + cleanDatapoint = defaultValue + } + + return { + push, + raw, + clean, + reliable, + reset + } +} + +export { createStreamFilter } diff --git a/app/engine/utils/StreamFilter.test.js b/app/engine/utils/StreamFilter.test.js new file mode 100644 index 0000000000..05c0d4fea4 --- /dev/null +++ b/app/engine/utils/StreamFilter.test.js @@ -0,0 +1,69 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor +*/ +import { test } from 'uvu' +import * as assert from 'uvu/assert' + +import { createStreamFilter } from './StreamFilter.js' + +test('average should be initValue on empty dataset', () => { + const datapoints = createStreamFilter(3, 5.5) + testReliable(datapoints, false) + testClean(datapoints, 5.5) +}) + +test('an averager of length 1 should return the last added value', () => { + const datapoints = createStreamFilter(3, 5.5) + datapoints.push(9) + testReliable(datapoints, true) + testClean(datapoints, 9) +}) + +test('a median of length 2 should return average of the 2 added elements', () => { + const datapoints = createStreamFilter(3, 5.5) + datapoints.push(9) + datapoints.push(4) + testReliable(datapoints, true) + testClean(datapoints, 6.5) +}) + +test('a median of three values should deliver the middle element', () => { + const datapoints = createStreamFilter(3, 5.5) + datapoints.push(9) + datapoints.push(4) + datapoints.push(3) + testReliable(datapoints, true) + testClean(datapoints, 4) +}) + +test('elements outside of range should not be considered', () => { + const datapoints = createStreamFilter(3, 5.5) + datapoints.push(9) + datapoints.push(4) + datapoints.push(3) + datapoints.push(1) + testReliable(datapoints, true) + testClean(datapoints, 3) +}) + +test('elements outside of range should not be considered', () => { + const datapoints = createStreamFilter(3, 5.5) + datapoints.push(9) + datapoints.push(4) + datapoints.push(3) + datapoints.push(1) + datapoints.reset() + testReliable(datapoints, false) + testClean(datapoints, 5.5) +}) + +function testClean (series, expectedValue) { + assert.ok(series.clean() === expectedValue, `Expected clean datapoint should be ${expectedValue}, encountered ${series.clean()}`) +} + +function testReliable (series, expectedValue) { + assert.ok(series.reliable() === expectedValue, `Expected clean datapoint should be ${expectedValue}, encountered ${series.reliable()}`) +} + +test.run() diff --git a/app/engine/utils/WeighedSeries.js b/app/engine/utils/WeighedSeries.js new file mode 100644 index 0000000000..4bbbe5c0b6 --- /dev/null +++ b/app/engine/utils/WeighedSeries.js @@ -0,0 +1,114 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + This creates a series with a maximum number of values + It allows for determining the Average, Median, Number of Positive, number of Negative +*/ + +import { createSeries } from './Series.js' + +function createWeighedSeries (maxSeriesLength, defaultValue) { + const dataArray = createSeries(maxSeriesLength) + const weightArray = createSeries(maxSeriesLength) + const weightedArray = createSeries(maxSeriesLength) + + function push (value, weight) { + dataArray.push(value) + weightArray.push(weight) + weightedArray.push(value * weight) + } + + function length () { + return dataArray.length() + } + + function atSeriesBegin () { + return dataArray.atSeriesBegin() + } + + function atSeriesEnd () { + return dataArray.atSeriesEnd() + } + + function get (position) { + return dataArray.get(position) + } + + function numberOfValuesAbove (testedValue) { + return dataArray.numberOfValuesAbove(testedValue) + } + + function numberOfValuesEqualOrBelow (testedValue) { + return dataArray.numberOfValuesEqualOrBelow(testedValue) + } + + function sum () { + return dataArray.sum() + } + + function average () { + if (dataArray.length() > 0) { + // The series contains sufficient values to be valid + return dataArray.average() + } else { + // The array isn't sufficiently filled + return defaultValue + } + } + + function weighedAverage () { + if (dataArray.length() > 0 && weightArray.sum() !== 0) { + return (weightedArray.sum() / weightArray.sum()) + } else { + return defaultValue + } + } + + function minimum () { + return dataArray.minimum() + } + + function maximum () { + return dataArray.maximum() + } + + function median () { + return dataArray.median() + } + + function reliable () { + return dataArray.length() > 0 + } + + function series () { + return dataArray.series() + } + + function reset () { + dataArray.reset() + weightArray.reset() + weightedArray.reset() + } + + return { + push, + length, + atSeriesBegin, + atSeriesEnd, + get, + numberOfValuesAbove, + numberOfValuesEqualOrBelow, + sum, + average, + weighedAverage, + minimum, + maximum, + median, + series, + reliable, + reset + } +} + +export { createWeighedSeries } diff --git a/app/engine/utils/curveMetrics.js b/app/engine/utils/curveMetrics.js new file mode 100644 index 0000000000..1236424011 --- /dev/null +++ b/app/engine/utils/curveMetrics.js @@ -0,0 +1,73 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + This keeps an array, for all in-stroke metrics +*/ +import { createSeries } from './Series.js' + +function createCurveMetrics () { + const _curve = createSeries() + let _max = 0 + let totalInputXTime = 0 + let totaltime = 0 + + function push (deltaTime, inputValue) { + // add the new dataPoint to the array, we have to move datapoints starting at the oldst ones + if (inputValue > 0) { + _curve.push(inputValue) + _max = Math.max(_max, inputValue) + totalInputXTime += deltaTime * inputValue + totaltime += deltaTime + } else { + // Let's skip negative and zero values with 0's as they are not relevant + _curve.push(0) + } + } + + function peak () { + if (_max > 0) { + return _max + } else { + return 0 + } + } + + function average () { + if (totaltime > 0 && totalInputXTime > 0) { + return totalInputXTime / totaltime + } else { + return 0 + } + } + + function curve () { + if (_curve.length() > 0) { + return Array.from(_curve.series()) + } else { + return [] + } + } + + function length () { + return _curve.length() + } + + function reset () { + _curve.reset() + _max = 0 + totalInputXTime = 0 + totaltime = 0 + } + + return { + push, + peak, + average, + curve, + length, + reset + } +} + +export { createCurveMetrics } diff --git a/app/gpio/GpioTimerService.js b/app/gpio/GpioTimerService.js index e73646c5ba..093a89529e 100644 --- a/app/gpio/GpioTimerService.js +++ b/app/gpio/GpioTimerService.js @@ -7,7 +7,7 @@ possible to real time. */ import process from 'process' -import { Gpio } from 'onoff' +import pigpio from 'pigpio' import os from 'os' import config from '../tools/ConfigManager.js' import log from 'loglevel' @@ -15,37 +15,57 @@ import log from 'loglevel' log.setLevel(config.loglevel.default) export function createGpioTimerService () { - if (Gpio.accessible) { - if (config.gpioHighPriority) { - // setting top (near-real-time) priority for the Gpio process, as we don't want to miss anything - log.debug('setting priority for the Gpio-service to maximum (-20)') - try { - // setting priority of current process - os.setPriority(-20) - } catch (err) { - log.debug('need root permission to set priority of Gpio-Thread') - } + // Import the settings from the settings file + const triggeredFlank = config.gpioTriggeredFlank + const pollingInterval = config.gpioPollingInterval + const minimumPulseLength = config.gpioMinimumPulseLength + + if (config.gpioPriority) { + // setting top (near-real-time) priority for the Gpio process, as we don't want to miss anything + log.debug(`Gpio-service: Setting priority to ${config.gpioPriority}`) + try { + // setting priority of current process + os.setPriority(config.gpioPriority) + } catch (err) { + log.debug('Gpio-service: FAILED to set priority of Gpio-Thread, are root permissions granted?') } + } - // read the sensor data from one of the Gpio pins of Raspberry Pi - const sensor = new Gpio(config.gpioPin, 'in', 'rising') - // use hrtime for time measurement to get a higher time precision - let hrStartTime = process.hrtime() + const Gpio = pigpio.Gpio - // assumes that GPIO-Port 17 is set to pullup and reed is connected to GND - // therefore the value is 1 if the reed sensor is open - sensor.watch((err, value) => { - if (err) { - throw err - } - const hrDelta = process.hrtime(hrStartTime) - hrStartTime = process.hrtime() - const delta = hrDelta[0] + hrDelta[1] / 1e9 - process.send(delta) + // Configure the gpio polling frequency + pigpio.configureClock(pollingInterval, pigpio.CLOCK_PCM) + + // Configure the sensor readings for one of the Gpio pins of Raspberry Pi + const sensor = new Gpio( + config.gpioPin, { + mode: Gpio.INPUT, + pullUpDown: Gpio.PUD_UP, + alert: true }) - } else { - log.info('reading from Gpio is not (yet) supported on this platform') - } -} + // Set a minumum time a level must be stable before an alert event is emitted. + sensor.glitchFilter(minimumPulseLength) + log.debug(`Gpio-service: pin number ${config.gpioPin}, polling interval ${pollingInterval} us, triggered on ${triggeredFlank} flank, minimal pulse time ${minimumPulseLength} us`) + + // set the default value + let previousTick = 0 + + // Define the alert handler + sensor.on('alert', (level, rawCurrentTick) => { + if ((triggeredFlank === 'Both') || (triggeredFlank === 'Down' && level === 0) || (triggeredFlank === 'Up' && level === 1)) { + const currentTick = (rawCurrentTick >> 0) / 1e6 + let currentDt + if (currentTick > previousTick) { + currentDt = currentTick - previousTick + } else { + // We had a rollover of the tick, so the current tick misses 4,294,967,295 us + log.debug('Gpio-service: tick rollover detected and corrected') + currentDt = (currentTick + 4294.967295) - previousTick + } + previousTick = currentTick + process.send(currentDt) + } + }) +} createGpioTimerService() diff --git a/app/peripherals/PeripheralConstants.js b/app/peripherals/PeripheralConstants.js new file mode 100644 index 0000000000..d408243838 --- /dev/null +++ b/app/peripherals/PeripheralConstants.js @@ -0,0 +1,16 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Some PM5 specific constants +*/ +export const PeripheralConstants = { + serial: '431234567', + model: 'PM5', + name: 'PM5 431234567 Row', + hardwareRevision: '907', + // See https://www.concept2.com/service/monitors/pm5/firmware for available versions + // please note: hardware versions exclude a software version, and thus might confuse the client + firmwareRevision: '210', + manufacturer: 'Concept2' +} diff --git a/app/peripherals/PeripheralManager.js b/app/peripherals/PeripheralManager.js new file mode 100644 index 0000000000..df793d7e4f --- /dev/null +++ b/app/peripherals/PeripheralManager.js @@ -0,0 +1,296 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + This manager creates the different Bluetooth Low Energy (BLE) Peripherals and allows + switching between them +*/ +import config from '../tools/ConfigManager.js' +import { createFtmsPeripheral } from './ble/FtmsPeripheral.js' +import { createPm5Peripheral } from './ble/Pm5Peripheral.js' +import log from 'loglevel' +import EventEmitter from 'node:events' +import { createCpsPeripheral } from './ble/CpsPeripheral.js' +import { createCscPeripheral } from './ble/CscPeripheral.js' +import AntManager from './ant/AntManager.js' +import { createAntHrmPeripheral } from './ant/HrmPeripheral.js' +import { createBleHrmPeripheral } from './ble/HrmPeripheral.js' +import { createFEPeripheral } from './ant/FEPeripheral.js' + +const bleModes = ['FTMS', 'FTMSBIKE', 'PM5', 'CSC', 'CPS', 'OFF'] +const antModes = ['FE', 'OFF'] +const hrmModes = ['ANT', 'BLE', 'OFF'] +function createPeripheralManager () { + const emitter = new EventEmitter() + let _antManager + let blePeripheral + let bleMode + + let antPeripheral + let antMode + + let hrmPeripheral + let hrmMode + + let isPeripheralChangeInProgress = false + + setupPeripherals() + + async function setupPeripherals () { + await createBlePeripheral(config.bluetoothMode) + await createHrmPeripheral(config.heartRateMode) + await createAntPeripheral(config.antplusMode) + } + + function getBlePeripheral () { + return blePeripheral + } + + function getBlePeripheralMode () { + return bleMode + } + + function getAntPeripheral () { + return antPeripheral + } + + function getAntPeripheralMode () { + return antMode + } + + function getHrmPeripheral () { + return hrmPeripheral + } + + function getHrmPeripheralMode () { + return hrmMode + } + + function switchBlePeripheralMode (newMode) { + if (isPeripheralChangeInProgress) return + isPeripheralChangeInProgress = true + // if now mode was passed, select the next one from the list + if (newMode === undefined) { + newMode = bleModes[(bleModes.indexOf(bleMode) + 1) % bleModes.length] + } + createBlePeripheral(newMode) + isPeripheralChangeInProgress = false + } + + function notifyMetrics (type, metrics) { + if (bleMode !== 'OFF') { blePeripheral?.notifyData(type, metrics) } + if (antMode !== 'OFF') { antPeripheral?.notifyData(type, metrics) } + } + + function notifyStatus (status) { + if (bleMode !== 'OFF') { blePeripheral?.notifyStatus(status) } + if (antMode !== 'OFF') { antPeripheral?.notifyStatus(status) } + } + + async function createBlePeripheral (newMode) { + if (blePeripheral) { + await blePeripheral?.destroy() + blePeripheral = undefined + } + + switch (newMode) { + case 'PM5': + log.info('bluetooth profile: Concept2 PM5') + blePeripheral = createPm5Peripheral(controlCallback) + bleMode = 'PM5' + break + + case 'FTMSBIKE': + log.info('bluetooth profile: FTMS Indoor Bike') + blePeripheral = createFtmsPeripheral(controlCallback, { + simulateIndoorBike: true + }) + bleMode = 'FTMSBIKE' + break + + case 'CSC': + log.info('bluetooth profile: Cycling Speed and Cadence') + blePeripheral = createCscPeripheral() + bleMode = 'CSC' + break + + case 'CPS': + log.info('bluetooth profile: Cycling Power Meter') + blePeripheral = createCpsPeripheral() + bleMode = 'CPS' + break + + case 'FTMS': + log.info('bluetooth profile: FTMS Rower') + blePeripheral = createFtmsPeripheral(controlCallback, { + simulateIndoorBike: false + }) + bleMode = 'FTMS' + break + + default: + log.info('bluetooth profile: Off') + bleMode = 'OFF' + } + if (bleMode.toLocaleLowerCase() !== 'OFF'.toLocaleLowerCase()) { blePeripheral.triggerAdvertising() } + + emitter.emit('control', { + req: { + name: 'blePeripheralMode', + peripheralMode: bleMode + } + }) + } + + function switchAntPeripheralMode (newMode) { + if (isPeripheralChangeInProgress) return + isPeripheralChangeInProgress = true + if (newMode === undefined) { + newMode = antModes[(antModes.indexOf(antMode) + 1) % antModes.length] + } + createAntPeripheral(newMode) + isPeripheralChangeInProgress = false + } + + async function createAntPeripheral (newMode) { + if (antPeripheral) { + await antPeripheral?.destroy() + antPeripheral = undefined + + try { + if (_antManager && hrmMode !== 'ANT' && newMode === 'OFF') { await _antManager.closeAntStick() } + } catch (error) { + log.error(error) + return + } + } + + switch (newMode) { + case 'FE': + log.info('ant plus profile: FE') + if (!_antManager) { + _antManager = new AntManager() + } + + try { + antPeripheral = createFEPeripheral(_antManager) + antMode = 'FE' + await antPeripheral.attach() + } catch (error) { + log.error(error) + return + } + break + + default: + log.info('ant plus profile: Off') + antMode = 'OFF' + } + + emitter.emit('control', { + req: { + name: 'antPeripheralMode', + peripheralMode: antMode + } + }) + } + + function switchHrmMode (newMode) { + if (isPeripheralChangeInProgress) return + isPeripheralChangeInProgress = true + if (newMode === undefined) { + newMode = hrmModes[(hrmModes.indexOf(hrmMode) + 1) % hrmModes.length] + } + createHrmPeripheral(newMode) + isPeripheralChangeInProgress = false + } + + async function createHrmPeripheral (newMode) { + if (hrmPeripheral) { + await hrmPeripheral?.destroy() + hrmPeripheral?.removeAllListeners() + hrmPeripheral = undefined + try { + if (_antManager && newMode !== 'ANT' && antMode === 'OFF') { await _antManager.closeAntStick() } + } catch (error) { + log.error(error) + return + } + } + + switch (newMode) { + case 'ANT': + log.info('heart rate profile: ANT') + if (!_antManager) { + _antManager = new AntManager() + } + + try { + hrmPeripheral = createAntHrmPeripheral(_antManager) + hrmMode = 'ANT' + await hrmPeripheral.attach() + } catch (error) { + log.error(error) + return + } + break + + case 'BLE': + log.info('heart rate profile: BLE') + hrmPeripheral = createBleHrmPeripheral() + hrmMode = 'BLE' + break + + default: + log.info('heart rate profile: Off') + hrmMode = 'OFF' + } + + if (hrmMode.toLocaleLowerCase() !== 'OFF'.toLocaleLowerCase()) { + hrmPeripheral.on('heartRateMeasurement', (heartRateMeasurement) => { + emitter.emit('heartRateMeasurement', heartRateMeasurement) + }) + } + + emitter.emit('control', { + req: { + name: 'hrmPeripheralMode', + peripheralMode: hrmMode + } + }) + } + + function controlCallback (event) { + emitter.emit('control', event) + } + + async function shutdownAllPeripherals () { + log.debug('shutting down all peripherals') + + try { + await blePeripheral?.destroy() + await antPeripheral?.destroy() + await hrmPeripheral?.destroy() + await _antManager?.closeAntStick() + } catch (error) { + log.error('peripheral shutdown was unsuccessful, restart of Pi may required', error) + } + } + + return Object.assign(emitter, { + shutdownAllPeripherals, + getBlePeripheral, + getBlePeripheralMode, + getAntPeripheral, + getAntPeripheralMode, + getHrmPeripheral, + getHrmPeripheralMode, + switchHrmMode, + switchBlePeripheralMode, + switchAntPeripheralMode, + notifyMetrics, + notifyStatus + }) +} + +export { createPeripheralManager } diff --git a/app/peripherals/ant/AntManager.js b/app/peripherals/ant/AntManager.js new file mode 100644 index 0000000000..e6759d4b3b --- /dev/null +++ b/app/peripherals/ant/AntManager.js @@ -0,0 +1,43 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + This manager creates a module to listen to ANT+ devices. + This currently can be used to get the heart rate from ANT+ heart rate sensors. + + Requires an ANT+ USB stick, the following models might work: + - Garmin USB or USB2 ANT+ or an off-brand clone of it (ID 0x1008) + - Garmin mini ANT+ (ID 0x1009) +*/ +import log from 'loglevel' +import { AntDevice } from 'incyclist-ant-plus/lib/ant-device.js' + +export default class AntManager { + _isStickOpen = false + _stick = new AntDevice({ startupTimeout: 2000 }) + + async openAntStick () { + if (this._isStickOpen) return + if (!(await this._stick.open())) { throw (new Error('Error opening Ant Stick')) } + + log.info('ANT+ stick found') + this._isStickOpen = true + } + + async closeAntStick () { + if (!this._isStickOpen) return + + if (!(await this._stick.close())) { throw (new Error('Error closing Ant Stick')) } + + log.info('ANT+ stick is closed') + this._isStickOpen = false + } + + isStickOpen () { + return this._isStickOpen + } + + getAntStick () { + return this._stick + } +} diff --git a/app/peripherals/ant/FEPeripheral.js b/app/peripherals/ant/FEPeripheral.js new file mode 100644 index 0000000000..0a3b99cc38 --- /dev/null +++ b/app/peripherals/ant/FEPeripheral.js @@ -0,0 +1,218 @@ +'use strict' + +import log from 'loglevel' +import { Messages } from 'incyclist-ant-plus' +import { PeripheralConstants } from '../PeripheralConstants.js' + +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Creates a Bluetooth Low Energy (BLE) Peripheral with all the Services that are required for + a Cycling Speed and Cadence Profile +*/ + +function createFEPeripheral (antManager) { + const antStick = antManager.getAntStick() + const deviceType = 0x11 // Ant FE-C device + const deviceNumber = 1 + const deviceId = parseInt(PeripheralConstants.serial, 10) & 0xFFFF + const channel = 1 + const broadcastPeriod = 8192 // 8192/32768 ~4hz + const broadcastInterval = broadcastPeriod / 32768 * 1000 // millisecond + const rfChannel = 57 // 2457 MHz + let dataPageCount = 0 + let commonPageCount = 0 + let timer + + let sessionData = { + accumulatedStrokes: 0, + accumulatedDistance: 0, + accumulatedTime: 0, + accumulatedPower: 0, + cycleLinearVelocity: 0, + strokeRate: 0, + instantaneousPower: 0, + distancePerStroke: 0, + fitnessEquipmentState: fitnessEquipmentStates.inUse, + sessionStatus: 'WaitingForStart' + } + + async function attach () { + if (!antManager.isStickOpen()) { await antManager.openAntStick() } + + const messages = [ + Messages.assignChannel(channel, 'transmit'), + Messages.setDevice(channel, deviceId, deviceType, deviceNumber), + Messages.setFrequency(channel, rfChannel), + Messages.setPeriod(channel, broadcastPeriod), + Messages.openChannel(channel) + ] + + log.info(`ANT+ FE server start [deviceId=${deviceId} channel=${channel}]`) + for (const message of messages) { + antStick.write(message) + } + + timer = setInterval(onBroadcastInterval, broadcastInterval) + } + + function destroy () { + return new Promise((resolve) => { + clearInterval(timer) + log.info(`ANT+ FE server stopped [deviceId=${deviceId} channel=${channel}]`) + + const messages = [ + Messages.closeChannel(channel), + Messages.unassignChannel(channel) + ] + for (const message of messages) { + antStick.write(message) + } + resolve() + }) + } + + function onBroadcastInterval () { + dataPageCount++ + let data + + switch (true) { + case dataPageCount === 65 || dataPageCount === 66: + if (commonPageCount % 2 === 0) { // 0x50 - Common Page for Manufacturers Identification (approx twice a minute) + data = [ + channel, + 0x50, // Page 80 + 0xFF, // Reserved + 0xFF, // Reserved + parseInt(PeripheralConstants.hardwareRevision, 10) & 0xFF, // Hardware Revision + ...Messages.intToLEHexArray(40, 2), // Manufacturer ID (value 255 = Development ID, value 40 = concept2) + 0x0001 // Model Number + ] + } + if (commonPageCount % 2 === 1) { // 0x51 - Common Page for Product Information (approx twice a minute) + data = [ + channel, + 0x51, // Page 81 + 0xFF, // Reserved + parseInt(PeripheralConstants.firmwareRevision.slice(-2), 10), // SW Revision (Supplemental) + parseInt(PeripheralConstants.firmwareRevision[0], 10), // SW Version + ...Messages.intToLEHexArray(parseInt(PeripheralConstants.serial, 10), 4) // Serial Number (None) + ] + } + + if (dataPageCount === 66) { + commonPageCount++ + dataPageCount = 0 + } + break + case dataPageCount % 8 === 4: // 0x11 - General Settings Page (once a second) + case dataPageCount % 8 === 7: + data = [ + channel, + 0x11, // Page 17 + 0xFF, // Reserved + 0xFF, // Reserved + ...Messages.intToLEHexArray(sessionData.distancePerStroke, 1), // Stroke Length in 0.01 m + 0x7FFF, // Incline (Not Used) + 0x00, // Resistance (DF may be reported if conversion to the % is worked out (value in % with a resolution of 0.5%). + ...Messages.intToLEHexArray(feCapabilitiesBitField, 1) + ] + if (sessionData.sessionStatus === 'Rowing') { + log.trace(`Page 17 Data Sent. Event=${dataPageCount}. Stroke Length=${sessionData.distancePerStroke}.`) + log.trace(`Hex Stroke Length=0x${sessionData.distancePerStroke.toString(16)}.`) + } + break + case dataPageCount % 8 === 3: // 0x16 - Specific Rower Data (once a second) + case dataPageCount % 8 === 0: + data = [ + channel, + 0x16, // Page 22 + 0xFF, // Reserved + 0xFF, // Reserved + ...Messages.intToLEHexArray(sessionData.accumulatedStrokes, 1), // Stroke Count + ...Messages.intToLEHexArray(sessionData.strokeRate, 1), // Cadence / Stroke Rate + ...Messages.intToLEHexArray(sessionData.instantaneousPower, 2), // Instant Power (2 bytes) + ...Messages.intToLEHexArray((sessionData.fitnessEquipmentState + rowingCapabilitiesBitField), 1) + ] + if (sessionData.sessionStatus === 'Rowing') { + log.trace(`Page 22 Data Sent. Event=${dataPageCount}. Strokes=${sessionData.accumulatedStrokes}. Stroke Rate=${sessionData.strokeRate}. Power=${sessionData.instantaneousPower}`) + log.trace(`Hex Strokes=0x${sessionData.accumulatedStrokes.toString(16)}. Hex Stroke Rate=0x${sessionData.strokeRate.toString(16)}. Hex Power=0x${Messages.intToLEHexArray(sessionData.instantaneousPower, 2)}.`) + } + break + case dataPageCount % 4 === 2: // 0x10 - General FE Data (twice a second) + default: + data = [ + channel, + 0x10, // Page 16 + 0x16, // Rowing Machine (22) + ...Messages.intToLEHexArray(sessionData.accumulatedTime, 1), // elapsed time + ...Messages.intToLEHexArray(sessionData.accumulatedDistance, 1), // distance travelled + ...Messages.intToLEHexArray(sessionData.cycleLinearVelocity, 2), // speed in 0.001 m/s + 0xFF, // heart rate not being sent + ...Messages.intToLEHexArray((sessionData.fitnessEquipmentState + feCapabilitiesBitField), 1) + ] + if (sessionData.sessionStatus === 'Rowing') { + log.trace(`Page 16 Data Sent. Event=${dataPageCount}. Time=${sessionData.accumulatedTime}. Distance=${sessionData.accumulatedDistance}. Speed=${sessionData.cycleLinearVelocity}.`) + log.trace(`Hex Time=0x${sessionData.accumulatedTime.toString(16)}. Hex Distance=0x${sessionData.accumulatedDistance.toString(16)}. Hex Speed=0x${Messages.intToLEHexArray(sessionData.cycleLinearVelocity, 2)}.`) + } + break + } + + const message = Messages.broadcastData(data) + antStick.write(message) + } + + function notifyData (type, data) { + if (type === 'strokeFinished' || type === 'metricsUpdate') { + sessionData = { + ...sessionData, + accumulatedDistance: data.totalLinearDistance & 0xFF, + accumulatedStrokes: data.totalNumberOfStrokes & 0xFF, + accumulatedTime: Math.trunc(data.totalMovingTime * 4) & 0xFF, + cycleLinearVelocity: Math.round(data.cycleLinearVelocity * 1000), + strokeRate: Math.round(data.cycleStrokeRate) & 0xFF, + instantaneousPower: Math.round(data.cyclePower) & 0xFFFF, + distancePerStroke: Math.round(data.cycleDistance * 100), + sessionStatus: data.sessionStatus + } + } + } + + // FE does not have status characteristic + function notifyStatus (status) { + } + + return { + notifyData, + notifyStatus, + attach, + destroy + } +} + +const fitnessEquipmentStates = { + asleep: (1 << 0x04), + ready: (2 << 0x04), + inUse: (3 << 0x04), + finished: (4 << 0x04), + lapToggleBit: (8 << 0x04) +} + +const fitnessEquipmentCapabilities = { + hrDataSourceHandContactSensors: (0x03 << 0), + hrDataSourceEmSensors: (0x02 << 0), + hrDataSourceAntSensors: (0x01 << 0), + hrDataSourceInvalid: (0x00 << 0), + distanceTraveledEnabled: (0x01 << 2), + virtualSpeed: (0x01 << 3), + realSpeed: (0x00 << 3) +} + +const rowingMachineCapabilities = { + accumulatedStrokesEnabled: (0x01 << 0) +} + +const feCapabilitiesBitField = fitnessEquipmentCapabilities.hrDataSourceInvalid | fitnessEquipmentCapabilities.distanceTraveledEnabled | fitnessEquipmentCapabilities.realSpeed +const rowingCapabilitiesBitField = rowingMachineCapabilities.accumulatedStrokesEnabled + +export { createFEPeripheral } diff --git a/app/peripherals/ant/HrmPeripheral.js b/app/peripherals/ant/HrmPeripheral.js new file mode 100644 index 0000000000..8e7cff85fe --- /dev/null +++ b/app/peripherals/ant/HrmPeripheral.js @@ -0,0 +1,69 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Creates a Bluetooth Low Energy (BLE) Peripheral with all the Services that are required for + a Cycling Speed and Cadence Profile +*/ +import EventEmitter from 'node:events' +import log from 'loglevel' +import { HeartRateSensor } from 'incyclist-ant-plus' + +function createAntHrmPeripheral (antManager) { + const emitter = new EventEmitter() + const antStick = antManager.getAntStick() + const heartRateSensor = new HeartRateSensor(0) + let batteryLevel = 0 + + async function attach () { + if (!antManager.isStickOpen()) { await antManager.openAntStick() } + this.channel = await antStick.getChannel() + + this.channel.on('data', (profile, deviceID, data) => { + switch (data.BatteryStatus) { + case 'New': + batteryLevel = 100 + break + case 'Good': + batteryLevel = 80 + break + case 'Ok': + batteryLevel = 60 + break + case 'Low': + batteryLevel = 40 + break + case 'Critical': + batteryLevel = 20 + break + default: + batteryLevel = 0 + } + + if (data.BatteryLevel > 0) { + batteryLevel = data.BatteryLevel + } + + emitter.emit('heartRateMeasurement', { heartrate: data.ComputedHeartRate, batteryLevel }) + }) + + if (!(await this.channel.startSensor(heartRateSensor))) { + log.error('Could not start ANT+ heart rate sensor') + } + } + + async function destroy () { + if (!this.channel) { + log.debug('Ant Sensor does not seem to be running') + return + } + await this.channel.stopSensor(heartRateSensor) + } + + return Object.assign(emitter, { + destroy, + attach + }) +} + +export { createAntHrmPeripheral } diff --git a/app/ble/BufferBuilder.js b/app/peripherals/ble/BufferBuilder.js similarity index 73% rename from app/ble/BufferBuilder.js rename to app/peripherals/ble/BufferBuilder.js index 5ddd624d03..5aebc7f32c 100644 --- a/app/ble/BufferBuilder.js +++ b/app/peripherals/ble/BufferBuilder.js @@ -47,6 +47,21 @@ export default class BufferBuilder { this._dataArray.push(buffer) } + writeUInt32LE (value) { + const _value = value || 0 + const buffer = Buffer.alloc(4) + if (value > 0xffffffff || value < 0) { + log.warn(new RangeError(`The value of "value" is out of range. It must be >= 0 and <= ${0xffffffff}. Received ${value}`)) + } else { + try { + buffer.writeUint32LE(_value) + } catch (error) { + log.warn(error) + } + } + this._dataArray.push(buffer) + } + getBuffer () { return Buffer.concat(this._dataArray) } diff --git a/app/ble/BufferBuilder.test.js b/app/peripherals/ble/BufferBuilder.test.js similarity index 81% rename from app/ble/BufferBuilder.test.js rename to app/peripherals/ble/BufferBuilder.test.js index 186be4281b..72d7f133c5 100644 --- a/app/ble/BufferBuilder.test.js +++ b/app/peripherals/ble/BufferBuilder.test.js @@ -13,7 +13,8 @@ test('valid max UInts should produce correct buffer', () => { buffer.writeUInt8(255) buffer.writeUInt16LE(65535) buffer.writeUInt24LE(16777215) - assert.equal(buffer.getBuffer(), Buffer.from([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])) + buffer.writeUInt32LE(4294967295) + assert.equal(buffer.getBuffer(), Buffer.from([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])) }) test('valid min UInts should produce correct buffer', () => { @@ -21,7 +22,8 @@ test('valid min UInts should produce correct buffer', () => { buffer.writeUInt8(0) buffer.writeUInt16LE(0) buffer.writeUInt24LE(0) - assert.equal(buffer.getBuffer(), Buffer.from([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + buffer.writeUInt32LE(0) + assert.equal(buffer.getBuffer(), Buffer.from([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) }) test('negative UInt8 should produce 1 bit buffer of 0x0', () => { @@ -42,6 +44,12 @@ test('negative writeUInt24LE should produce 3 bit buffer of 0x0', () => { assert.equal(buffer.getBuffer(), Buffer.from([0x0, 0x0, 0x0])) }) +test('negative writeUInt32LE should produce 4 bit buffer of 0x0', () => { + const buffer = new BufferBuilder() + buffer.writeUInt32LE(-1) + assert.equal(buffer.getBuffer(), Buffer.from([0x0, 0x0, 0x0, 0x0])) +}) + test('invalid datatype value UInt16LE should produce 2 bit buffer of 0x0', () => { const buffer = new BufferBuilder() buffer.writeUInt16LE(new Map()) diff --git a/app/peripherals/ble/CpsPeripheral.js b/app/peripherals/ble/CpsPeripheral.js new file mode 100644 index 0000000000..48dc712781 --- /dev/null +++ b/app/peripherals/ble/CpsPeripheral.js @@ -0,0 +1,108 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Creates a Bluetooth Low Energy (BLE) Peripheral with all the Services that are required for + a Cycling Power Profile +*/ +import bleno from '@abandonware/bleno' +import config from '../../tools/ConfigManager.js' +import log from 'loglevel' +import CyclingPowerService from './cps/CyclingPowerMeterService.js' +import DeviceInformationService from './common/DeviceInformationService.js' +import AdvertisingDataBuilder from './common/AdvertisingDataBuilder.js' + +function createCpsPeripheral () { + const peripheralName = `${config.ftmsRowerPeripheralName} (CPS)` + const cyclingPowerService = new CyclingPowerService((event) => log.debug('CPS Control Point', event)) + + bleno.on('stateChange', (state) => { + triggerAdvertising(state) + }) + + bleno.on('advertisingStart', (error) => { + if (!error) { + bleno.setServices( + [ + cyclingPowerService, + new DeviceInformationService() + ], + (error) => { + if (error) log.error(error) + }) + } + }) + + bleno.on('accept', (clientAddress) => { + log.debug(`ble central connected: ${clientAddress}`) + bleno.updateRssi() + }) + + bleno.on('disconnect', (clientAddress) => { + log.debug(`ble central disconnected: ${clientAddress}`) + }) + + bleno.on('platform', (event) => { + log.debug('platform', event) + }) + bleno.on('addressChange', (event) => { + log.debug('addressChange', event) + }) + bleno.on('mtuChange', (event) => { + log.debug('mtuChange', event) + }) + bleno.on('advertisingStartError', (event) => { + log.debug('advertisingStartError', event) + }) + bleno.on('servicesSetError', (event) => { + log.debug('servicesSetError', event) + }) + bleno.on('rssiUpdate', (event) => { + log.debug('rssiUpdate', event) + }) + + function destroy () { + return new Promise((resolve) => { + bleno.disconnect() + bleno.removeAllListeners() + bleno.stopAdvertising(() => resolve()) + }) + } + + function triggerAdvertising (eventState) { + const activeState = eventState || bleno.state + if (activeState === 'poweredOn') { + const cpsAppearance = 1156 + const advertisingData = new AdvertisingDataBuilder([cyclingPowerService.uuid], cpsAppearance, peripheralName) + + bleno.startAdvertisingWithEIRData( + advertisingData.buildAppearanceData(), + advertisingData.buildScanData(), + (error) => { + if (error) log.error(error) + } + ) + } else { + bleno.stopAdvertising() + } + } + + function notifyData (type, data) { + if (type === 'strokeFinished' || type === 'metricsUpdate') { + cyclingPowerService.notifyData(data) + } + } + + // CPS does not have status characteristic + function notifyStatus (status) { + } + + return { + triggerAdvertising, + notifyData, + notifyStatus, + destroy + } +} + +export { createCpsPeripheral } diff --git a/app/peripherals/ble/CscPeripheral.js b/app/peripherals/ble/CscPeripheral.js new file mode 100644 index 0000000000..cc457fe93b --- /dev/null +++ b/app/peripherals/ble/CscPeripheral.js @@ -0,0 +1,108 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Creates a Bluetooth Low Energy (BLE) Peripheral with all the Services that are required for + a Cycling Speed and Cadence Profile +*/ +import bleno from '@abandonware/bleno' +import config from '../../tools/ConfigManager.js' +import log from 'loglevel' +import DeviceInformationService from './common/DeviceInformationService.js' +import CyclingSpeedCadenceService from './csc/CyclingSpeedCadenceService.js' +import AdvertisingDataBuilder from './common/AdvertisingDataBuilder.js' + +function createCscPeripheral () { + const peripheralName = `${config.ftmsRowerPeripheralName} (CSC)` + const cyclingSpeedCadenceService = new CyclingSpeedCadenceService((event) => log.debug('CSC Control Point', event)) + + bleno.on('stateChange', (state) => { + triggerAdvertising(state) + }) + + bleno.on('advertisingStart', (error) => { + if (!error) { + bleno.setServices( + [ + cyclingSpeedCadenceService, + new DeviceInformationService() + ], + (error) => { + if (error) log.error(error) + }) + } + }) + + bleno.on('accept', (clientAddress) => { + log.debug(`ble central connected: ${clientAddress}`) + bleno.updateRssi() + }) + + bleno.on('disconnect', (clientAddress) => { + log.debug(`ble central disconnected: ${clientAddress}`) + }) + + bleno.on('platform', (event) => { + log.debug('platform', event) + }) + bleno.on('addressChange', (event) => { + log.debug('addressChange', event) + }) + bleno.on('mtuChange', (event) => { + log.debug('mtuChange', event) + }) + bleno.on('advertisingStartError', (event) => { + log.debug('advertisingStartError', event) + }) + bleno.on('servicesSetError', (event) => { + log.debug('servicesSetError', event) + }) + bleno.on('rssiUpdate', (event) => { + log.debug('rssiUpdate', event) + }) + + function destroy () { + return new Promise((resolve) => { + bleno.disconnect() + bleno.removeAllListeners() + bleno.stopAdvertising(() => resolve()) + }) + } + + function triggerAdvertising (eventState) { + const activeState = eventState || bleno.state + if (activeState === 'poweredOn') { + const cscAppearance = 1157 + const advertisingData = new AdvertisingDataBuilder([cyclingSpeedCadenceService.uuid], cscAppearance, peripheralName) + + bleno.startAdvertisingWithEIRData( + advertisingData.buildAppearanceData(), + advertisingData.buildScanData(), + (error) => { + if (error) log.error(error) + } + ) + } else { + bleno.stopAdvertising() + } + } + + function notifyData (type, data) { + if (type === 'strokeFinished' || type === 'metricsUpdate') { + cyclingSpeedCadenceService.notifyData(data) + } + } + + // CSC does not have status characteristic + function notifyStatus (status) { + } + + return { + triggerAdvertising, + notifyData, + notifyStatus, + destroy + } +} + +export { createCscPeripheral } diff --git a/app/ble/FtmsPeripheral.js b/app/peripherals/ble/FtmsPeripheral.js similarity index 81% rename from app/ble/FtmsPeripheral.js rename to app/peripherals/ble/FtmsPeripheral.js index 926b8702be..53b363ec52 100644 --- a/app/ble/FtmsPeripheral.js +++ b/app/peripherals/ble/FtmsPeripheral.js @@ -13,14 +13,15 @@ */ import bleno from '@abandonware/bleno' import FitnessMachineService from './ftms/FitnessMachineService.js' -// import DeviceInformationService from './ftms/DeviceInformationService.js' -import config from '../tools/ConfigManager.js' +import config from '../../tools/ConfigManager.js' import log from 'loglevel' +import DeviceInformationService from './common/DeviceInformationService.js' +import AdvertisingDataBuilder from './common/AdvertisingDataBuilder.js' function createFtmsPeripheral (controlCallback, options) { const peripheralName = options?.simulateIndoorBike ? config.ftmsBikePeripheralName : config.ftmsRowerPeripheralName const fitnessMachineService = new FitnessMachineService(options, controlPointCallback) - // const deviceInformationService = new DeviceInformationService() + const deviceInformationService = new DeviceInformationService() bleno.on('stateChange', (state) => { triggerAdvertising(state) @@ -29,8 +30,7 @@ function createFtmsPeripheral (controlCallback, options) { bleno.on('advertisingStart', (error) => { if (!error) { bleno.setServices( - // [fitnessMachineService, deviceInformationService], - [fitnessMachineService], + [fitnessMachineService, deviceInformationService], (error) => { if (error) log.error(error) }) @@ -78,17 +78,20 @@ function createFtmsPeripheral (controlCallback, options) { return new Promise((resolve) => { bleno.disconnect() bleno.removeAllListeners() - bleno.stopAdvertising(resolve) + bleno.stopAdvertising(() => resolve()) }) } function triggerAdvertising (eventState) { const activeState = eventState || bleno.state if (activeState === 'poweredOn') { - bleno.startAdvertising( - peripheralName, - // [fitnessMachineService.uuid, deviceInformationService.uuid], - [fitnessMachineService.uuid], + const advertisingBuilder = new AdvertisingDataBuilder([fitnessMachineService.uuid]) + advertisingBuilder.setShortName(peripheralName) + advertisingBuilder.setLongName(peripheralName) + + bleno.startAdvertisingWithEIRData( + advertisingBuilder.buildAppearanceData(), + advertisingBuilder.buildScanData(), (error) => { if (error) log.error(error) } diff --git a/app/peripherals/ble/HrmPeripheral.js b/app/peripherals/ble/HrmPeripheral.js new file mode 100644 index 0000000000..cd3de2767b --- /dev/null +++ b/app/peripherals/ble/HrmPeripheral.js @@ -0,0 +1,33 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Starts the central manager in a forked thread since noble does not like + to run in the same thread as bleno +*/ +import EventEmitter from 'node:events' +import child_process from 'child_process' + +function createBleHrmPeripheral () { + const emitter = new EventEmitter() + + const bleHrmProcess = child_process.fork('./app/peripherals/ble/hrm/HrmService.js') + + bleHrmProcess.on('message', (heartRateMeasurement) => { + emitter.emit('heartRateMeasurement', heartRateMeasurement) + }) + + function destroy () { + return new Promise(resolve => { + bleHrmProcess.kill() + bleHrmProcess.removeAllListeners() + resolve() + }) + } + + return Object.assign(emitter, { + destroy + }) +} + +export { createBleHrmPeripheral } diff --git a/app/ble/Pm5Peripheral.js b/app/peripherals/ble/Pm5Peripheral.js similarity index 95% rename from app/ble/Pm5Peripheral.js rename to app/peripherals/ble/Pm5Peripheral.js index 4e905198a6..d9050647dc 100644 --- a/app/ble/Pm5Peripheral.js +++ b/app/peripherals/ble/Pm5Peripheral.js @@ -8,7 +8,7 @@ see: https://www.concept2.co.uk/files/pdf/us/monitors/PM5_BluetoothSmartInterfaceDefinition.pdf */ import bleno from '@abandonware/bleno' -import { constants } from './pm5/Pm5Constants.js' +import { pm5Constants } from './pm5/Pm5Constants.js' import DeviceInformationService from './pm5/DeviceInformationService.js' import GapService from './pm5/GapService.js' import log from 'loglevel' @@ -16,7 +16,7 @@ import Pm5ControlService from './pm5/Pm5ControlService.js' import Pm5RowingService from './pm5/Pm5RowingService.js' function createPm5Peripheral (controlCallback, options) { - const peripheralName = constants.name + const peripheralName = pm5Constants.name const deviceInformationService = new DeviceInformationService() const gapService = new GapService() const controlService = new Pm5ControlService() @@ -68,7 +68,7 @@ function createPm5Peripheral (controlCallback, options) { return new Promise((resolve) => { bleno.disconnect() bleno.removeAllListeners() - bleno.stopAdvertising(resolve) + bleno.stopAdvertising(() => resolve()) }) } diff --git a/app/peripherals/ble/common/AdvertisingDataBuilder.js b/app/peripherals/ble/common/AdvertisingDataBuilder.js new file mode 100644 index 0000000000..ba3dabf412 --- /dev/null +++ b/app/peripherals/ble/common/AdvertisingDataBuilder.js @@ -0,0 +1,133 @@ +'use strict' + +export default class AdvertisingDataBuilder { + constructor (serviceUuids, appearance, longName, shortName) { + this.shortName = shortName || longName || 'ORM' + this.longName = longName || 'OpenRowingMonitor' + this.serviceUuids = serviceUuids || [] + this.appearance = appearance + } + + setLongName (name) { + this.longName = name + } + + setShortName (name) { + this.shortName = name + } + + addServiceUuid (serviceUuid) { + this.serviceUuids.push(serviceUuid) + } + + setAppearance (appearance) { + this.appearance = appearance + } + + buildScanData () { + let scanDataLength = 0 + scanDataLength += 2 + this.longName.length + const scanData = Buffer.alloc(scanDataLength) + + const nameBuffer = Buffer.from(this.longName) + + scanData.writeUInt8(1 + nameBuffer.length, 0) + scanData.writeUInt8(0x08, 1) + nameBuffer.copy(scanData, 2) + + return scanData + } + + buildAppearanceData () { + let advertisementDataLength = 3 + + const serviceUuids16bit = [] + const serviceUuids128bit = [] + let i = 0 + + if (this.serviceUuids.length) { + for (i = 0; i < this.serviceUuids.length; i++) { + const serviceUuid = Buffer.from(this.serviceUuids[i].match(/.{1,2}/g).reverse().join(''), 'hex') + + if (serviceUuid.length === 2) { + serviceUuids16bit.push(serviceUuid) + } else if (serviceUuid.length === 16) { + serviceUuids128bit.push(serviceUuid) + } + } + } + + if (serviceUuids16bit.length) { + advertisementDataLength += 2 + 2 * serviceUuids16bit.length + } + + if (serviceUuids128bit.length) { + advertisementDataLength += 2 + 16 * serviceUuids128bit.length + } + + if (this.appearance) { + advertisementDataLength += 4 + } + + let name = this.shortName + + if (advertisementDataLength + 2 + name.length > 31) { + const remainingDataLength = 31 - advertisementDataLength - 2 + name = name.substring(0, remainingDataLength) + } + advertisementDataLength += 2 + name.length + + const advertisementData = Buffer.alloc(advertisementDataLength) + + // flags + advertisementData.writeUInt8(2, 0) + advertisementData.writeUInt8(0x01, 1) + advertisementData.writeUInt8(0x06, 2) + + let advertisementDataOffset = 3 + + if (this.appearance) { + advertisementData.writeUInt8(3, advertisementDataOffset) + advertisementDataOffset++ + advertisementData.writeUInt8(0x19, advertisementDataOffset) + advertisementDataOffset++ + advertisementData.writeUInt16LE(this.appearance, advertisementDataOffset) + advertisementDataOffset += 2 + } + + advertisementData.writeUInt8(name.length + 1, advertisementDataOffset) + advertisementDataOffset++ + advertisementData.writeUInt8(0x08, advertisementDataOffset) + advertisementDataOffset++ + Buffer.from(name).copy(advertisementData, advertisementDataOffset) + advertisementDataOffset += name.length + + if (serviceUuids16bit.length) { + advertisementData.writeUInt8(1 + 2 * serviceUuids16bit.length, advertisementDataOffset) + advertisementDataOffset++ + + advertisementData.writeUInt8(0x03, advertisementDataOffset) + advertisementDataOffset++ + + for (i = 0; i < serviceUuids16bit.length; i++) { + serviceUuids16bit[i].copy(advertisementData, advertisementDataOffset) + advertisementDataOffset += serviceUuids16bit[i].length + } + } + + if (serviceUuids128bit.length) { + advertisementData.writeUInt8(1 + 16 * serviceUuids128bit.length, advertisementDataOffset) + advertisementDataOffset++ + + advertisementData.writeUInt8(0x06, advertisementDataOffset) + advertisementDataOffset++ + + for (i = 0; i < serviceUuids128bit.length; i++) { + serviceUuids128bit[i].copy(advertisementData, advertisementDataOffset) + advertisementDataOffset += serviceUuids128bit[i].length + } + } + + return advertisementData + } +} diff --git a/app/peripherals/ble/common/AdvertisingDataBuilder.test.js b/app/peripherals/ble/common/AdvertisingDataBuilder.test.js new file mode 100644 index 0000000000..8fbc991f75 --- /dev/null +++ b/app/peripherals/ble/common/AdvertisingDataBuilder.test.js @@ -0,0 +1,117 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor +*/ +import { test } from 'uvu' +import * as assert from 'uvu/assert' +import log from 'loglevel' +import AdvertisingDataBuilder from './AdvertisingDataBuilder.js' +log.setLevel(log.levels.SILENT) + +test('empty constructor should create default values', () => { + // arrange + const advertisementDataBuilder = new AdvertisingDataBuilder() + + // act + + // assert + assert.type(advertisementDataBuilder.appearance, 'undefined') + assert.equal(advertisementDataBuilder.longName, 'OpenRowingMonitor') + assert.equal(advertisementDataBuilder.shortName, 'ORM', 'if longName is not defined short name should be ORM') + assert.equal(advertisementDataBuilder.serviceUuids.length, 0) +}) + +test('should use long name as short name if latter is not set', () => { + // arrange + const advertisementDataBuilder = new AdvertisingDataBuilder([], undefined, 'testLongName') + + // act + + // assert + assert.equal(advertisementDataBuilder.shortName, advertisementDataBuilder.longName) +}) + +test('should be able to set long name', () => { + // arrange + const advertisementDataBuilder = new AdvertisingDataBuilder() + const name = 'longNameTest' + // act + advertisementDataBuilder.setLongName(name) + + // assert + assert.equal(advertisementDataBuilder.longName, name) +}) + +test('should be able to set short name', () => { + // arrange + const advertisementDataBuilder = new AdvertisingDataBuilder() + + const name = 'shortNameTest' + // act + advertisementDataBuilder.setShortName(name) + + // assert + assert.equal(advertisementDataBuilder.shortName, name) +}) + +test('should be able to set appearance field', () => { + // arrange + const advertisementDataBuilder = new AdvertisingDataBuilder() + + const appearance = 1157 + // act + advertisementDataBuilder.setAppearance(appearance) + + // assert + assert.equal(advertisementDataBuilder.appearance, appearance) +}) + +test('should be able to add service UUID', () => { + // arrange + const advertisementDataBuilder = new AdvertisingDataBuilder() + + // act + advertisementDataBuilder.addServiceUuid('1800') + advertisementDataBuilder.addServiceUuid('1801') + + // assert + assert.equal(advertisementDataBuilder.serviceUuids.length, 2) +}) + +test('should add long name to scan data', () => { + // arrange + const name = 'testLongName' + const advertisementDataBuilder = new AdvertisingDataBuilder(['1800'], undefined, name, 'short') + + // act + const scanData = advertisementDataBuilder.buildScanData() + + // assert + assert.equal(scanData.length, name.length + 2) +}) + +test('should produce correct byte array for advertising data', () => { + // arrange + const advertisementDataBuilder = new AdvertisingDataBuilder(['1816'], 1156, 'ORM') + + // act + const advertisementData = advertisementDataBuilder.buildAppearanceData() + // assert + assert.equal([...advertisementData], [2, 1, 6, 3, 25, 132, 4, 4, 8, 79, 82, 77, 3, 3, 22, 24] + ) +}) + +test('should trim short name if advertising data is longer than 31 byte', () => { + // arrange + const advertisementDataBuilder = new AdvertisingDataBuilder(['1816'], 1156, 'OpenRowingMonitor CSC') + + // act + const advertisementData = advertisementDataBuilder.buildAppearanceData() + + // assert + assert.equal(advertisementData.length, 31) + assert.equal([...advertisementData], [2, 1, 6, 3, 25, 132, 4, 19, 8, 79, 112, 101, 110, 82, 111, 119, 105, 110, 103, 77, 111, 110, 105, 116, 111, 114, 32, 3, 3, 22, 24]) + assert.match(advertisementData.toString(), /OpenRowingMonitor/) +}) + +test.run() diff --git a/app/peripherals/ble/common/CommonOpCodes.js b/app/peripherals/ble/common/CommonOpCodes.js new file mode 100644 index 0000000000..2c8d16e15d --- /dev/null +++ b/app/peripherals/ble/common/CommonOpCodes.js @@ -0,0 +1,10 @@ +'use-strict' + +export const ResultOpCode = { + reserved: 0x00, + success: 0x01, + opCodeNotSupported: 0x02, + invalidParameter: 0x03, + operationFailed: 0x04, + controlNotPermitted: 0x05 +} diff --git a/app/peripherals/ble/common/DeviceInformationService.js b/app/peripherals/ble/common/DeviceInformationService.js new file mode 100644 index 0000000000..c0b2daa3bf --- /dev/null +++ b/app/peripherals/ble/common/DeviceInformationService.js @@ -0,0 +1,24 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + todo: Could provide some info on the device here, maybe OS, Node version etc... +*/ +import bleno from '@abandonware/bleno' +import { PeripheralConstants } from '../../PeripheralConstants.js' +import StaticReadCharacteristic from './StaticReadCharacteristic.js' + +export default class DeviceInformationService extends bleno.PrimaryService { + constructor () { + super({ + // uuid of 'Device Information Service' + uuid: '180a', + characteristics: [ + new StaticReadCharacteristic('2A24', 'Model Number', PeripheralConstants.model), + new StaticReadCharacteristic('2A25', 'Serial Number', PeripheralConstants.serial), + new StaticReadCharacteristic('2A28', 'Software Revision', PeripheralConstants.firmwareRevision), + new StaticReadCharacteristic('2A29', 'Manufacturer Name', PeripheralConstants.manufacturer) + ] + }) + } +} diff --git a/app/peripherals/ble/common/SensorLocation.js b/app/peripherals/ble/common/SensorLocation.js new file mode 100644 index 0000000000..9a86a15ed6 --- /dev/null +++ b/app/peripherals/ble/common/SensorLocation.js @@ -0,0 +1,30 @@ +'use strict' + +import BufferBuilder from '../BufferBuilder.js' + +export const sensorLocations = +{ + other: 0, + topOfShoe: 1, + inShoe: 2, + hip: 3, + frontWheel: 4, + leftCrank: 5, + rightCrank: 6, + leftPedal: 7, + rightPedal: 8, + frontHub: 9, + rearDropout: 10, + chainstay: 11, + rearWheel: 12, + rearHub: 13, + chest: 14, + spider: 15, + chainRing: 16 +} + +export const SensorLocationAsBuffer = () => { + const sensorLocationBuffer = new BufferBuilder() + sensorLocationBuffer.writeUInt8(sensorLocations.other) + return sensorLocationBuffer.getBuffer() +} diff --git a/app/ble/pm5/characteristic/ValueReadCharacteristic.js b/app/peripherals/ble/common/StaticNotifyCharacteristic.js similarity index 57% rename from app/ble/pm5/characteristic/ValueReadCharacteristic.js rename to app/peripherals/ble/common/StaticNotifyCharacteristic.js index 7797cd109f..37b6181874 100644 --- a/app/ble/pm5/characteristic/ValueReadCharacteristic.js +++ b/app/peripherals/ble/common/StaticNotifyCharacteristic.js @@ -1,39 +1,40 @@ 'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - A simple Characteristic that gives read and notify access to a static value - Currently also used as placeholder on a lot of characteristics that are not yet implemented properly -*/ import bleno from '@abandonware/bleno' import log from 'loglevel' -export default class ValueReadCharacteristic extends bleno.Characteristic { - constructor (uuid, value, description) { +export default class StaticNotifyCharacteristic extends bleno.Characteristic { + constructor (uuid, description, value, addRead = false) { super({ uuid, - properties: ['read', 'notify'], - value: null + properties: addRead ? ['read', 'notify'] : ['notify'], + value: null, + descriptors: [ + new bleno.Descriptor({ + uuid: '2901', + value: description + }) + ] }) - this.uuid = uuid - this._value = Buffer.isBuffer(value) ? value : Buffer.from(value) + this._uuid = uuid this._description = description + this._value = Buffer.isBuffer(value) ? value : Buffer.from(value) this._updateValueCallback = null } onReadRequest (offset, callback) { - log.debug(`ValueReadRequest: ${this._description ? this._description : this.uuid}`) + log.debug(`ValueReadRequest: ${this._description ? this._description : this._uuid}`) callback(this.RESULT_SUCCESS, this._value.slice(offset, this._value.length)) } onSubscribe (maxValueSize, updateValueCallback) { - log.debug(`characteristic ${this._description ? this._description : this.uuid} - central subscribed with maxSize: ${maxValueSize}`) + log.debug(`characteristic ${this._description ? this._description : this._uuid} - central subscribed with maxSize: ${maxValueSize}`) this._updateValueCallback = updateValueCallback return this.RESULT_SUCCESS } onUnsubscribe () { - log.debug(`characteristic ${this._description ? this._description : this.uuid} - central unsubscribed`) + log.debug(`characteristic ${this._description ? this._description : this._uuid} - central unsubscribed`) this._updateValueCallback = null return this.RESULT_UNLIKELY_ERROR } diff --git a/app/peripherals/ble/common/StaticReadCharacteristic.js b/app/peripherals/ble/common/StaticReadCharacteristic.js new file mode 100644 index 0000000000..71bfbb8248 --- /dev/null +++ b/app/peripherals/ble/common/StaticReadCharacteristic.js @@ -0,0 +1,36 @@ +'use strict' + +import bleno from '@abandonware/bleno' +import log from 'loglevel' + +export default class StaticReadCharacteristic extends bleno.Characteristic { + constructor (uuid, description, value, addNotify = false) { + super({ + uuid, + properties: addNotify ? ['read', 'notify'] : ['read'], + value: Buffer.isBuffer(value) ? value : Buffer.from(value), + descriptors: [ + new bleno.Descriptor({ + uuid: '2901', + value: description + }) + ] + }) + this._uuid = uuid + this._description = description + this._value = Buffer.isBuffer(value) ? value : Buffer.from(value) + this._updateValueCallback = null + } + + onSubscribe (maxValueSize, updateValueCallback) { + log.debug(`characteristic ${this._description ? this._description : this._uuid} - central subscribed with maxSize: ${maxValueSize}`) + this._updateValueCallback = updateValueCallback + return this.RESULT_SUCCESS + } + + onUnsubscribe () { + log.debug(`characteristic ${this._description ? this._description : this._uuid} - central unsubscribed`) + this._updateValueCallback = null + return this.RESULT_UNLIKELY_ERROR + } +} diff --git a/app/peripherals/ble/cps/CpsControlPointCharacteristic.js b/app/peripherals/ble/cps/CpsControlPointCharacteristic.js new file mode 100644 index 0000000000..6b1283d625 --- /dev/null +++ b/app/peripherals/ble/cps/CpsControlPointCharacteristic.js @@ -0,0 +1,29 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + The connected Central can remotely control some parameters or our rowing monitor via this Control Point + + But for our use case proper implementation is not necessary (its mere existence with an empty handler suffice) +*/ +import bleno from '@abandonware/bleno' + +export default class CyclingPowerControlPointCharacteristic extends bleno.Characteristic { + constructor (controlPointCallback) { + super({ + // Cycling Power Meter Control Point + uuid: '2A66', + value: null, + properties: ['indicate', 'write'] + }) + + this.controlled = false + if (!controlPointCallback) { throw new Error('controlPointCallback required') } + this.controlPointCallback = controlPointCallback + } + + // Central sends a command to the Control Point + // No need to handle any request to have this working + onWriteRequest (data, offset, withoutResponse, callback) { + } +} diff --git a/app/peripherals/ble/cps/CpsMeasurementCharacteristic.js b/app/peripherals/ble/cps/CpsMeasurementCharacteristic.js new file mode 100644 index 0000000000..c87fcece50 --- /dev/null +++ b/app/peripherals/ble/cps/CpsMeasurementCharacteristic.js @@ -0,0 +1,95 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor +*/ +import bleno from '@abandonware/bleno' +import log from 'loglevel' +import BufferBuilder from '../BufferBuilder.js' + +export const cpsMeasurementFeaturesFlags = { + pedalPowerBalancePresent: (0x01 << 0), + pedalPowerBalanceReference: (0x01 << 1), + accumulatedTorquePresent: (0x01 << 2), + accumulatedTorqueSource: (0x01 << 3), + accumulatedTorqueSourceWheel: (0x00 << 3), + accumulatedTorqueSourceCrank: (0x01 << 3), + wheelRevolutionDataPresent: (0x01 << 4), + crankRevolutionDataPresent: (0x01 << 5), + extremeForceMagnitudesPresent: (0x01 << 6), + extremeTorqueMagnitudesPresent: (0x01 << 7), + extremeAnglesPresent: (0x01 << 8), + topDeadSpotAnglePresent: (0x01 << 9), + bottomDeadSpotAnglePresent: (0x01 << 10), + accumulatedEnergyPresent: (0x01 << 11), + offsetCompensationIndicator: (0x01 << 12) +} + +export default class CyclingPowerMeasurementCharacteristic extends bleno.Characteristic { + constructor () { + super({ + // Cycling Power Meter Measurement + uuid: '2A63', + value: null, + properties: ['notify'], + descriptors: [ + new bleno.Descriptor({ + uuid: '2901', + value: 'Cycling Power Measurement' + }) + ] + }) + this._updateValueCallback = null + this._subscriberMaxValueSize = null + } + + onSubscribe (maxValueSize, updateValueCallback) { + log.debug(`CyclingPowerMeasurementCharacteristic - central subscribed with maxSize: ${maxValueSize}`) + this._updateValueCallback = updateValueCallback + this._subscriberMaxValueSize = maxValueSize + return this.RESULT_SUCCESS + } + + onUnsubscribe () { + log.debug('CyclingPowerMeasurementCharacteristic - central unsubscribed') + this._updateValueCallback = null + this._subscriberMaxValueSize = null + return this.RESULT_UNLIKELY_ERROR + } + + notify (data) { + // ignore events without the mandatory fields + if (!('cyclePower' in data)) { + log.error('can not deliver bike data without mandatory fields') + return this.RESULT_SUCCESS + } + + if (this._updateValueCallback) { + const bufferBuilder = new BufferBuilder() + + // Features flag + bufferBuilder.writeUInt16LE(cpsMeasurementFeaturesFlags.wheelRevolutionDataPresent | cpsMeasurementFeaturesFlags.crankRevolutionDataPresent) + + // Instantaneous Power + bufferBuilder.writeUInt16LE(Math.round(data.cyclePower)) + + // Wheel revolution count (basically the distance in cm) + bufferBuilder.writeUInt32LE(Math.round(Math.round(data.totalLinearDistance * 100))) + + // Wheel revolution time (ushort with 2048 resolution, resetting in every 32sec) + bufferBuilder.writeUInt16LE(Math.round(data.totalMovingTime * 2048) % Math.pow(2, 16)) + + // Total stroke count + bufferBuilder.writeUInt16LE(Math.round(data.totalNumberOfStrokes)) + + // last stroke time time (ushort with 1024 resolution, resetting in every 64sec) + bufferBuilder.writeUInt16LE(Math.round(data.driveLastStartTime * 1024) % Math.pow(2, 16)) + + const buffer = bufferBuilder.getBuffer() + if (buffer.length > this._subscriberMaxValueSize) { + log.warn(`CyclingPowerMeasurementCharacteristic - notification of ${buffer.length} bytes is too large for the subscriber`) + } + this._updateValueCallback(bufferBuilder.getBuffer()) + } + return this.RESULT_SUCCESS + } +} diff --git a/app/peripherals/ble/cps/CyclingPowerMeterService.js b/app/peripherals/ble/cps/CyclingPowerMeterService.js new file mode 100644 index 0000000000..ac2c811f32 --- /dev/null +++ b/app/peripherals/ble/cps/CyclingPowerMeterService.js @@ -0,0 +1,66 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor +*/ +import bleno from '@abandonware/bleno' +import BufferBuilder from '../BufferBuilder.js' +import { SensorLocationAsBuffer } from '../common/SensorLocation.js' +import StaticReadCharacteristic from '../common/StaticReadCharacteristic.js' +import CyclingPowerControlPointCharacteristic from './CpsControlPointCharacteristic.js' +import CyclingPowerMeasurementCharacteristic from './CpsMeasurementCharacteristic.js' + +export default class CyclingPowerService extends bleno.PrimaryService { + constructor (controlPointCallback) { + const cpsFeatureBuffer = new BufferBuilder() + cpsFeatureBuffer.writeUInt32LE(featuresFlag) + + const measurementCharacteristic = new CyclingPowerMeasurementCharacteristic() + super({ + // Cycling Power + uuid: '1818', + characteristics: [ + new StaticReadCharacteristic('2A65', 'Cycling Power Feature', cpsFeatureBuffer.getBuffer()), + measurementCharacteristic, + new StaticReadCharacteristic('2A5D', 'Sensor Location', SensorLocationAsBuffer()), + new CyclingPowerControlPointCharacteristic(controlPointCallback) + ] + }) + this.measurementCharacteristic = measurementCharacteristic + } + + notifyData (event) { + this.measurementCharacteristic.notify(event) + } +} + +export const cpsFeaturesFlags = +{ + pedalPowerBalanceSupported: (0x01 << 0), + accumulatedTorqueSupported: (0x01 << 1), + wheelRevolutionDataSupported: (0x01 << 2), + crankRevolutionDataSupported: (0x01 << 3), + extremeMagnitudesSupported: (0x01 << 4), + extremeAnglesSupported: (0x01 << 5), + topAndBottomDeadSpotAnglesSupported: (0x01 << 6), + accumulatedEnergySupported: (0x01 << 7), + offsetCompensationIndicatorSupported: (0x01 << 8), + offsetCompensationSupported: (0x01 << 9), + cyclingPowerMeasurementCharacteristicContentMaskingSupported: (0x01 << 10), + multipleSensorLocationsSupported: (0x01 << 11), + crankLengthAdjustmentSupported: (0x01 << 12), + chainLengthAdjustmentSupported: (0x01 << 13), + chainWeightAdjustmentSupported: (0x01 << 14), + spanLengthAdjustmentSupported: (0x01 << 15), + sensorMeasurementContext: (0x01 << 16), + sensorMeasurementContextForce: (0x00 << 16), + sensorMeasurementContextTorque: (0x01 << 16), + instantaneousMeasurementDirectionSupported: (0x01 << 17), + factoryCalibrationDateSupported: (0x01 << 18), + enhancedOffsetCompensationSupported: (0x01 << 19), + distributeSystemSupportUnspecified: (0x00 << 20), + distributeSystemSupportNotInDistributed: (0x01 << 20), + distributeSystemSupportInDistributed: (0x02 << 20), + distributeSystemSupportRFU: (0x03 << 20) +} + +const featuresFlag = cpsFeaturesFlags.sensorMeasurementContextForce | cpsFeaturesFlags.wheelRevolutionDataSupported | cpsFeaturesFlags.crankRevolutionDataSupported | cpsFeaturesFlags.distributeSystemSupportNotInDistributed diff --git a/app/peripherals/ble/csc/CscControlPointCharacteristic.js b/app/peripherals/ble/csc/CscControlPointCharacteristic.js new file mode 100644 index 0000000000..1f9a110b95 --- /dev/null +++ b/app/peripherals/ble/csc/CscControlPointCharacteristic.js @@ -0,0 +1,29 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + The connected Central can remotely control some parameters or our rowing monitor via this Control Point + + But for our use case proper implementation is not necessary (its mere existence with an empty handler suffice) +*/ +import bleno from '@abandonware/bleno' + +export default class CyclingSpeedCadenceControlPointCharacteristic extends bleno.Characteristic { + constructor (controlPointCallback) { + super({ + // Cycling Speed and Cadence Control Point + uuid: '2A55', + value: null, + properties: ['indicate', 'write'] + }) + + this.controlled = false + if (!controlPointCallback) { throw new Error('controlPointCallback required') } + this.controlPointCallback = controlPointCallback + } + + // Central sends a command to the Control Point + // No need to handle any request to have this working + onWriteRequest (data, offset, withoutResponse, callback) { + } +} diff --git a/app/peripherals/ble/csc/CscMeasurementCharacteristic.js b/app/peripherals/ble/csc/CscMeasurementCharacteristic.js new file mode 100644 index 0000000000..60461588c2 --- /dev/null +++ b/app/peripherals/ble/csc/CscMeasurementCharacteristic.js @@ -0,0 +1,81 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor +*/ +import bleno from '@abandonware/bleno' +import log from 'loglevel' +import BufferBuilder from '../BufferBuilder.js' + +export default class CyclingSpeedCadenceMeasurementCharacteristic extends bleno.Characteristic { + constructor () { + super({ + // Cycling Speed and Cadence Measurement + uuid: '2A5B', + value: null, + properties: ['notify'], + descriptors: [ + new bleno.Descriptor({ + uuid: '2901', + value: 'Cycling Speed and Cadence Measurement' + }) + ] + }) + this._updateValueCallback = null + this._subscriberMaxValueSize = null + } + + onSubscribe (maxValueSize, updateValueCallback) { + log.debug(`CyclingSpeedCadenceMeasurementCharacteristic - central subscribed with maxSize: ${maxValueSize}`) + this._updateValueCallback = updateValueCallback + this._subscriberMaxValueSize = maxValueSize + return this.RESULT_SUCCESS + } + + onUnsubscribe () { + log.debug('CyclingSpeedCadenceMeasurementCharacteristic - central unsubscribed') + this._updateValueCallback = null + this._subscriberMaxValueSize = null + return this.RESULT_UNLIKELY_ERROR + } + + notify (data) { + // ignore events without the mandatory fields + if (!('cyclePower' in data)) { + log.error('can not deliver bike data without mandatory fields') + return this.RESULT_SUCCESS + } + + if (this._updateValueCallback) { + const bufferBuilder = new BufferBuilder() + + // Features flag + bufferBuilder.writeUInt8(cscFeaturesFlags.crankRevolutionDataSupported | cscFeaturesFlags.wheelRevolutionDataSupported) + + // Wheel revolution count (basically the distance in cm) + bufferBuilder.writeUInt32LE(Math.round(Math.round(data.totalLinearDistance * 100))) + + // Wheel revolution time (ushort with 1024 resolution, resetting in every 64sec) + bufferBuilder.writeUInt16LE(Math.round(data.totalMovingTime * 1024) % Math.pow(2, 16)) + + // Total stroke count + bufferBuilder.writeUInt16LE(Math.round(data.totalNumberOfStrokes)) + + // last stroke time time (ushort with 1024 resolution, resetting in every 64sec) + bufferBuilder.writeUInt16LE(Math.round(data.driveLastStartTime * 1024) % Math.pow(2, 16)) + + const buffer = bufferBuilder.getBuffer() + if (buffer.length > this._subscriberMaxValueSize) { + log.warn(`CyclingSpeedCadenceMeasurementCharacteristic - notification of ${buffer.length} bytes is too large for the subscriber`) + } + this._updateValueCallback(bufferBuilder.getBuffer()) + } + return this.RESULT_SUCCESS + } +} + +export const cscFeaturesFlags = +{ + wheelRevolutionDataSupported: (0x01 << 0), + crankRevolutionDataSupported: (0x01 << 1), + multipleSensorLocationSupported: (0x01 << 2) +} diff --git a/app/peripherals/ble/csc/CyclingSpeedCadenceService.js b/app/peripherals/ble/csc/CyclingSpeedCadenceService.js new file mode 100644 index 0000000000..261b38505f --- /dev/null +++ b/app/peripherals/ble/csc/CyclingSpeedCadenceService.js @@ -0,0 +1,36 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor +*/ +import bleno from '@abandonware/bleno' +import BufferBuilder from '../BufferBuilder.js' +import { SensorLocationAsBuffer } from '../common/SensorLocation.js' +import StaticReadCharacteristic from '../common/StaticReadCharacteristic.js' +import CyclingSpeedCadenceControlPointCharacteristic from './CscControlPointCharacteristic.js' +import CyclingSpeedCadenceMeasurementCharacteristic, { cscFeaturesFlags } from './CscMeasurementCharacteristic.js' + +export default class CyclingSpeedCadenceService extends bleno.PrimaryService { + constructor (controlPointCallback) { + const cscFeatureBuffer = new BufferBuilder() + cscFeatureBuffer.writeUInt16LE(featuresFlag) + + const measurementCharacteristic = new CyclingSpeedCadenceMeasurementCharacteristic() + super({ + // Cycling Speed and Cadence + uuid: '1816', + characteristics: [ + new StaticReadCharacteristic('2A5C', 'Cycling Speed and Cadence Feature', cscFeatureBuffer.getBuffer()), + measurementCharacteristic, + new CyclingSpeedCadenceControlPointCharacteristic(controlPointCallback), + new StaticReadCharacteristic('2A5D', 'Sensor Location', SensorLocationAsBuffer()) + ] + }) + this.measurementCharacteristic = measurementCharacteristic + } + + notifyData (event) { + this.measurementCharacteristic.notify(event) + } +} + +const featuresFlag = cscFeaturesFlags.crankRevolutionDataSupported | cscFeaturesFlags.wheelRevolutionDataSupported diff --git a/app/ble/ftms/FitnessMachineControlPointCharacteristic.js b/app/peripherals/ble/ftms/FitnessMachineControlPointCharacteristic.js similarity index 84% rename from app/ble/ftms/FitnessMachineControlPointCharacteristic.js rename to app/peripherals/ble/ftms/FitnessMachineControlPointCharacteristic.js index 7d96096f12..9d52b3c207 100644 --- a/app/ble/ftms/FitnessMachineControlPointCharacteristic.js +++ b/app/peripherals/ble/ftms/FitnessMachineControlPointCharacteristic.js @@ -10,6 +10,7 @@ */ import bleno from '@abandonware/bleno' import log from 'loglevel' +import { ResultOpCode } from '../common/CommonOpCodes.js' // see https://www.bluetooth.com/specifications/specs/fitness-machine-service-1-0 for details const ControlPointOpCode = { @@ -37,15 +38,6 @@ const ControlPointOpCode = { responseCode: 0x80 } -const ResultCode = { - reserved: 0x00, - success: 0x01, - opCodeNotSupported: 0x02, - invalidParameter: 0x03, - operationFailed: 0x04, - controlNotPermitted: 0x05 -} - export default class FitnessMachineControlPointCharacteristic extends bleno.Characteristic { constructor (controlPointCallback) { super({ @@ -70,12 +62,12 @@ export default class FitnessMachineControlPointCharacteristic extends bleno.Char if (this.controlPointCallback({ name: 'requestControl' })) { log.debug('requestControl sucessful') this.controlled = true - callback(this.buildResponse(opCode, ResultCode.success)) + callback(this.buildResponse(opCode, ResultOpCode.success)) } else { - callback(this.buildResponse(opCode, ResultCode.operationFailed)) + callback(this.buildResponse(opCode, ResultOpCode.operationFailed)) } } else { - callback(this.buildResponse(opCode, ResultCode.controlNotPermitted)) + callback(this.buildResponse(opCode, ResultOpCode.controlNotPermitted)) } break @@ -109,30 +101,30 @@ export default class FitnessMachineControlPointCharacteristic extends bleno.Char const crr = data.readUInt8(5) * 0.0001 const cw = data.readUInt8(6) * 0.01 if (this.controlPointCallback({ name: 'setIndoorBikeSimulationParameters', value: { windspeed, grade, crr, cw } })) { - callback(this.buildResponse(opCode, ResultCode.success)) + callback(this.buildResponse(opCode, ResultOpCode.success)) } else { - callback(this.buildResponse(opCode, ResultCode.operationFailed)) + callback(this.buildResponse(opCode, ResultOpCode.operationFailed)) } break } default: log.info(`opCode ${opCode} is not supported`) - callback(this.buildResponse(opCode, ResultCode.opCodeNotSupported)) + callback(this.buildResponse(opCode, ResultOpCode.opCodeNotSupported)) } } handleSimpleCommand (opCode, opName, callback) { if (this.controlled) { if (this.controlPointCallback({ name: opName })) { - const response = this.buildResponse(opCode, ResultCode.success) + const response = this.buildResponse(opCode, ResultOpCode.success) callback(response) } else { - callback(this.buildResponse(opCode, ResultCode.operationFailed)) + callback(this.buildResponse(opCode, ResultOpCode.operationFailed)) } } else { log.info(`initating command '${opName}' requires 'requestControl'`) - callback(this.buildResponse(opCode, ResultCode.controlNotPermitted)) + callback(this.buildResponse(opCode, ResultOpCode.controlNotPermitted)) } } diff --git a/app/ble/ftms/FitnessMachineService.js b/app/peripherals/ble/ftms/FitnessMachineService.js similarity index 59% rename from app/ble/ftms/FitnessMachineService.js rename to app/peripherals/ble/ftms/FitnessMachineService.js index d4703742b4..6a411fe355 100644 --- a/app/ble/ftms/FitnessMachineService.js +++ b/app/peripherals/ble/ftms/FitnessMachineService.js @@ -18,23 +18,25 @@ import bleno from '@abandonware/bleno' import RowerDataCharacteristic from './RowerDataCharacteristic.js' -import RowerFeatureCharacteristic from './RowerFeatureCharacteristic.js' import IndoorBikeDataCharacteristic from './IndoorBikeDataCharacteristic.js' -import IndoorBikeFeatureCharacteristic from './IndoorBikeFeatureCharacteristic.js' import FitnessMachineControlPointCharacteristic from './FitnessMachineControlPointCharacteristic.js' import FitnessMachineStatusCharacteristic from './FitnessMachineStatusCharacteristic.js' +import StaticReadCharacteristic from '../common/StaticReadCharacteristic.js' +import BufferBuilder from '../BufferBuilder.js' export default class FitnessMachineService extends bleno.PrimaryService { constructor (options, controlPointCallback) { const simulateIndoorBike = options?.simulateIndoorBike === true const dataCharacteristic = simulateIndoorBike ? new IndoorBikeDataCharacteristic() : new RowerDataCharacteristic() - const featureCharacteristic = simulateIndoorBike ? new IndoorBikeFeatureCharacteristic() : new RowerFeatureCharacteristic() const statusCharacteristic = new FitnessMachineStatusCharacteristic() + const ftmsFeaturesBuffer = new BufferBuilder() + ftmsFeaturesBuffer.writeUInt16LE(featuresFlag) + super({ // Fitness Machine uuid: '1826', characteristics: [ - featureCharacteristic, + new StaticReadCharacteristic('2ACC', 'FTMS Feature', ftmsFeaturesBuffer.getBuffer()), dataCharacteristic, new FitnessMachineControlPointCharacteristic(controlPointCallback), statusCharacteristic @@ -52,3 +54,25 @@ export default class FitnessMachineService extends bleno.PrimaryService { this.statusCharacteristic.notify(event) } } + +export const FtmsBikeFeaturesFlags = { + averageSpeedSupported: (0x01 << 0), + cadenceSupported: (0x01 << 1), + totalDistanceSupported: (0x01 << 2), + inclinationSupported: (0x01 << 3), + elevationGainSupported: (0x01 << 4), + paceSupported: (0x01 << 5), + stepCountSupported: (0x01 << 6), + resistanceLevelSupported: (0x01 << 7), + strideCountSupported: (0x01 << 8), + expendedEnergySupported: (0x01 << 9), + heartRateMeasurementSupported: (0x01 << 10), + metabolicEquivalentSupported: (0x01 << 11), + elapsedTimeSupported: (0x01 << 12), + remainingTimeSupported: (0x01 << 13), + powerMeasurementSupported: (0x01 << 14), + forceOnBeltAndPowerOutputSupported: (0x01 << 15), + userDataRetentionSupported: (0x01 << 16) +} + +export const featuresFlag = FtmsBikeFeaturesFlags.cadenceSupported | FtmsBikeFeaturesFlags.totalDistanceSupported | FtmsBikeFeaturesFlags.paceSupported | FtmsBikeFeaturesFlags.expendedEnergySupported | FtmsBikeFeaturesFlags.heartRateMeasurementSupported | FtmsBikeFeaturesFlags.elapsedTimeSupported | FtmsBikeFeaturesFlags.powerMeasurementSupported diff --git a/app/ble/ftms/FitnessMachineStatusCharacteristic.js b/app/peripherals/ble/ftms/FitnessMachineStatusCharacteristic.js similarity index 100% rename from app/ble/ftms/FitnessMachineStatusCharacteristic.js rename to app/peripherals/ble/ftms/FitnessMachineStatusCharacteristic.js diff --git a/app/ble/ftms/IndoorBikeDataCharacteristic.js b/app/peripherals/ble/ftms/IndoorBikeDataCharacteristic.js similarity index 70% rename from app/ble/ftms/IndoorBikeDataCharacteristic.js rename to app/peripherals/ble/ftms/IndoorBikeDataCharacteristic.js index 141a49cdd5..f33e05e729 100644 --- a/app/ble/ftms/IndoorBikeDataCharacteristic.js +++ b/app/peripherals/ble/ftms/IndoorBikeDataCharacteristic.js @@ -50,7 +50,7 @@ export default class IndoorBikeDataCharacteristic extends bleno.Characteristic { notify (data) { // ignore events without the mandatory fields - if (!('speed' in data)) { + if (!('cycleLinearVelocity' in data)) { log.error('can not deliver bike data without mandatory fields') return this.RESULT_SUCCESS } @@ -60,34 +60,33 @@ export default class IndoorBikeDataCharacteristic extends bleno.Characteristic { // Field flags as defined in the Bluetooth Documentation // Instantaneous speed (default), Instantaneous Cadence (2), Total Distance (4), // Instantaneous Power (6), Total / Expended Energy (8), Heart Rate (9), Elapsed Time (11) - // 01010100 - bufferBuilder.writeUInt8(0x54) // 00001011 - bufferBuilder.writeUInt8(0x0B) + // 01010100 + bufferBuilder.writeUInt16LE(measurementFlag) // see https://www.bluetooth.com/specifications/specs/gatt-specification-supplement-3/ // for some of the data types // Instantaneous Speed in km/h - bufferBuilder.writeUInt16LE(Math.round(data.speed * 100)) + bufferBuilder.writeUInt16LE(data.cycleLinearVelocity * 3.6 * 100) // Instantaneous Cadence in rotations per minute (we use this to communicate the strokes per minute) - bufferBuilder.writeUInt16LE(Math.round(data.strokesPerMinute * 2)) + bufferBuilder.writeUInt16LE(Math.round(data.cycleStrokeRate * 2)) // Total Distance in meters - bufferBuilder.writeUInt24LE(Math.round(data.distanceTotal)) + bufferBuilder.writeUInt24LE(Math.round(data.totalLinearDistance)) // Instantaneous Power in watts - bufferBuilder.writeUInt16LE(Math.round(data.power)) + bufferBuilder.writeUInt16LE(Math.round(data.cyclePower)) // Energy // Total energy in kcal - bufferBuilder.writeUInt16LE(Math.round(data.caloriesTotal)) + bufferBuilder.writeUInt16LE(Math.round(data.totalCalories)) // Energy per hour // The Energy per Hour field represents the average expended energy of a user during a // period of one hour. - bufferBuilder.writeUInt16LE(Math.round(data.caloriesPerHour)) + bufferBuilder.writeUInt16LE(Math.round(data.totalCaloriesPerHour)) // Energy per minute - bufferBuilder.writeUInt8(Math.round(data.caloriesPerMinute)) + bufferBuilder.writeUInt8(Math.round(data.totalCaloriesPerMinute)) // Heart Rate: Beats per minute with a resolution of 1 bufferBuilder.writeUInt8(Math.round(data.heartrate)) // Elapsed Time: Seconds with a resolution of 1 - bufferBuilder.writeUInt16LE(Math.round(data.durationTotal)) + bufferBuilder.writeUInt16LE(Math.round(data.totalMovingTime)) const buffer = bufferBuilder.getBuffer() if (buffer.length > this._subscriberMaxValueSize) { @@ -98,3 +97,21 @@ export default class IndoorBikeDataCharacteristic extends bleno.Characteristic { return this.RESULT_SUCCESS } } + +export const RowingMeasurementFlags = { + moreDataPresent: (0x01 << 0), + averageSpeedPresent: (0x01 << 1), + instantaneousCadencePresent: (0x01 << 2), + averageCadencePresent: (0x01 << 3), + totalDistancePresent: (0x01 << 4), + resistanceLevelPresent: (0x01 << 5), + instantaneousPowerPresent: (0x01 << 6), + averagePowerPresent: (0x01 << 7), + expendedEnergyPresent: (0x01 << 8), + heartRatePresent: (0x01 << 9), + metabolicEquivalentPresent: (0x01 << 10), + elapsedTimePresent: (0x01 << 11), + remainingTimePresent: (0x01 << 12) +} + +export const measurementFlag = RowingMeasurementFlags.instantaneousCadencePresent | RowingMeasurementFlags.totalDistancePresent | RowingMeasurementFlags.instantaneousPowerPresent | RowingMeasurementFlags.expendedEnergyPresent | RowingMeasurementFlags.heartRatePresent | RowingMeasurementFlags.elapsedTimePresent diff --git a/app/ble/ftms/RowerDataCharacteristic.js b/app/peripherals/ble/ftms/RowerDataCharacteristic.js similarity index 67% rename from app/ble/ftms/RowerDataCharacteristic.js rename to app/peripherals/ble/ftms/RowerDataCharacteristic.js index 6f9c63aa0d..81df3f6a1d 100644 --- a/app/ble/ftms/RowerDataCharacteristic.js +++ b/app/peripherals/ble/ftms/RowerDataCharacteristic.js @@ -43,7 +43,7 @@ export default class RowerDataCharacteristic extends bleno.Characteristic { notify (data) { // ignore events without the mandatory fields - if (!('strokesPerMinute' in data && 'strokesTotal' in data)) { + if (!('cycleStrokeRate' in data && 'totalNumberOfStrokes' in data)) { return this.RESULT_SUCCESS } @@ -55,39 +55,38 @@ export default class RowerDataCharacteristic extends bleno.Characteristic { // todo: might add: Average Stroke Rate (1), Average Pace (4), Average Power (6) // Remaining Time (12) // 00101100 - bufferBuilder.writeUInt8(0x2c) + bufferBuilder.writeUInt16LE(measurementFlag) // 00001011 - bufferBuilder.writeUInt8(0x0B) // see https://www.bluetooth.com/specifications/specs/gatt-specification-supplement-3/ // for some of the data types // Stroke Rate in stroke/minute, value is multiplied by 2 to have a .5 precision - bufferBuilder.writeUInt8(Math.round(data.strokesPerMinute * 2)) + bufferBuilder.writeUInt8(Math.round(data.cycleStrokeRate * 2)) // Stroke Count - bufferBuilder.writeUInt16LE(Math.round(data.strokesTotal)) + bufferBuilder.writeUInt16LE(Math.round(data.totalNumberOfStrokes)) // Total Distance in meters - bufferBuilder.writeUInt24LE(Math.round(data.distanceTotal)) + bufferBuilder.writeUInt24LE(Math.round(data.totalLinearDistance)) // Instantaneous Pace in seconds/500m // if split is infinite (i.e. while pausing), should use the highest possible number (0xFFFF) // todo: eventhough mathematically correct, setting 0xFFFF (65535s) causes some ugly spikes // in some applications which could shift the axis (i.e. workout diagrams in MyHomeFit) // so instead for now we use 0 here - bufferBuilder.writeUInt16LE(data.split !== Infinity ? Math.round(data.split) : 0) + bufferBuilder.writeUInt16LE(data.cyclePace !== Infinity && data.cyclePace < 65535 ? Math.round(data.cyclePace) : 0xFFFF) // Instantaneous Power in watts - bufferBuilder.writeUInt16LE(Math.round(data.power)) + bufferBuilder.writeUInt16LE(Math.round(data.cyclePower)) // Energy in kcal // Total energy in kcal - bufferBuilder.writeUInt16LE(Math.round(data.caloriesTotal)) + bufferBuilder.writeUInt16LE(Math.round(data.totalCalories)) // Energy per hour // The Energy per Hour field represents the average expended energy of a user during a // period of one hour. - bufferBuilder.writeUInt16LE(Math.round(data.caloriesPerHour)) + bufferBuilder.writeUInt16LE(Math.round(data.totalCaloriesPerHour)) // Energy per minute - bufferBuilder.writeUInt8(Math.round(data.caloriesPerMinute)) + bufferBuilder.writeUInt8(Math.round(data.totalCaloriesPerMinute)) // Heart Rate: Beats per minute with a resolution of 1 bufferBuilder.writeUInt8(Math.round(data.heartrate)) // Elapsed Time: Seconds with a resolution of 1 - bufferBuilder.writeUInt16LE(Math.round(data.durationTotal)) + bufferBuilder.writeUInt16LE(Math.round(data.totalMovingTime)) const buffer = bufferBuilder.getBuffer() if (buffer.length > this._subscriberMaxValueSize) { @@ -98,3 +97,21 @@ export default class RowerDataCharacteristic extends bleno.Characteristic { return this.RESULT_SUCCESS } } + +export const RowingMeasurementFlags = { + moreDataPresent: (0x01 << 0), + averageStrokeRatePresent: (0x01 << 1), + totalDistancePresent: (0x01 << 2), + instantaneousPacePresent: (0x01 << 3), + averagePacePresent: (0x01 << 4), + instantaneousPowerPresent: (0x01 << 5), + averagePowerPresent: (0x01 << 6), + resistanceLevelPresent: (0x01 << 7), + expendedEnergyPresent: (0x01 << 8), + heartRatePresent: (0x01 << 9), + metabolicEquivalentPresent: (0x01 << 10), + elapsedTimePresent: (0x01 << 11), + remainingTimePresent: (0x01 << 12) +} + +export const measurementFlag = RowingMeasurementFlags.totalDistancePresent | RowingMeasurementFlags.instantaneousPacePresent | RowingMeasurementFlags.instantaneousPowerPresent | RowingMeasurementFlags.expendedEnergyPresent | RowingMeasurementFlags.heartRatePresent | RowingMeasurementFlags.elapsedTimePresent diff --git a/app/ble/CentralManager.js b/app/peripherals/ble/hrm/HeartRateManager.js similarity index 79% rename from app/ble/CentralManager.js rename to app/peripherals/ble/hrm/HeartRateManager.js index c21c340447..1b6c91f76e 100644 --- a/app/ble/CentralManager.js +++ b/app/peripherals/ble/hrm/HeartRateManager.js @@ -49,7 +49,7 @@ NobleBindings.prototype.onDisconnComplete = function (handle, reason) { const noble = new Noble(new NobleBindings()) // END of noble patch -function createCentralManager () { +function createHeartRateManager () { const emitter = new EventEmitter() let batteryLevel @@ -64,10 +64,10 @@ function createCentralManager () { noble.on('discover', (peripheral) => { noble.stopScanning() - connectHeartratePeripheral(peripheral) + connectHeartRatePeripheral(peripheral) }) - function connectHeartratePeripheral (peripheral) { + function connectHeartRatePeripheral (peripheral) { // connect to the heart rate sensor peripheral.connect((error) => { if (error) { @@ -75,45 +75,45 @@ function createCentralManager () { return } log.info(`heart rate peripheral connected, name: '${peripheral.advertisement?.localName}', id: ${peripheral.id}`) - subscribeToHeartrateMeasurement(peripheral) + subscribeToHeartRateMeasurement(peripheral) }) peripheral.once('disconnect', () => { // todo: figure out if we have to dispose the peripheral somehow to prevent memory leaks log.info('heart rate peripheral disconnected, searching new one') - batteryLevel = undefined + batteryLevel = 0 noble.startScanning(['180d'], false) }) } // see https://www.bluetooth.com/specifications/specs/heart-rate-service-1-0/ - function subscribeToHeartrateMeasurement (peripheral) { - const heartrateMeasurementUUID = '2a37' + function subscribeToHeartRateMeasurement (peripheral) { + const heartRateMeasurementUUID = '2a37' const batteryLevelUUID = '2a19' - peripheral.discoverSomeServicesAndCharacteristics([], [heartrateMeasurementUUID, batteryLevelUUID], + peripheral.discoverSomeServicesAndCharacteristics([], [heartRateMeasurementUUID, batteryLevelUUID], (error, services, characteristics) => { if (error) { log.error(error) return } - const heartrateMeasurementCharacteristic = characteristics.find( - characteristic => characteristic.uuid === heartrateMeasurementUUID + const heartRateMeasurementCharacteristic = characteristics.find( + characteristic => characteristic.uuid === heartRateMeasurementUUID ) const batteryLevelCharacteristic = characteristics.find( characteristic => characteristic.uuid === batteryLevelUUID ) - if (heartrateMeasurementCharacteristic !== undefined) { - heartrateMeasurementCharacteristic.notify(true, (error) => { + if (heartRateMeasurementCharacteristic !== undefined) { + heartRateMeasurementCharacteristic.notify(true, (error) => { if (error) { log.error(error) return } - heartrateMeasurementCharacteristic.on('data', (data, isNotification) => { + heartRateMeasurementCharacteristic.on('data', (data, isNotification) => { const buffer = Buffer.from(data) const flags = buffer.readUInt8(0) // bits of the feature flag: @@ -121,7 +121,7 @@ function createCentralManager () { // 1 + 2: Sensor Contact Status // 3: Energy Expended Status // 4: RR-Interval - const heartrateUint16LE = flags & 0b1 + const heartRateUint16LE = flags & 0b1 // from the specs: // While most human applications require support for only 255 bpm or less, special @@ -129,13 +129,23 @@ function createCentralManager () { // If the Heart Rate Measurement Value is less than or equal to 255 bpm a UINT8 format // should be used for power savings. // If the Heart Rate Measurement Value exceeds 255 bpm a UINT16 format shall be used. - const heartrate = heartrateUint16LE ? buffer.readUInt16LE(1) : buffer.readUInt8(1) - emitter.emit('heartrateMeasurement', { heartrate, batteryLevel }) + const heartrate = heartRateUint16LE ? buffer.readUInt16LE(1) : buffer.readUInt8(1) + emitter.emit('heartRateMeasurement', { heartrate, batteryLevel }) }) }) } if (batteryLevelCharacteristic !== undefined) { + batteryLevelCharacteristic.read((error, data) => { + if (error) { + log.error(error) + return + } + + const buffer = Buffer.from(data) + batteryLevel = buffer.readUInt8(0) + }) + batteryLevelCharacteristic.notify(true, (error) => { if (error) { log.error(error) @@ -155,4 +165,4 @@ function createCentralManager () { }) } -export { createCentralManager } +export { createHeartRateManager } diff --git a/app/peripherals/ble/hrm/HrmService.js b/app/peripherals/ble/hrm/HrmService.js new file mode 100644 index 0000000000..00a6551160 --- /dev/null +++ b/app/peripherals/ble/hrm/HrmService.js @@ -0,0 +1,25 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Starts the central manager in a forked thread since noble does not like + to run in the same thread as bleno +*/ +import process from 'process' +import log from 'loglevel' +import config from '../../../tools/ConfigManager.js' +import { createHeartRateManager } from './HeartRateManager.js' + +log.setLevel(config.loglevel.default) +start() + +function start () { + const heartRateManager = createHeartRateManager() + heartRateManager.on('heartRateMeasurement', (heartRateMeasurement) => { + process.send(heartRateMeasurement) + }) + + process.on('uncaughtException', (err) => { + log.error('An error occurred in BLE Heart Rate service if you experience issues with the bluetooth connection to your heart rate sensor please restart app: ', err) + }) +} diff --git a/app/peripherals/ble/pm5/DeviceInformationService.js b/app/peripherals/ble/pm5/DeviceInformationService.js new file mode 100644 index 0000000000..42a25efaba --- /dev/null +++ b/app/peripherals/ble/pm5/DeviceInformationService.js @@ -0,0 +1,32 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Provides the required Device Information of the PM5 +*/ +import bleno from '@abandonware/bleno' +import StaticNotifyCharacteristic from '../common/StaticNotifyCharacteristic.js' +import { getFullUUID, pm5Constants } from './Pm5Constants.js' + +export default class DeviceInformationService extends bleno.PrimaryService { + constructor () { + super({ + // InformationenService uuid as defined by the PM5 specification + uuid: getFullUUID('0010'), + characteristics: [ + // C2 module number string + new StaticNotifyCharacteristic(getFullUUID('0011'), 'model', 'PM5', true), + // C2 serial number string + new StaticNotifyCharacteristic(getFullUUID('0012'), 'serial', pm5Constants.serial, true), + // C2 hardware revision string + new StaticNotifyCharacteristic(getFullUUID('0013'), 'hardwareRevision', pm5Constants.hardwareRevision, true), + // C2 firmware revision string + new StaticNotifyCharacteristic(getFullUUID('0014'), 'firmwareRevision', pm5Constants.firmwareRevision, true), + // C2 manufacturer name string + new StaticNotifyCharacteristic(getFullUUID('0015'), 'manufacturer', pm5Constants.manufacturer, true), + // Erg Machine Type + new StaticNotifyCharacteristic(getFullUUID('0016'), 'ergMachineType', pm5Constants.ergMachineType, true) + ] + }) + } +} diff --git a/app/ble/pm5/GapService.js b/app/peripherals/ble/pm5/GapService.js similarity index 52% rename from app/ble/pm5/GapService.js rename to app/peripherals/ble/pm5/GapService.js index f90c42c8b0..168cdd5116 100644 --- a/app/ble/pm5/GapService.js +++ b/app/peripherals/ble/pm5/GapService.js @@ -6,8 +6,8 @@ todo: not sure if this is correct, the normal GAP service has 0x1800 */ import bleno from '@abandonware/bleno' -import { constants, getFullUUID } from './Pm5Constants.js' -import ValueReadCharacteristic from './characteristic/ValueReadCharacteristic.js' +import StaticNotifyCharacteristic from '../common/StaticNotifyCharacteristic.js' +import { getFullUUID, pm5Constants } from './Pm5Constants.js' export default class GapService extends bleno.PrimaryService { constructor () { @@ -16,15 +16,15 @@ export default class GapService extends bleno.PrimaryService { uuid: getFullUUID('0000'), characteristics: [ // GAP device name - new ValueReadCharacteristic('2A00', constants.name), + new StaticNotifyCharacteristic('2A00', undefined, pm5Constants.name, true), // GAP appearance - new ValueReadCharacteristic('2A01', [0x00, 0x00]), + new StaticNotifyCharacteristic('2A01', undefined, [0x00, 0x00], true), // GAP peripheral privacy - new ValueReadCharacteristic('2A02', [0x00]), + new StaticNotifyCharacteristic('2A02', undefined, [0x00], true), // GAP reconnect address - new ValueReadCharacteristic('2A03', '00:00:00:00:00:00'), + new StaticNotifyCharacteristic('2A03', undefined, '00:00:00:00:00:00', true), // Peripheral preferred connection parameters - new ValueReadCharacteristic('2A04', [0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0xE8, 0x03]) + new StaticNotifyCharacteristic('2A04', undefined, [0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0xE8, 0x03], true) ] }) } diff --git a/app/ble/pm5/Pm5Constants.js b/app/peripherals/ble/pm5/Pm5Constants.js similarity index 54% rename from app/ble/pm5/Pm5Constants.js rename to app/peripherals/ble/pm5/Pm5Constants.js index 398701b742..c71762126f 100644 --- a/app/ble/pm5/Pm5Constants.js +++ b/app/peripherals/ble/pm5/Pm5Constants.js @@ -1,17 +1,16 @@ 'use strict' + +import { PeripheralConstants } from '../../PeripheralConstants.js' + /* Open Rowing Monitor, https://github.com/laberning/openrowingmonitor Some PM5 specific constants */ -const constants = { - serial: '123456789', - model: 'PM5', - name: 'PM5 123456789', - hardwareRevision: '633', - // see https://www.concept2.com/service/monitors/pm5/firmware for available versions - firmwareRevision: '207', - manufacturer: 'Concept2', +const pm5Constants = { + ...PeripheralConstants, + // See https://www.concept2.com/service/monitors/pm5/firmware for available versions + // please note: hardware versions exclude a software version, and thus might confuse the client ergMachineType: [0x05] } @@ -22,5 +21,5 @@ function getFullUUID (uuid) { export { getFullUUID, - constants + pm5Constants } diff --git a/app/ble/pm5/Pm5ControlService.js b/app/peripherals/ble/pm5/Pm5ControlService.js similarity index 100% rename from app/ble/pm5/Pm5ControlService.js rename to app/peripherals/ble/pm5/Pm5ControlService.js diff --git a/app/ble/pm5/Pm5RowingService.js b/app/peripherals/ble/pm5/Pm5RowingService.js similarity index 80% rename from app/ble/pm5/Pm5RowingService.js rename to app/peripherals/ble/pm5/Pm5RowingService.js index 8e00cf5f1d..e0fc3842f7 100644 --- a/app/ble/pm5/Pm5RowingService.js +++ b/app/peripherals/ble/pm5/Pm5RowingService.js @@ -20,13 +20,13 @@ */ import bleno from '@abandonware/bleno' import { getFullUUID } from './Pm5Constants.js' -import ValueReadCharacteristic from './characteristic/ValueReadCharacteristic.js' import MultiplexedCharacteristic from './characteristic/MultiplexedCharacteristic.js' import GeneralStatus from './characteristic/GeneralStatus.js' import AdditionalStatus from './characteristic/AdditionalStatus.js' import AdditionalStatus2 from './characteristic/AdditionalStatus2.js' import AdditionalStrokeData from './characteristic/AdditionalStrokeData.js' import StrokeData from './characteristic/StrokeData.js' +import StaticNotifyCharacteristic from '../common/StaticNotifyCharacteristic.js' export default class PM5RowingService extends bleno.PrimaryService { constructor () { @@ -46,23 +46,23 @@ export default class PM5RowingService extends bleno.PrimaryService { // C2 rowing additional status 2 additionalStatus2, // C2 rowing general status and additional status samplerate - new ValueReadCharacteristic(getFullUUID('0034'), 'samplerate', 'samplerate'), + new StaticNotifyCharacteristic(getFullUUID('0034'), 'samplerate', 'samplerate', true), // C2 rowing stroke data strokeData, // C2 rowing additional stroke data additionalStrokeData, // C2 rowing split/interval data - new ValueReadCharacteristic(getFullUUID('0037'), 'split data', 'split data'), + new StaticNotifyCharacteristic(getFullUUID('0037'), 'split data', 'split data', true), // C2 rowing additional split/interval data - new ValueReadCharacteristic(getFullUUID('0038'), 'additional split data', 'additional split data'), + new StaticNotifyCharacteristic(getFullUUID('0038'), 'additional split data', 'additional split data', true), // C2 rowing end of workout summary data - new ValueReadCharacteristic(getFullUUID('0039'), 'workout summary', 'workout summary'), + new StaticNotifyCharacteristic(getFullUUID('0039'), 'workout summary', 'workout summary', true), // C2 rowing end of workout additional summary data - new ValueReadCharacteristic(getFullUUID('003A'), 'additional workout summary', 'additional workout summary'), + new StaticNotifyCharacteristic(getFullUUID('003A'), 'additional workout summary', 'additional workout summary', true), // C2 rowing heart rate belt information - new ValueReadCharacteristic(getFullUUID('003B'), 'heart rate belt information', 'heart rate belt information'), + new StaticNotifyCharacteristic(getFullUUID('003B'), 'heart rate belt information', 'heart rate belt information', true), // C2 force curve data - new ValueReadCharacteristic(getFullUUID('003D'), 'force curve data', 'force curve data'), + new StaticNotifyCharacteristic(getFullUUID('003D'), 'force curve data', 'force curve data', true), // C2 multiplexed information multiplexedCharacteristic ] diff --git a/app/ble/pm5/characteristic/AdditionalStatus.js b/app/peripherals/ble/pm5/characteristic/AdditionalStatus.js similarity index 78% rename from app/ble/pm5/characteristic/AdditionalStatus.js rename to app/peripherals/ble/pm5/characteristic/AdditionalStatus.js index 0e8dd267c9..2173275976 100644 --- a/app/ble/pm5/characteristic/AdditionalStatus.js +++ b/app/peripherals/ble/pm5/characteristic/AdditionalStatus.js @@ -38,22 +38,22 @@ export default class AdditionalStatus extends bleno.Characteristic { if (this._updateValueCallback || this._multiplexedCharacteristic.centralSubscribed()) { const bufferBuilder = new BufferBuilder() // elapsedTime: UInt24LE in 0.01 sec - bufferBuilder.writeUInt24LE(Math.round(data.durationTotal * 100)) + bufferBuilder.writeUInt24LE(Math.round(data.totalMovingTime * 100)) // speed: UInt16LE in 0.001 m/sec - bufferBuilder.writeUInt16LE(Math.round(data.speed * 1000 / 3.6)) + bufferBuilder.writeUInt16LE(Math.round(data.cycleLinearVelocity * 1000)) // strokeRate: UInt8 in strokes/min - bufferBuilder.writeUInt8(Math.round(data.strokesPerMinute)) + bufferBuilder.writeUInt8(Math.round(data.cycleStrokeRate)) // heartrate: UInt8 in bpm, 255 if invalid bufferBuilder.writeUInt8(Math.round(data.heartrate)) // currentPace: UInt16LE in 0.01 sec/500m // if split is infinite (i.e. while pausing), use the highest possible number - bufferBuilder.writeUInt16LE(data.split !== Infinity ? Math.round(data.split * 100) : 0xFFFF) + bufferBuilder.writeUInt16LE(data.cyclePace !== Infinity && data.cyclePace > 0 && data.cyclePace < 655.34 ? data.cyclePace * 100 : 0xFFFF) // averagePace: UInt16LE in 0.01 sec/500m let averagePace = 0 - if (data.distanceTotal && data.distanceTotal !== 0) { - averagePace = data.durationTotal / data.distanceTotal * 500 + if (data.totalLinearDistance && data.totalLinearDistance !== 0) { + averagePace = (data.totalMovingTime / data.totalLinearDistance) * 500 } - bufferBuilder.writeUInt16LE(Math.round(averagePace * 100)) + bufferBuilder.writeUInt16LE(Math.round(Math.min(averagePace * 100, 65535))) // restDistance: UInt16LE bufferBuilder.writeUInt16LE(0) // restTime: UInt24LE in 0.01 sec @@ -62,7 +62,7 @@ export default class AdditionalStatus extends bleno.Characteristic { // the multiplexer uses a slightly different format for the AdditionalStatus // it adds averagePower before the ergMachineType // averagePower: UInt16LE in watts - bufferBuilder.writeUInt16LE(Math.round(data.power)) + bufferBuilder.writeUInt16LE(Math.round(data.cyclePower)) } // ergMachineType: 0 TYPE_STATIC_D bufferBuilder.writeUInt8(0) diff --git a/app/ble/pm5/characteristic/AdditionalStatus2.js b/app/peripherals/ble/pm5/characteristic/AdditionalStatus2.js similarity index 90% rename from app/ble/pm5/characteristic/AdditionalStatus2.js rename to app/peripherals/ble/pm5/characteristic/AdditionalStatus2.js index 67f54f1650..66ccc66aec 100644 --- a/app/ble/pm5/characteristic/AdditionalStatus2.js +++ b/app/peripherals/ble/pm5/characteristic/AdditionalStatus2.js @@ -38,17 +38,17 @@ export default class AdditionalStatus2 extends bleno.Characteristic { if (this._updateValueCallback || this._multiplexedCharacteristic.centralSubscribed()) { const bufferBuilder = new BufferBuilder() // elapsedTime: UInt24LE in 0.01 sec - bufferBuilder.writeUInt24LE(Math.round(data.durationTotal * 100)) + bufferBuilder.writeUInt24LE(Math.round(data.totalMovingTime * 100)) // intervalCount: UInt8 bufferBuilder.writeUInt8(0) if (this._updateValueCallback) { // the multiplexer uses a slightly different format for the AdditionalStatus2 // it skips averagePower before totalCalories // averagePower: UInt16LE in watts - bufferBuilder.writeUInt16LE(Math.round(data.power)) + bufferBuilder.writeUInt16LE(Math.round(data.cyclePower)) } - // totalCalories: UInt16LE in cal - bufferBuilder.writeUInt16LE(Math.round(data.caloriesTotal)) + // totalCalories: UInt16LE in kCal + bufferBuilder.writeUInt16LE(Math.round(data.totalCalories)) // splitAveragePace: UInt16LE in 0.01 sec/500m bufferBuilder.writeUInt16LE(0 * 100) // splitAveragePower UInt16LE in watts diff --git a/app/ble/pm5/characteristic/AdditionalStrokeData.js b/app/peripherals/ble/pm5/characteristic/AdditionalStrokeData.js similarity index 78% rename from app/ble/pm5/characteristic/AdditionalStrokeData.js rename to app/peripherals/ble/pm5/characteristic/AdditionalStrokeData.js index 3b6e7bf46a..2a05515806 100644 --- a/app/ble/pm5/characteristic/AdditionalStrokeData.js +++ b/app/peripherals/ble/pm5/characteristic/AdditionalStrokeData.js @@ -38,22 +38,22 @@ export default class AdditionalStrokeData extends bleno.Characteristic { if (this._updateValueCallback || this._multiplexedCharacteristic.centralSubscribed()) { const bufferBuilder = new BufferBuilder() // elapsedTime: UInt24LE in 0.01 sec - bufferBuilder.writeUInt24LE(Math.round(data.durationTotal * 100)) + bufferBuilder.writeUInt24LE(Math.round(data.totalMovingTime * 100)) // strokePower: UInt16LE in watts - bufferBuilder.writeUInt16LE(Math.round(data.power)) + bufferBuilder.writeUInt16LE(Math.round(data.cyclePower)) // strokeCalories: UInt16LE in cal - bufferBuilder.writeUInt16LE(0) + bufferBuilder.writeUInt16LE(Math.round(data.strokeCalories * 1000)) // strokeCount: UInt16LE - bufferBuilder.writeUInt16LE(Math.round(data.strokesTotal)) + bufferBuilder.writeUInt16LE(Math.round(data.totalNumberOfStrokes)) // projectedWorkTime: UInt24LE in 1 sec - bufferBuilder.writeUInt24LE(0) + bufferBuilder.writeUInt24LE(Math.round(data.cycleProjectedEndTime)) // projectedWorkDistance: UInt24LE in 1 m - bufferBuilder.writeUInt24LE(0) + bufferBuilder.writeUInt24LE(Math.round(data.cycleProjectedEndLinearDistance)) if (!this._updateValueCallback) { // the multiplexer uses a slightly different format for the AdditionalStrokeData // it adds workPerStroke at the end - // workPerStroke: UInt16LE - bufferBuilder.writeUInt16LE(0) + // workPerStroke: UInt16LE in 0.1 Joules + bufferBuilder.writeUInt16LE(Math.round(data.strokeWork * 10)) } if (this._updateValueCallback) { diff --git a/app/ble/pm5/characteristic/ControlReceive.js b/app/peripherals/ble/pm5/characteristic/ControlReceive.js similarity index 100% rename from app/ble/pm5/characteristic/ControlReceive.js rename to app/peripherals/ble/pm5/characteristic/ControlReceive.js diff --git a/app/ble/pm5/characteristic/ControlTransmit.js b/app/peripherals/ble/pm5/characteristic/ControlTransmit.js similarity index 100% rename from app/ble/pm5/characteristic/ControlTransmit.js rename to app/peripherals/ble/pm5/characteristic/ControlTransmit.js diff --git a/app/ble/pm5/characteristic/GeneralStatus.js b/app/peripherals/ble/pm5/characteristic/GeneralStatus.js similarity index 58% rename from app/ble/pm5/characteristic/GeneralStatus.js rename to app/peripherals/ble/pm5/characteristic/GeneralStatus.js index 2748b41ba7..b4b7a5aa6a 100644 --- a/app/ble/pm5/characteristic/GeneralStatus.js +++ b/app/peripherals/ble/pm5/characteristic/GeneralStatus.js @@ -38,27 +38,28 @@ export default class GeneralStatus extends bleno.Characteristic { if (this._updateValueCallback || this._multiplexedCharacteristic.centralSubscribed()) { const bufferBuilder = new BufferBuilder() // elapsedTime: UInt24LE in 0.01 sec - bufferBuilder.writeUInt24LE(Math.round(data.durationTotal * 100)) + bufferBuilder.writeUInt24LE(Math.round(data.totalMovingTime * 100)) // distance: UInt24LE in 0.1 m - bufferBuilder.writeUInt24LE(Math.round(data.distanceTotal * 10)) - // workoutType: UInt8 will always use 0 (WORKOUTTYPE_JUSTROW_NOSPLITS) - bufferBuilder.writeUInt8(0) - // intervalType: UInt8 will always use 255 (NONE) - bufferBuilder.writeUInt8(255) + bufferBuilder.writeUInt24LE(Math.round(data.totalLinearDistance * 10)) + // workoutType: UInt8: 0 WORKOUTTYPE_JUSTROW_NOSPLITS, 2 WORKOUTTYPE_FIXEDDIST_NOSPLITS, 4 WORKOUTTYPE_FIXEDTIME_NOSPLITS + bufferBuilder.writeUInt8(data.sessiontype === 'Distance' ? 2 : (data.sessiontype === 'Time' ? 4 : 0)) + // intervalType: UInt8: 1 INTERVALTYPE_TIME, 2 INTERVALTYPE_DIST, 255 NONE + // ToDo: split down further to allow rest intervals when the PM5 schedule dictates it + bufferBuilder.writeUInt8(data.sessiontype === 'Distance' ? 2 : (data.sessiontype === 'Time' ? 1 : 255)) // workoutState: UInt8 0 WAITTOBEGIN, 1 WORKOUTROW, 10 WORKOUTEND - bufferBuilder.writeUInt8(data.sessionState === 'rowing' ? 1 : (data.sessionState === 'waitingForStart' ? 0 : 10)) + bufferBuilder.writeUInt8(data.sessionStatus === 'Rowing' ? 1 : (data.sessionStatus === 'WaitingForStart' ? 0 : 10)) // rowingState: UInt8 0 INACTIVE, 1 ACTIVE - bufferBuilder.writeUInt8(data.sessionState === 'rowing' ? 1 : 0) + bufferBuilder.writeUInt8(data.sessionStatus === 'Rowing' ? 1 : 0) // strokeState: UInt8 2 DRIVING, 4 RECOVERY - bufferBuilder.writeUInt8(data.strokeState === 'DRIVING' ? 2 : 4) + bufferBuilder.writeUInt8(data.strokeState === 'WaitingForDrive' ? 0 : (data.strokeState === 'Drive' ? 2 : 4)) // totalWorkDistance: UInt24LE in 1 m - bufferBuilder.writeUInt24LE(Math.round(data.distanceTotal)) + bufferBuilder.writeUInt24LE(Math.round(data.totalLinearDistance)) // workoutDuration: UInt24LE in 0.01 sec (if type TIME) - bufferBuilder.writeUInt24LE(0 * 100) - // workoutDurationType: UInt8 0 TIME, 1 CALORIES, 2 DISTANCE, 3 WATTS - bufferBuilder.writeUInt8(0) + bufferBuilder.writeUInt24LE(Math.round(data.totalMovingTime * 100)) + // workoutDurationType: UInt8 0 TIME, 0x40 CALORIES, 0x80 DISTANCE, 0xC0 WATTS + bufferBuilder.writeUInt8(data.sessiontype === 'Distance' ? 0x80 : 0) // dragFactor: UInt8 - bufferBuilder.writeUInt8(0) + bufferBuilder.writeUInt8(Math.round(Math.min(data.dragFactor, 255))) if (this._updateValueCallback) { this._updateValueCallback(bufferBuilder.getBuffer()) diff --git a/app/ble/pm5/characteristic/MultiplexedCharacteristic.js b/app/peripherals/ble/pm5/characteristic/MultiplexedCharacteristic.js similarity index 100% rename from app/ble/pm5/characteristic/MultiplexedCharacteristic.js rename to app/peripherals/ble/pm5/characteristic/MultiplexedCharacteristic.js diff --git a/app/ble/pm5/characteristic/StrokeData.js b/app/peripherals/ble/pm5/characteristic/StrokeData.js similarity index 69% rename from app/ble/pm5/characteristic/StrokeData.js rename to app/peripherals/ble/pm5/characteristic/StrokeData.js index ededdb224b..4f69bda2e9 100644 --- a/app/ble/pm5/characteristic/StrokeData.js +++ b/app/peripherals/ble/pm5/characteristic/StrokeData.js @@ -39,28 +39,28 @@ export default class StrokeData extends bleno.Characteristic { if (this._updateValueCallback || this._multiplexedCharacteristic.centralSubscribed()) { const bufferBuilder = new BufferBuilder() // elapsedTime: UInt24LE in 0.01 sec - bufferBuilder.writeUInt24LE(Math.round(data.durationTotal * 100)) + bufferBuilder.writeUInt24LE(Math.round(data.totalMovingTime * 100)) // distance: UInt24LE in 0.1 m - bufferBuilder.writeUInt24LE(Math.round(data.distanceTotal * 10)) + bufferBuilder.writeUInt24LE(Math.round(data.totalLinearDistance * 10)) // driveLength: UInt8 in 0.01 m - bufferBuilder.writeUInt8(0 * 100) + bufferBuilder.writeUInt8(Math.round(data.driveLength * 100)) // driveTime: UInt8 in 0.01 s - bufferBuilder.writeUInt8(0 * 100) + bufferBuilder.writeUInt8(Math.round(data.driveDuration * 100)) // strokeRecoveryTime: UInt16LE in 0.01 s - bufferBuilder.writeUInt16LE(0 * 100) + bufferBuilder.writeUInt16LE(Math.round(data.recoveryDuration * 100)) // strokeDistance: UInt16LE in 0.01 s - bufferBuilder.writeUInt16LE(0 * 100) - // peakDriveForce: UInt16LE in 0.1 watts - bufferBuilder.writeUInt16LE(0 * 10) - // averageDriveForce: UInt16LE in 0.1 watts - bufferBuilder.writeUInt16LE(0 * 10) + bufferBuilder.writeUInt16LE(Math.round(data.cycleDistance * 100)) + // peakDriveForce: UInt16LE in 0.1 lbs + bufferBuilder.writeUInt16LE(Math.round(data.drivePeakHandleForce * 0.224809 * 10)) + // averageDriveForce: UInt16LE in 0.1 lbs + bufferBuilder.writeUInt16LE(Math.round(data.driveAverageHandleForce * 0.224809 * 10)) if (this._updateValueCallback) { // workPerStroke is only added if data is not send via multiplexer - // workPerStroke: UInt16LE - bufferBuilder.writeUInt16LE(0) + // workPerStroke: UInt16LE in 0.1 Joules + bufferBuilder.writeUInt16LE(Math.round(data.strokeWork * 10)) } // strokeCount: UInt16LE - bufferBuilder.writeUInt16LE(data.strokesTotal) + bufferBuilder.writeUInt16LE(data.totalNumberOfStrokes) if (this._updateValueCallback) { this._updateValueCallback(bufferBuilder.getBuffer()) } else { diff --git a/app/server.js b/app/server.js index 32ce7621aa..6534a93000 100644 --- a/app/server.js +++ b/app/server.js @@ -6,19 +6,19 @@ everything together while figuring out the physics and model of the application. todo: refactor this as we progress */ +import os from 'os' import child_process from 'child_process' import { promisify } from 'util' import log from 'loglevel' import config from './tools/ConfigManager.js' -import { createRowingEngine } from './engine/RowingEngine.js' import { createRowingStatistics } from './engine/RowingStatistics.js' import { createWebServer } from './WebServer.js' -import { createPeripheralManager } from './ble/PeripheralManager.js' -import { createAntManager } from './ant/AntManager.js' +import { createPeripheralManager } from './peripherals/PeripheralManager.js' // eslint-disable-next-line no-unused-vars import { replayRowingSession } from './tools/RowingRecorder.js' import { createWorkoutRecorder } from './engine/WorkoutRecorder.js' import { createWorkoutUploader } from './engine/WorkoutUploader.js' +import { secondsToTimeString } from './tools/Helper.js' const exec = promisify(child_process.exec) // set the log levels @@ -31,39 +31,109 @@ for (const [loggerName, logLevel] of Object.entries(config.loglevel)) { log.info(`==== Open Rowing Monitor ${process.env.npm_package_version || ''} ====\n`) +if (config.appPriority) { + // setting the (near-real-time?) priority for the main process, to prevent blocking the GPIO thread + const mainPriority = Math.min((config.appPriority), 0) + log.debug(`Setting priority for the main server thread to ${mainPriority}`) + try { + // setting priority of current process + os.setPriority(mainPriority) + } catch (err) { + log.debug('need root permission to set priority of main server thread') + } +} + +// a hook for setting session parameters that the rower has to obey +// Hopefully this will be filled through the WebGUI or through the BLE interface (PM5-BLE can do this...) +// When set, ORM will terminate the session after reaching the target. If not set, it will behave as usual (a "Just row" session). +// When set, the GUI will behave similar to a PM5 in that it counts down from the target to 0 +const intervalSettings = [] + +/* an example of the workout setting that RowingStatistics will obey: a 1 minute warmup, a 2K timed piece followed by a 1 minute cooldown +// This should normally come from the PM5 interface or the webinterface +intervalSettings[0] = { + targetDistance: 0, + targetTime: 60 +} + +/* Additional intervals for testing +intervalSettings[1] = { + targetDistance: 2000, + targetTime: 0 +} + +intervalSettings[2] = { + targetDistance: 0, + targetTime: 60 +} +*/ + const peripheralManager = createPeripheralManager() peripheralManager.on('control', (event) => { - if (event?.req?.name === 'requestControl') { - event.res = true - } else if (event?.req?.name === 'reset') { - log.debug('reset requested') - resetWorkout() - event.res = true - // todo: we could use these controls once we implement a concept of a rowing session - } else if (event?.req?.name === 'stop') { - log.debug('stop requested') - peripheralManager.notifyStatus({ name: 'stoppedOrPausedByUser' }) - event.res = true - } else if (event?.req?.name === 'pause') { - log.debug('pause requested') - peripheralManager.notifyStatus({ name: 'stoppedOrPausedByUser' }) - event.res = true - } else if (event?.req?.name === 'startOrResume') { - log.debug('startOrResume requested') - peripheralManager.notifyStatus({ name: 'startedOrResumedByUser' }) - event.res = true - } else if (event?.req?.name === 'peripheralMode') { - webServer.notifyClients('config', getConfig()) - event.res = true - } else { - log.info('unhandled Command', event.req) + switch (event?.req?.name) { + case 'requestControl': + event.res = true + break + case 'reset': + log.debug('reset requested') + resetWorkout() + event.res = true + break + // todo: we could use these controls once we implement a concept of a rowing session + case 'stop': + log.debug('stop requested') + stopWorkout() + peripheralManager.notifyStatus({ name: 'stoppedOrPausedByUser' }) + event.res = true + break + case 'pause': + log.debug('pause requested') + pauseWorkout() + peripheralManager.notifyStatus({ name: 'stoppedOrPausedByUser' }) + event.res = true + break + case 'startOrResume': + log.debug('startOrResume requested') + resumeWorkout() + peripheralManager.notifyStatus({ name: 'startedOrResumedByUser' }) + event.res = true + break + case 'blePeripheralMode': + webServer.notifyClients('config', getConfig()) + event.res = true + break + case 'antPeripheralMode': + webServer.notifyClients('config', getConfig()) + event.res = true + break + case 'hrmPeripheralMode': + webServer.notifyClients('config', getConfig()) + event.res = true + break + default: + log.info('unhandled Command', event.req) } }) +peripheralManager.on('heartRateMeasurement', (heartRateMeasurement) => { + rowingStatistics.handleHeartRateMeasurement(heartRateMeasurement) +}) + +function pauseWorkout () { + rowingStatistics.pause() +} + +function stopWorkout () { + rowingStatistics.stop() +} + +function resumeWorkout () { + rowingStatistics.resume() +} + function resetWorkout () { workoutRecorder.reset() - rowingEngine.reset() rowingStatistics.reset() peripheralManager.notifyStatus({ name: 'reset' }) } @@ -71,14 +141,36 @@ function resetWorkout () { const gpioTimerService = child_process.fork('./app/gpio/GpioTimerService.js') gpioTimerService.on('message', handleRotationImpulse) +process.once('SIGINT', async (signal) => { + log.debug(`${signal} signal was received, shutting down gracefully`) + await peripheralManager.shutdownAllPeripherals() + process.exit(0) +}) +process.once('SIGTERM', async (signal) => { + log.debug(`${signal} signal was received, shutting down gracefully`) + await peripheralManager.shutdownAllPeripherals() + process.exit(0) +}) +process.once('uncaughtException', async (error) => { + log.error('Uncaught Exception:', error) + await peripheralManager.shutdownAllPeripherals() + process.exit(1) +}) + function handleRotationImpulse (dataPoint) { workoutRecorder.recordRotationImpulse(dataPoint) - rowingEngine.handleRotationImpulse(dataPoint) + rowingStatistics.handleRotationImpulse(dataPoint) } -const rowingEngine = createRowingEngine(config.rowerSettings) const rowingStatistics = createRowingStatistics(config) -rowingEngine.notify(rowingStatistics) +if (intervalSettings.length > 0) { + // There is an interval defined at startup, let's inform RowingStatistics + // ToDo: update these settings when the PM5 or webinterface tells us to + rowingStatistics.setIntervalParameters(intervalSettings) +} else { + log.info('Starting a just row session, no time or distance target set') +} + const workoutRecorder = createWorkoutRecorder() const workoutUploader = createWorkoutUploader(workoutRecorder) @@ -88,15 +180,10 @@ rowingStatistics.on('driveFinished', (metrics) => { }) rowingStatistics.on('recoveryFinished', (metrics) => { - log.info(`stroke: ${metrics.strokesTotal}, dur: ${metrics.strokeTime.toFixed(2)}s, power: ${Math.round(metrics.power)}w` + - `, split: ${metrics.splitFormatted}, ratio: ${metrics.powerRatio.toFixed(2)}, dist: ${metrics.distanceTotal.toFixed(1)}m` + - `, cal: ${metrics.caloriesTotal.toFixed(1)}kcal, SPM: ${metrics.strokesPerMinute.toFixed(1)}, speed: ${metrics.speed.toFixed(2)}km/h` + - `, cal/hour: ${metrics.caloriesPerHour.toFixed(1)}kcal, cal/minute: ${metrics.caloriesPerMinute.toFixed(1)}kcal`) + logMetrics(metrics) webServer.notifyClients('metrics', metrics) peripheralManager.notifyMetrics('strokeFinished', metrics) - if (metrics.sessionState === 'rowing') { - workoutRecorder.recordStroke(metrics) - } + workoutRecorder.recordStroke(metrics) }) rowingStatistics.on('webMetricsUpdate', (metrics) => { @@ -107,23 +194,36 @@ rowingStatistics.on('peripheralMetricsUpdate', (metrics) => { peripheralManager.notifyMetrics('metricsUpdate', metrics) }) -rowingStatistics.on('rowingPaused', () => { +rowingStatistics.on('rowingPaused', (metrics) => { + logMetrics(metrics) + workoutRecorder.recordStroke(metrics) workoutRecorder.handlePause() + webServer.notifyClients('metrics', metrics) + peripheralManager.notifyMetrics('metricsUpdate', metrics) }) -if (config.heartrateMonitorBLE) { - const bleCentralService = child_process.fork('./app/ble/CentralService.js') - bleCentralService.on('message', (heartrateMeasurement) => { - rowingStatistics.handleHeartrateMeasurement(heartrateMeasurement) - }) -} +rowingStatistics.on('intervalTargetReached', (metrics) => { + // This is called when the RowingStatistics conclude the intervaltarget is reached + // Update all screens to reflect this change, as targetTime and targetDistance have changed + // ToDo: recording this event in the recordings accordingly should be done as well + webServer.notifyClients('metrics', metrics) + peripheralManager.notifyMetrics('metricsUpdate', metrics) +}) -if (config.heartrateMonitorANT) { - const antManager = createAntManager() - antManager.on('heartrateMeasurement', (heartrateMeasurement) => { - rowingStatistics.handleHeartrateMeasurement(heartrateMeasurement) - }) -} +rowingStatistics.on('rowingStopped', (metrics) => { + // This is called when the rowingmachine is stopped for some reason, could be reaching the end of the session, + // could be user intervention + logMetrics(metrics) + workoutRecorder.recordStroke(metrics) + webServer.notifyClients('metrics', metrics) + peripheralManager.notifyMetrics('metricsUpdate', metrics) + workoutRecorder.writeRecordings() +}) + +rowingStatistics.on('HRRecoveryUpdate', (hrMetrics) => { + // This is called at minute intervals after the rowingmachine has stopped, to record the Recovery heartrate in the tcx + workoutRecorder.updateHRRecovery(hrMetrics) +}) workoutUploader.on('authorizeStrava', (data, client) => { webServer.notifyClient(client, 'authorizeStrava', data) @@ -136,40 +236,29 @@ workoutUploader.on('resetWorkout', () => { const webServer = createWebServer() webServer.on('messageReceived', async (message, client) => { switch (message.command) { - case 'switchPeripheralMode': { - peripheralManager.switchPeripheralMode() + case 'switchBlePeripheralMode': + peripheralManager.switchBlePeripheralMode() break - } - case 'reset': { + case 'switchAntPeripheralMode': + peripheralManager.switchAntPeripheralMode() + break + case 'switchHrmMode': + peripheralManager.switchHrmMode() + break + case 'reset': resetWorkout() break - } - case 'uploadTraining': { + case 'uploadTraining': workoutUploader.upload(client) break - } - case 'shutdown': { - if (getConfig().shutdownEnabled) { - console.info('shutting down device...') - try { - const { stdout, stderr } = await exec(config.shutdownCommand) - if (stderr) { - log.error('can not shutdown: ', stderr) - } - log.info(stdout) - } catch (error) { - log.error('can not shutdown: ', error) - } - } + case 'shutdown': + await shutdown() break - } - case 'stravaAuthorizationCode': { + case 'stravaAuthorizationCode': workoutUploader.stravaAuthorizationCode(message.data) break - } - default: { + default: log.warn('invalid command received:', message) - } } }) @@ -180,16 +269,42 @@ webServer.on('clientConnected', (client) => { // todo: extract this into some kind of state manager function getConfig () { return { - peripheralMode: peripheralManager.getPeripheralMode(), + blePeripheralMode: peripheralManager.getBlePeripheralMode(), + antPeripheralMode: peripheralManager.getAntPeripheralMode(), + hrmPeripheralMode: peripheralManager.getHrmPeripheralMode(), stravaUploadEnabled: !!config.stravaClientId && !!config.stravaClientSecret, shutdownEnabled: !!config.shutdownCommand } } +// This shuts down the pi, use with caution! +async function shutdown () { + stopWorkout() + if (getConfig().shutdownEnabled) { + console.info('shutting down device...') + try { + const { stdout, stderr } = await exec(config.shutdownCommand) + if (stderr) { + log.error('can not shutdown: ', stderr) + } + log.info(stdout) + } catch (error) { + log.error('can not shutdown: ', error) + } + } +} + +function logMetrics (metrics) { + log.info(`stroke: ${metrics.totalNumberOfStrokes}, dist: ${metrics.totalLinearDistance.toFixed(1)}m, speed: ${metrics.cycleLinearVelocity.toFixed(2)}m/s` + + `, pace: ${secondsToTimeString(metrics.cyclePace)}/500m, power: ${Math.round(metrics.cyclePower)}W, cal: ${metrics.totalCalories.toFixed(1)}kcal` + + `, SPM: ${metrics.cycleStrokeRate.toFixed(1)}, drive dur: ${metrics.driveDuration.toFixed(2)}s, rec. dur: ${metrics.recoveryDuration.toFixed(2)}s` + + `, stroke dur: ${metrics.cycleDuration.toFixed(2)}s`) +} + /* replayRowingSession(handleRotationImpulse, { - filename: 'recordings/WRX700_2magnets.csv', + filename: 'recordings/Concept2_RowErg_Session_2000meters.csv', // Example row from a Concept 2 RowErg, 2000 meters realtime: true, - loop: true + loop: false }) */ diff --git a/app/tools/ConfigManager.js b/app/tools/ConfigManager.js index ed4575b523..1a3a0aba23 100644 --- a/app/tools/ConfigManager.js +++ b/app/tools/ConfigManager.js @@ -3,19 +3,214 @@ Open Rowing Monitor, https://github.com/laberning/openrowingmonitor Merges the different config files and presents the configuration to the application + Checks the config for plausibilit, fixes the errors when needed */ import defaultConfig from '../../config/default.config.js' import { deepMerge } from './Helper.js' +import log from 'loglevel' async function getConfig () { let customConfig try { customConfig = await import('../../config/config.js') } catch (exception) {} - return customConfig !== undefined ? deepMerge(defaultConfig, customConfig.default) : defaultConfig } +function checkConfig (configToCheck) { + checkRangeValue(configToCheck.loglevel, 'default', ['trace', 'debug', 'info', 'warn', 'error', 'silent'], true, 'error') + checkRangeValue(configToCheck.loglevel, 'RowingEngine', ['trace', 'debug', 'info', 'warn', 'error', 'silent'], true, 'error') + checkIntegerValue(configToCheck, 'gpioPin', 1, 27, false, false, null) + checkIntegerValue(configToCheck, 'gpioPriority', -7, 0, true, true, 0) + checkIntegerValue(configToCheck, 'gpioMinimumPulseLength', 1, null, false, true, 0) + checkIntegerValue(configToCheck, 'gpioPollingInterval', 1, 10, false, true, 10) + checkRangeValue(configToCheck, 'gpioPollingInterval', [1, 2, 5, 10], true, 10) + checkRangeValue(configToCheck, 'gpioTriggeredFlank', ['Up', 'Down', 'Both'], true, 'Up') + checkIntegerValue(configToCheck, 'appPriority', configToCheck.gpioPriority, 0, true, true, 0) + checkIntegerValue(configToCheck, 'webUpdateInterval', 80, 1000, false, true, 1000) + checkIntegerValue(configToCheck, 'peripheralUpdateInterval', 80, 1000, false, true, 1000) + checkRangeValue(configToCheck, 'bluetoothMode', ['OFF', 'PM5', 'FTMS', 'FTMSBIKE', 'CPS', 'CSC'], true, 'OFF') + checkRangeValue(configToCheck, 'antplusMode', ['OFF', 'FE'], true, 'OFF') + checkRangeValue(configToCheck, 'heartRateMode', ['OFF', 'ANT', 'BLE'], true, 'OFF') + checkIntegerValue(configToCheck, 'numOfPhasesForAveragingScreenData', 2, null, false, true, 4) + checkBooleanValue(configToCheck, 'createRowingDataFiles', true, true) + checkBooleanValue(configToCheck, 'createRawDataFiles', true, true) + checkBooleanValue(configToCheck, 'gzipRawDataFiles', true, false) + checkBooleanValue(configToCheck, 'createTcxFiles', true, true) + checkBooleanValue(configToCheck, 'gzipTcxFiles', true, false) + checkFloatValue(configToCheck.userSettings, 'restingHR', 30, 220, false, true, 40) + checkFloatValue(configToCheck.userSettings, 'maxHR', configToCheck.userSettings.restingHR, 220, false, true, 220) + if (configToCheck.createTcxFiles) { + checkFloatValue(configToCheck.userSettings, 'minPower', 1, 500, false, true, 50) + checkFloatValue(configToCheck.userSettings, 'maxPower', 100, 6000, false, true, 500) + checkFloatValue(configToCheck.userSettings, 'distanceCorrectionFactor', 0, 50, false, true, 5) + checkFloatValue(configToCheck.userSettings, 'weight', 25, 500, false, true, 80) + checkRangeValue(configToCheck.userSettings, 'sex', ['male', 'female'], true, 'male') + checkBooleanValue(configToCheck.userSettings, 'highlyTrained', true, false) + } + checkIntegerValue(configToCheck.rowerSettings, 'numOfImpulsesPerRevolution', 1, null, false, false, null) + checkIntegerValue(configToCheck.rowerSettings, 'flankLength', 3, null, false, false, null) + checkFloatValue(configToCheck.rowerSettings, 'sprocketRadius', 0, 20, false, true, 3) + checkFloatValue(configToCheck.rowerSettings, 'minimumTimeBetweenImpulses', 0, 3, false, false, null) + checkFloatValue(configToCheck.rowerSettings, 'maximumTimeBetweenImpulses', configToCheck.rowerSettings.minimumTimeBetweenImpulses, 3, false, false, null) + checkFloatValue(configToCheck.rowerSettings, 'smoothing', 1, null, false, true, 1) + checkFloatValue(configToCheck.rowerSettings, 'dragFactor', 1, null, false, false, null) + checkBooleanValue(configToCheck.rowerSettings, 'autoAdjustDragFactor', true, false) + checkIntegerValue(configToCheck.rowerSettings, 'dragFactorSmoothing', 1, null, false, true, 1) + if (configToCheck.rowerSettings.autoAdjustDragFactor) { + checkFloatValue(configToCheck.rowerSettings, 'minimumDragQuality', 0, 1, true, true, 0) + } + checkFloatValue(configToCheck.rowerSettings, 'flywheelInertia', 0, null, false, false, null) + checkFloatValue(configToCheck.rowerSettings, 'minumumForceBeforeStroke', 0, 500, true, true, 0) + checkFloatValue(configToCheck.rowerSettings, 'minumumRecoverySlope', 0, null, true, true, 0) + checkFloatValue(configToCheck.rowerSettings, 'minimumStrokeQuality', 0, 1, true, true, 0) + checkBooleanValue(configToCheck.rowerSettings, 'autoAdjustRecoverySlope', true, false) + if (!configToCheck.rowerSettings.autoAdjustDragFactor && configToCheck.rowerSettings.autoAdjustRecoverySlope) { + log.error('Configuration Error: rowerSettings.autoAdjustRecoverySlope can not be true when rowerSettings.autoAdjustDragFactor is false, ignoring request') + } + if (configToCheck.rowerSettings.autoAdjustDragFactor && configToCheck.rowerSettings.autoAdjustRecoverySlope) { + checkFloatValue(configToCheck.rowerSettings, 'autoAdjustRecoverySlopeMargin', 0, 1, false, true, 1) + } + checkFloatValue(configToCheck.rowerSettings, 'minimumDriveTime', 0, null, false, true, 0.001) + checkFloatValue(configToCheck.rowerSettings, 'minimumRecoveryTime', 0, null, false, true, 0.001) + checkFloatValue(configToCheck.rowerSettings, 'maximumStrokeTimeBeforePause', 3, 60, false, true, 6) + checkFloatValue(configToCheck.rowerSettings, 'magicConstant', 0, null, false, true, 2.8) +} + +function checkIntegerValue (parameterSection, parameterName, minimumValue, maximumvalue, allowZero, allowRepair, defaultValue) { + // PLEASE NOTE: the parameterSection, parameterName seperation is needed to force a call by reference, which is needed for the repair action + let errors = 0 + switch (true) { + case (parameterSection[parameterName] === undefined): + log.error(`Configuration Error: ${parameterSection}.${parameterName} isn't defined`) + errors++ + break + case (!Number.isInteger(parameterSection[parameterName])): + log.error(`Configuration Error: ${parameterSection}.${parameterName} should be an integer value, encountered ${parameterSection[parameterName]}`) + errors++ + break + case (minimumValue != null && parameterSection[parameterName] < minimumValue): + log.error(`Configuration Error: ${parameterSection}.${parameterName} should be at least ${minimumValue}, encountered ${parameterSection[parameterName]}`) + errors++ + break + case (maximumvalue != null && parameterSection[parameterName] > maximumvalue): + log.error(`Configuration Error: ${parameterSection}.${parameterName} can't be above ${maximumvalue}, encountered ${parameterSection[parameterName]}`) + errors++ + break + case (!allowZero && parameterSection[parameterName] === 0): + log.error(`Configuration Error: ${parameterSection}.${parameterName} can't be zero`) + errors++ + break + default: + // No error detected :) + } + if (errors > 0) { + // Errors were made + if (allowRepair) { + log.error(` resolved by setting ${parameterSection}.${parameterName} to ${defaultValue}`) + parameterSection[parameterName] = defaultValue + } else { + log.error(` as ${parameterSection}.${parameterName} is a fatal parameter, I'm exiting`) + process.exit(9) + } + } +} + +function checkFloatValue (parameterSection, parameterName, minimumValue, maximumvalue, allowZero, allowRepair, defaultValue) { + // PLEASE NOTE: the parameterSection, parameterName seperation is needed to force a call by reference, which is needed for the repair action + let errors = 0 + switch (true) { + case (parameterSection[parameterName] === undefined): + log.error(`Configuration Error: ${parameterSection}.${parameterName} isn't defined`) + errors++ + break + case (!(typeof (parameterSection[parameterName]) === 'number')): + log.error(`Configuration Error: ${parameterSection}.${parameterName} should be a numerical value, encountered ${parameterSection[parameterName]}`) + errors++ + break + case (minimumValue != null && parameterSection[parameterName] < minimumValue): + log.error(`Configuration Error: ${parameterSection}.${parameterName} should be at least ${minimumValue}, encountered ${parameterSection[parameterName]}`) + errors++ + break + case (maximumvalue != null && parameterSection[parameterName] > maximumvalue): + log.error(`Configuration Error: ${parameterSection}.${parameterName} can't be above ${maximumvalue}, encountered ${parameterSection[parameterName]}`) + errors++ + break + case (!allowZero && parameterSection[parameterName] === 0): + log.error(`Configuration Error: ${parameterSection}.${parameterName} can't be zero`) + errors++ + break + default: + // No error detected :) + } + if (errors > 0) { + // Errors were made + if (allowRepair) { + log.error(` resolved by setting ${parameterSection}.${parameterName} to ${defaultValue}`) + parameterSection[parameterName] = defaultValue + } else { + log.error(` as ${parameterSection}.${parameterName} is a fatal parameter, I'm exiting`) + process.exit(9) + } + } +} + +function checkBooleanValue (parameterSection, parameterName, allowRepair, defaultValue) { + // PLEASE NOTE: the parameterSection, parameterName seperation is needed to force a call by reference, which is needed for the repair action + let errors = 0 + switch (true) { + case (parameterSection[parameterName] === undefined): + log.error(`Configuration Error: ${parameterSection}.${parameterName} isn't defined`) + errors++ + break + case (!(parameterSection[parameterName] === true || parameterSection[parameterName] === false)): + log.error(`Configuration Error: ${parameterSection}.${parameterName} should be either false or true, encountered ${parameterSection[parameterName]}`) + errors++ + break + default: + // No error detected :) + } + if (errors > 0) { + // Errors were made + if (allowRepair) { + log.error(` resolved by setting ${parameterSection}.${parameterName} to ${defaultValue}`) + parameterSection[parameterName] = defaultValue + } else { + log.error(` as ${parameterSection}.${parameterName} is a fatal parameter, I'm exiting`) + process.exit(9) + } + } +} + +function checkRangeValue (parameterSection, parameterName, range, allowRepair, defaultValue) { + // PLEASE NOTE: the parameterSection, parameterName seperation is needed to force a call by reference, which is needed for the repair action + let errors = 0 + switch (true) { + case (parameterSection[parameterName] === undefined): + log.error(`Configuration Error: ${parameterSection}.${parameterName} isn't defined`) + errors++ + break + case (!range.includes(parameterSection[parameterName])): + log.error(`Configuration Error: ${parameterSection}.${parameterName} should be come from ${range}, encountered ${parameterSection[parameterName]}`) + errors++ + break + default: + // No error detected :) + } + if (errors > 0) { + // Errors were made + if (allowRepair) { + log.error(` resolved by setting ${parameterSection}.${parameterName} to ${defaultValue}`) + parameterSection[parameterName] = defaultValue + } else { + log.error(` as ${parameterSection}.${parameterName} is a fatal parameter, I'm exiting`) + process.exit(9) + } + } +} + const config = await getConfig() +checkConfig(config) + export default config diff --git a/app/tools/Helper.js b/app/tools/Helper.js index 63f388e04a..2a809fe709 100644 --- a/app/tools/Helper.js +++ b/app/tools/Helper.js @@ -26,3 +26,29 @@ export function deepMerge (...objects) { return prev }, {}) } + +// converts a timeStamp in seconds to a human readable hh:mm:ss format +export function secondsToTimeString (secondsTimeStamp) { + if (secondsTimeStamp === Infinity) return '∞' + const hours = Math.floor(secondsTimeStamp / 60 / 60) + const minutes = Math.floor(secondsTimeStamp / 60) - (hours * 60) + const seconds = Math.floor(secondsTimeStamp % 60) + if (hours > 0) { + return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}` + } else { + return `${minutes}:${seconds.toString().padStart(2, '0')}` + } +} + +/** + * Pipe for formatting numbers to specific decimal + * + * @param {number} value The number. + * @param {number} decimalPlaces The number of decimal places to round to (default: 0). +*/ +export function formatNumber (value, decimalPlaces = 0) { + const decimal = Math.pow(10, decimalPlaces) + if (value === undefined || value === null || value === Infinity || isNaN(value) || value === 0) { return '--' } + + return Math.round(value * decimal) / decimal +} diff --git a/config/default.config.js b/config/default.config.js index e493e26163..099bc400de 100644 --- a/config/default.config.js +++ b/config/default.config.js @@ -29,24 +29,93 @@ export default { // see: https://www.raspberrypi.org/documentation/configuration/config-txt/gpio.md gpioPin: 17, - // Experimental setting: enable this to boost the system level priority of the thread that - // measures the rotation speed of the flywheel. This might improve the precision of the - // measurements (especially on rowers with a fast spinning flywheel) - gpioHighPriority: false, - - // Selects the Bluetooth Low Energy Profile - // Supported modes: FTMS, FTMSBIKE, PM5 + // Enable this to boost or reduce the system level priority of the thread that measures the rotation + // speed of the flywheel. This might improve the precision of the measurements (especially + // on rowers with a fast spinning flywheel). + // This is the Linux NICE level: minimum setting is +19, theoretical maximum setting is -20 + // Setting this below -1 on a non-PREEMPT kernel might cause the app to crash + // Going beyond -7 on a PREEMPT kernel seems to kill the timing of the app + // 0 keeps the systems default value. + // Also note that you will require root permissions if you set anything other than 0 here + gpioPriority: 0, + + // GPIO polling interval: this is the interval at which the GPIO is inspected for state + // changes on the gpioPin, in microseconds (us). + // Valid values are 1 (i.e. 1,000,000 samples per second), 2 (i.e. 500,000 per second), + // 4 (i.e. 250,000 per second), 5 (i.e. 200,000 per second) and 10 (i.e. 100,000 per second). + // A high sample rate will burden your CPU more. Normal value is 5us. + // A raspberry pi 4 can handle a polling interval of 1 us, which results in a 16% CPU load. + gpioPollingInterval: 5, + + // Type of flank: what flank should be detected by the GPIO detection? + // Valid values are 'Up' for the upward flank, 'Down' for the downward flank, 'Both' for both flanks + // In practice, it shouldn't matter much which flank you detect, although in the presence of debounce, + // a specific flank might provide better filtering capabilities. + // Some machines are even capable of using both, but this requires a strong symmetry in the signal. + gpioTriggeredFlank: 'Up', + + // Minumum pulse length in microseconds. This is the minimum pulse length (i.e. a magnet should be + // present) before Open Rowing Monitor considers it a signal valid. Increasing this value reduces ghost readings + // due to bouncing reed switches etc., which typically are detected as very short measurements in the raw logs. + // Making this too long results in missed impulses. Both can be detected in the raw logs easily. + // Normal value is 50 us, but for some rowers, values up to 500 us are known to work. + gpioMinimumPulseLength: 50, + + // Enable this to boost or reduce the system level priority of the thread that processes the flywheel and HR data + // Although this process is not time critical per se, it could get caught up in Linux housekeeping tasks, preventing + // it to process data in a timely manner. + // This is the Linux NICE level: minimum setting is +19, theoretical maximum setting is -20 + // Setting this below -1 on a non-PREEMPT kernel might cause the app to crash + // Going beyond -5 on a PREEMPT kernel seems to kill the timing of the app + // 0 keeps the systems default value. + // Please make sure the app has a less high priority than gpioPriority + // Also note that you will require root permissions if you set anything other than 0 here + appPriority: 0, + + // Selects the Bluetooth Low Energy Profile that is broadcasted to external peripherals and apps. Supported modes: + // - PM5: the Concept2 PM5 emulator (not functionally complete yet) + // - FTMS: the FTMS profile for rowing machines + // - FTMSBIKE: The FTMS profile is used by Smart Bike Trainers (please note: the speed and power are still aimed for rowing, NOT for a bike!) + // - CPS: The BLE Cycling Power Profile simulates a bike for more modern Garmin watches + // - CSC: The BLE Cycling Speed and Cadence Profile simulates a bike for older Garmin watches + // - OFF: Turns Bluetooth advertisement off bluetoothMode: 'FTMS', - // Turn this on if you want support for Bluetooth Low Energy heart rate monitors - // Will currenty connect to the first device found - heartrateMonitorBLE: true, + // Selects the AN+ that is broadcasted to external peripherals and apps. Supported modes: + // - FE: ANT+ Fitness Equipment + // - OFF: Turns Bluetooth advertisement off + antplusMode: 'OFF', + + // Selects the heart rate monitor mode. Supported modes: + // - BLE: Use Bluetooth Low Energy to connect Heart Rate Monitor (Will currently connect to the first device found) + // - ANT: Use Ant+ to connect Heart Rate Monitor + // - OFF: turns of Heart Rate Monitor discovery + heartRateMode: 'OFF', + + // Defines the name that is used to announce the FTMS Rower via Bluetooth Low Energy (BLE) + // Some rowing training applications expect that the rowing device is announced with a certain name + ftmsRowerPeripheralName: 'OpenRowingMonitor', + + // Defines the name that is used to announce the FTMS Bike via Bluetooth Low Energy (BLE) + // Most bike training applications are fine with any device name + ftmsBikePeripheralName: 'OpenRowingBike', + + // The interval for updating all web clients (i.e. the monitor) in miliseconds + // Advised is to update at least once per second (1000 ms), to make sure the timer moves nice and smoothly. + // Around 100 ms results in a very smooth update experience for distance as well + // Please note that a smaller value will use more network and cpu ressources + webUpdateInterval: 200, + + // Interval between updates of the bluetooth devices (miliseconds) + // Advised is to update at least once per second, as consumers expect this interval + // Some apps, like EXR like a more frequent interval of 200 ms to better sync the stroke + peripheralUpdateInterval: 1000, - // Turn this on if you want support for ANT+ heart rate monitors - // You will need an ANT+ USB stick for this to work, the following models might work: - // - Garmin USB or USB2 ANT+ or an off-brand clone of it (ID 0x1008) - // - Garmin mini ANT+ (ID 0x1009) - heartrateMonitorANT: false, + // The number of stroke phases (i.e. Drive or Recovery) used to smoothen the data displayed on your + // screens (i.e. the monitor, but also bluetooth devices, etc.) and recorded data. A nice smooth experience is found at 6 + // phases, a much more volatile (but more accurate and responsive) is found around 3. The minimum is 2, + // but for recreational rowers that might feel much too restless to be useful + numOfPhasesForAveragingScreenData: 6, // The directory in which to store user specific content // currently this directory holds the recorded training sessions @@ -55,6 +124,9 @@ export default { // Stores the training sessions as TCX files createTcxFiles: true, + // Stores the (in-)stroke data in OpenRowingData CSV files + createRowingDataFiles: true, + // Stores the raw sensor data in CSV files createRawDataFiles: false, @@ -64,34 +136,40 @@ export default { // you will have to unzip the files before uploading gzipTcxFiles: false, - // Apply gzip compression to the ras sensor data recording files (csv.gz) + // Apply gzip compression to the raw sensor data recording files (csv.gz) gzipRawDataFiles: true, - // Defines the name that is used to announce the FTMS Rower via Bluetooth Low Energy (BLE) - // Some rowing training applications expect that the rowing device is announced with a certain name - ftmsRowerPeripheralName: 'OpenRowingMonitor', + // EXPERIMENTAL: Settings used for the VO2 Max calculation that is embedded in the tcx file comments + userSettings: { - // Defines the name that is used to announce the FTMS Bike via Bluetooth Low Energy (BLE) - // Most bike training applications are fine with any device name - ftmsBikePeripheralName: 'OpenRowingBike', + // The resting Heartrate of the user, to filter abnomral HR values + restingHR: 40, - // The interval for updating all web clients (i.e. the monitor) in ms. - // Advised is to update at least once per second, to make sure the timer moves nice and smoothly. - // Around 100 ms results in a very smooth update experience - // Please note that a smaller value will use more network and cpu ressources - webUpdateInterval: 1000, + // The maximum observed heartrate during the last year. If unknown, you can use maxHr = 220 - age + // This is used for filtering abnormal HR values and to project the maximum power a rower produces + maxHR: 190, - // The number of stroke phases (i.e. Drive or Recovery) used to smoothen the data displayed on your - // screens (i.e. the monitor, but also bluetooth devices, etc.). A nice smooth experience is found at 6 - // phases, a much more volatile (but more accurate and responsive) is found around 3. The minimum is 1, - // but for recreational rowers that might feel much too restless to be useful - numOfPhasesForAveragingScreenData: 6, + // The minimum power a rower can produce, used for filtering abnormal power values + minPower: 50, + + // The maximum power a rower can produce, used for filtering abnormal power values + maxPower: 500, - // The time between strokes in seconds before the rower considers it a pause. Default value is set to 10. - // It is not recommended to go below this value, as not recognizing a stroke could result in a pause - // (as a typical stroke is between 2 to 3 seconds for recreational rowers). Increase it when you have - // issues with your stroke detection and the rower is pausing unexpectedly - maximumStrokeTime: 10, + // The effect that doubling the distance has on the maximum achievable average pace. The proposed 5 is based on Paul's law, + // which states that doubling the distance leads to a slowdown in pace of 5 seconds. This value can be adapted if you know + // your PR (this pace) on both a 1000 meters and on 2000 meters, by substracting the pace. + distanceCorrectionFactor: 5, + + // The weight of the rower in kilograms + weight: 80, + + // The sex of the rower, as it is needed for Concept 2's calculation + // This can be "male" or "female" + sex: 'male', + + // See for this definition: https://www.concept2.com/indoor-rowers/training/calculators/vo2max-calculator + highlyTrained: false + }, // The rower specific settings. Either choose a profile from config/rowerProfiles.js or // define the settings individually. If you find good settings for a new rowing device diff --git a/config/rowerProfiles.js b/config/rowerProfiles.js index 6d37351321..3cc5c4f61e 100644 --- a/config/rowerProfiles.js +++ b/config/rowerProfiles.js @@ -4,9 +4,10 @@ This file contains the rower specific settings for different models of ergometers. - These have been generated by the community. If your rower is not listed here and you did find - good settings for your rowing device please send them to us (together with a raw recording of - 10 strokes) so we can add the device here. + These have been generated by the community. If your rower is not listed here, please follow + https://github.com/laberning/openrowingmonitor/blob/main/docs/rower_settings.md to find the right settings + After you found good settings for your rowing device please send them to us (together with a raw recording + of at least 10 strokes) so we can add the device here and start to maintain it. */ export default { @@ -16,36 +17,44 @@ export default { // i.e. the number of magnets if used with a reed sensor numOfImpulsesPerRevolution: 1, + // How big the sprocket is that attaches your belt/chain to your flywheel. This determines both the force on the handle + // as well as the drive length. If all goes well, you end up with average forces around 400 to 800 N and drive lengths around 1.20 to 1.35 m + sprocketRadius: 7.0, + // NOISE FILTER SETTINGS // Filter Settings to reduce noise in the measured data - // Minimum and maximum duration between impulses in seconds during active rowing. Measurements outside of this range - // are replaced by a default value. + // Minimum and maximum duration between impulses in seconds during active rowing. Measurements above the maximum are filtered, so setting these liberaly + // might help here minimumTimeBetweenImpulses: 0.014, maximumTimeBetweenImpulses: 0.5, - // Percentage change between successive intervals before measurements are considered invalid - maximumDownwardChange: 0.25, // effectively the maximum acceleration - maximumUpwardChange: 1.75, // effectively the maximum decceleration - // Smoothing determines the length of the running average for certain volatile measurements, 1 effectively turns it off + + // Smoothing determines the length of the running average for filtering the currentDt, 1 effectively turns it off smoothing: 1, // STROKE DETECTION SETTINGS // Flank length determines the minimum number of consecutive increasing/decreasing measuments that are needed before the stroke detection // considers a drive phase change - // numberOfErrorsAllowed allows for a more noisy approach, but shouldn't be needed - flankLength: 2, - numberOfErrorsAllowed: 0, + flankLength: 3, + + // This is the minimum force that has to be on the handle before ORM considers it a stroke, in Newtons. So this is about 2 Kg or 4.4 Lbs. + minumumForceBeforeStroke: 20, + + // The minimal inclination of the currentDt's before it is considered a recovery. When set to 0, it will look for a pure increase/decrease + minumumRecoverySlope: 0, + + // The minimum quality level of the stroke detection: 1.0 is perfect, 0.1 pretty bad. Normally around 0.33. Setting this too high will stop + // the recovery phase from being detected through the slope angle (i.e. it will completely rely on the absence of the minumumForceBeforeStroke). + minimumStrokeQuality: 0.34, + + // ORM can automatically calculate the recovery slope and adjust it dynamically. For this to work, autoAdjustDragFactor MUST be set to true + autoAdjustRecoverySlope: false, - // Natural deceleration is used to distinguish between a powered and unpowered flywheel. - // This must be a NEGATIVE number and indicates the level of deceleration required to interpret it as a free spinning - // flywheel. The best way to find the correct value for your rowing machine is a try and error approach. - // You can also set this to zero (or positive), to use the more robust, but not so precise acceleration-based stroke - // detection algorithm. - naturalDeceleration: 0, + // The margin used between the automatically calculated recovery slope and a next recovery. Don't touch unless you know what you are doing. + autoAdjustRecoverySlopeMargin: 0.05, // Error reducing settings for the rowing phase detection (in seconds) - maximumImpulseTimeBeforePause: 3.0, // maximum time between impulses before the rowing engine considers it a pause minimumDriveTime: 0.300, // minimum time of the drive phase - minimumRecoveryTime: 1.200, // minimum time of the recovery phase + minimumRecoveryTime: 0.900, // minimum time of the recovery phase // Needed to determine the drag factor of the rowing machine. This value can be measured in the recovery phase // of the stroke. @@ -64,23 +73,23 @@ export default { // When your machine's power and speed readings are too volatile it is wise to turn it off autoAdjustDragFactor: false, - // The moment of inertia of the flywheel kg*m^2, which is ONLY relevant when autoAdjustDragFactor is set to true or when you - // use Force Curves. Otherwise this value isn't relevant to your rower + // If autoAdjustDragFactor is set to true, it will calculate the drag each recovery phase and update it accordingly to calculate speed, + // distance, etc.. As this calculation that is prone to noise in the measuremnts, it is wise to apply smoothing to prevent this noise + // from throwing off your key metrics. The default value is a running average of the drag factor of 5 strokes + dragFactorSmoothing: 5, + + // When drag is calculated, we also get a quality indication. Based on this quality indication (1.0 is best, 0.1 pretty bad), low quality + // drag factors are rejected to prevent drag poisoning + minimumDragQuality: 0.83, + + // The moment of inertia of the flywheel kg*m^2 // A way to measure it is outlined here: https://dvernooy.github.io/projects/ergware/, "Flywheel moment of inertia" // You could also roughly estimate it by just doing some strokes and the comparing the calculated power values for // plausibility. Note that the power also depends on the drag factor (see above). flywheelInertia: 0.5, - // If autoAdjustDragFactor is set to true, it will calculate the drag each recovery phase and update it accordingly to calculate speed, - // distance, etc.. As this calculation that is prone to noise in the measuremnts, it is wise to apply smoothing to prevent this noise - // from throwing off your key metrics. The default value is a running average of the drag factor of 5 strokes - dampingConstantSmoothing: 5, - - // Another setting for when autoAdjustDragFactor is set to true: the maximum allowed change from the current value. Spikes usually imply - // measurement errors, so this setting determines the maximum change with respect to the current dragfactor. Please note that this filter - // will prevent large changes, but will still move the dragfactor upward/downward to prevent it from being stuck. The value is in maximum - // allowed change. The default value of 0.10 implies that the maximum upward/downward change is an increase of the drag with 10%. - dampingConstantMaxChange: 0.10, + // The time before a stroke is considered paused + maximumStrokeTimeBeforePause: 6.0, // A constant that is commonly used to convert flywheel revolutions to a rowed distance // see here: http://eodg.atm.ox.ac.uk/user/dudhia/rowing/physics/ergometer.html#section9 @@ -91,99 +100,108 @@ export default { magicConstant: 2.8 }, - // Sportstech WRX700 - WRX700: { - numOfImpulsesPerRevolution: 2, - naturalDeceleration: -5.0, - flywheelInertia: 0.72, - dragFactor: 32000 + // Cheap Clone of Concept2 RowErg Model D + // https://zocobodyfit.ro/produs/aparat-de-vaslit-zoco-body-fit-air-rower-pliabil-ecran-lcd-eficient-si-util-negru/ + Generic_Air_Rower: { + numOfImpulsesPerRevolution: 1, + sprocketRadius: 1.55, + minimumTimeBetweenImpulses: 0.007, + smoothing: 1, + flankLength: 6, + minumumForceBeforeStroke: 2, + minimumStrokeQuality: 0.6, + minimumDriveTime: 0.200, // minimum time of the drive phase + minimumRecoveryTime: 0.600, // minimum time of the recovery phase + dragFactor: 108, + autoAdjustDragFactor: true, + dragFactorSmoothing: 1, + minimumDragQuality: 0.97, + flywheelInertia: 0.073, + maximumStrokeTimeBeforePause: 6.0 + }, + + // Concept2 RowErg, Model D, E and RowErg + Concept2_RowErg: { + numOfImpulsesPerRevolution: 6, + sprocketRadius: 1.4, + maximumStrokeTimeBeforePause: 6.0, + dragFactor: 110, + autoAdjustDragFactor: true, + minimumDragQuality: 0.95, + dragFactorSmoothing: 3, + minimumTimeBetweenImpulses: 0.005, + maximumTimeBetweenImpulses: 0.0145, + flankLength: 12, + smoothing: 1, + minimumStrokeQuality: 0.36, + minumumForceBeforeStroke: 11, + minumumRecoverySlope: 0.00070, + autoAdjustRecoverySlope: true, + autoAdjustRecoverySlopeMargin: 0.01, + minimumDriveTime: 0.40, + minimumRecoveryTime: 0.90, + flywheelInertia: 0.10148, + magicConstant: 2.8 }, // DKN R-320 Air Rower - DKNR320: { + DKN_R320: { numOfImpulsesPerRevolution: 1, flywheelInertia: 0.94, dragFactor: 8522 }, + // Force USA R3 Air Rower + ForceUSA_R3: { + numOfImpulsesPerRevolution: 6, + minimumTimeBetweenImpulses: 0.005, + maximumTimeBetweenImpulses: 0.022, + flywheelInertia: 0.1015, + flankLength: 10, + dragFactor: 135, + autoAdjustDragFactor: true, + // new engine settings + sprocketRadius: 1.5, + minimumStrokeQuality: 0.50, + minumumRecoverySlope: 0.00070, + autoAdjustRecoverySlope: true, + autoAdjustRecoverySlopeMargin: 0.035, + minumumForceBeforeStroke: 20, + minimumDriveTime: 0.46, + minimumRecoveryTime: 0.80, + minimumDragQuality: 0.83, + dragFactorSmoothing: 3, + maximumStrokeTimeBeforePause: 4 + }, + // NordicTrack RX800 Air Rower - RX800: { + NordicTrack_RX800: { numOfImpulsesPerRevolution: 4, - /* Damper setting 10 - minimumTimeBetweenImpulses: 0.018, - maximumTimeBetweenImpulses: 0.0338, - smoothing: 3, - maximumDownwardChange: 0.88, - maximumUpwardChange: 1.11, - flankLength: 9, - numberOfErrorsAllowed: 2, - naturalDeceleration: -11.5, // perfect runs - minimumDriveTime: 0.40, - minimumRecoveryTime: 0.90, - flywheelInertia: 0.146, - dragFactor: 560 - */ - - /* Damper setting 8 - minimumTimeBetweenImpulses: 0.017, - maximumTimeBetweenImpulses: 0.034, - smoothing: 3, - maximumDownwardChange: 0.8, - maximumUpwardChange: 1.15, - flankLength: 9, - numberOfErrorsAllowed: 2, - naturalDeceleration: -10.25, // perfect runs - minimumDriveTime: 0.30, - minimumRecoveryTime: 0.90, - flywheelInertia: 0.131, - dragFactor: 440 - */ - - // Damper setting 6 - minimumTimeBetweenImpulses: 0.00925, - maximumTimeBetweenImpulses: 0.038, - smoothing: 3, - maximumDownwardChange: 0.86, - maximumUpwardChange: 1.13, - flankLength: 9, - numberOfErrorsAllowed: 2, - // naturalDeceleration: -8.5, // perfect runs IIII - naturalDeceleration: -8.6, // perfect runs IIIXI - minimumDriveTime: 0.28, - minimumRecoveryTime: 0.90, - flywheelInertia: 0.189, - dragFactor: 460 - // - - /* Damper setting 4 - minimumTimeBetweenImpulses: 0.00925, - maximumTimeBetweenImpulses: 0.0335, - smoothing: 3, - maximumDownwardChange: 0.890, - maximumUpwardChange: 1.07, - flankLength: 10, - numberOfErrorsAllowed: 2, - naturalDeceleration: -5.5, // perfect runs I - minimumDriveTime: 0.24, - minimumRecoveryTime: 0.90, - flywheelInertia: 0.140, - dragFactor: 255 - */ - - /* Damper setting 2 - minimumTimeBetweenImpulses: 0.00925, - maximumTimeBetweenImpulses: 0.030, - smoothing: 4, - maximumDownwardChange: 0.962, - maximumUpwardChange: 1.07, + minimumTimeBetweenImpulses: 0.005, + maximumTimeBetweenImpulses: 0.022, + sprocketRadius: 3.0, + autoAdjustDragFactor: true, + minimumDragQuality: 0.83, + dragFactorSmoothing: 3, + flywheelInertia: 0.180, + dragFactor: 225, flankLength: 11, - numberOfErrorsAllowed: 2, - naturalDeceleration: -2.45, // perfect runs - minimumDriveTime: 0.28, - minimumRecoveryTime: 0.90, - flywheelInertia: 0.155, - dragFactor: 155, - magicConstant: 2.8 - */ + minimumStrokeQuality: 0.34, + minumumRecoverySlope: 0.001, + autoAdjustRecoverySlope: true, + autoAdjustRecoverySlopeMargin: 0.036, + minumumForceBeforeStroke: 80, + minimumDriveTime: 0.30, + minimumRecoveryTime: 0.90 + }, + + // Sportstech WRX700 + Sportstech_WRX700: { + numOfImpulsesPerRevolution: 2, + minimumTimeBetweenImpulses: 0.005, + maximumTimeBetweenImpulses: 0.5, + minumumRecoverySlope: 0, + flywheelInertia: 0.72, + dragFactor: 32000 } } diff --git a/docs/Architecture.md b/docs/Architecture.md new file mode 100644 index 0000000000..6779af54b2 --- /dev/null +++ b/docs/Architecture.md @@ -0,0 +1,192 @@ +# Open Rowing Monitor architecture + + +In this document, we describe the architectual construction of Open Rowing Monitor. For the reasons behind the physics, please look at [the Physics behind Open Rowing Monitor](Physics_Of_OpenRowingMonitor.md). In this document we describe the main functional blocks in Open Rowing Monitor, and the major design decissions. + +## Platform choice + +We have chosen for Raspberry Pi, instead of Arduino, due to the CPU requirements needed for some machines. The Raspberry Pi can easily be bought by regular users and installation of the OS and applications is pretty straightforward. It also allows for easy connection of hardware through the GPIO interface. + +We have chosen to use Raspian as OS, as it is easily installed by the user, it provides a well maintained platform with many extensions, and it maintains a 64Bit PREEMPT kernel by default. [Ubuntu Core](https://ubuntu.com/core) provides a a leaner 64-bit low-latency kernel and their Snap-based IoT platform is beautiful, but it also requires a much more complex development and deployment toolchain, which would distract from the core application at the moment. + +## Choice for Node.js and JavaScript + +The choice has been made to use JavaScript to build te application, as many of the needed components (like GPIO and Bluetooth Low Energy) components are readily available. The choice for a runtime interpreted language is traditionally at odds with the low latency requirements that is close to physical hardware. The performance of the app depends heavily on the performance of node.js, which itself isn't optimized for low-latency and high frequency environments. However, in practice, we haven't run into any situations where CPU-load has proven to be too much or processing has been frustrated by latency, even when using full Theil-Senn quadratic regression models on larger flanks (which is O(n2)). + +## Main functional components + +At the highest level, we recognise the following functional components, with their primary dataflows: + +```mermaid +flowchart LR +A(GpioTimerService.js) -->|currentDt| B(server.js) +B(server.js) -->|currentDt| D(RowingStatistics.js) +D(RowingStatistics.js) -->|Rowing metrics| B(server.js) +C(PeripheralManager.js) -->|Heart rate data| B(server.js) +B(server.js) -->|Heart rate data| D(RowingStatistics.js) +B(server.js) -->|Rowing metrics| E(PeripheralManager.js) +E(PeripheralManager.js) -->|Rowing metrics| F(ANT+ clients) +E(PeripheralManager.js) -->|Rowing metrics| G(BLE clients) +B(server.js) -->|currentDt| H(RecordingManager.js) +B(server.js) -->|Rowing metrics| H(RecordingManager.js) +H(RecordingManager.js) -->|currentDt| I(raw recorder) +H(RecordingManager.js) -->|Rowing metrics| J(tcx recorder) +H(RecordingManager.js) -->|Rowing metrics| K(RowingData recorder) +B(server.js) -->|Rowing metrics| L(WebServer.js) +L(WebServer.js) -->|Rowing metrics| M(Client.js) +``` + +Here, *currentDt* stands for the time between the impulses of the sensor, as measured by the pigpio in 'ticks' (i.e. microseconds sinds OS start). + +We first describe the relation between these main functional components by describing the flow of the key pieces of information in more detail: the flywheel and heartrate measurements. We first follow the flow of the flywheel data, which is provided by the interrupt driven `GpioTimerService.js`. The only information retrieved by Open Rowing Monitor is *CurrentDt*: the time between impulses. This data element is transformed in meaningful metrics in the following manner: + +```mermaid +sequenceDiagram + participant clients + participant pigpio + participant GpioTimerService.js + participant server.js + participant RowingStatistics.js + participant Rower.js + participant Flywheel.js + pigpio -)GpioTimerService.js: tick
(interrupt based) + GpioTimerService.js-)server.js: currentDt
(interrupt based) + server.js-)RowingStatistics.js: currentDt
(interrupt based) + RowingStatistics.js->>Rower.js: currentDt
(interrupt based) + Rower.js->>Flywheel.js: currentDt
(interrupt based) + Flywheel.js-->>Rower.js: Angular metrics, Flywheel state
(interrupt based) + Rower.js-->>RowingStatistics.js: Strokes, Linear metrics
(interrupt based) + RowingStatistics.js-)server.js: Metrics Updates
(State/Time based) + server.js-)clients: Metrics Updates
(State/Time based) +``` + +The clients (both the webbased screens and periphal bluetooth devices) are updated based on both a set interval and when the stroke or session state changes. Open Rowing Monitor therefore consists out of two subsystems: an solely interruptdriven part that processes flywheel and heartrate interrupts, and the time/state based needs of the clients. It is the responsibility of `RowingStatistics.js` to manage this: it monitors the timers, session state and guarantees that it can present the clients with the freshest data availble. + +Secondly, the heartrate data follows the same path, but requires significantly less processing: + +```mermaid +sequenceDiagram + participant clients + participant heartrateMonitor + participant server.js + participant RowingStatistics.js + heartrateMonitor-)server.js: heartrate data
(interrupt based) + server.js-)RowingStatistics.js: heartrate data
(interrupt based) + RowingStatistics.js-)server.js: Metrics Updates
(State/Time based) + server.js-)clients: Metrics Updates
(State/Time based) +``` + +### pigpio + +`pigpio` is a wrapper around the [pigpio C library](https://github.com/joan2937/pigpio), which is an extreme high frequency monitor of the pigpio port. As the pigpio npm is just a wrapper around the C library, all time measurement is done by the high cyclic C library, making it extremely accurate. It can be configured to ignore too short pulses (thus providing a basis for debounce) and it reports the `tick` (i.e. the number of microseconds since OS bootup) when it concludes the signal is valid. It reporting is detached from its measurement, and we deliberatly use the *Alert* instead of the *Interrupt* as their documentation indicates that both types of messaging provide an identical accuracy of the `tick`, but *Alerts* do provide the functionality of a debounce filter. As the C-implementation of `pigpio` determines the accuracy of the `tick`, this is the only true time critical element of Open Rowing Monitor. Latency in this process will present itself as noise in the measurements of *CurrentDt*. + +### GpioTimerService.js + +`GpioTimerService.js` is a small independent process, acting as a data handler to the signals from `pigpio`. It translates the *Alerts* with their `tick` into a stream of times between these *Alerts* (which we call *CurrentDt*). The interrupthandler is still triggered to run with extreme low latency as the called `gpio` process will inherit its nice-level, which is extremely time critical. To Open Rowing Monitor it provides a stream of measurements that needed to be handled. + +### Server.js + +`Server.js` orchestrates all information flows and starts/stops processes when needed. It will: + +* Recieve (interrupt based) GPIO timing signals from `GpioTimerService.js` and send them to the `RowingStatistics.js`; +* Recieve (interrupt based) Heartrate measurements and sent them to the `RowingStatistics.js`; +* Recieve the metrics update messages from `RowingStatistics.js` (time-based and state-based updates of metrics) and distribut them to the webclients and blutooth periphials; +* Handle user input (through webinterface and periphials) and instruct `RowingStatistics.js` to act accordingly; +* Handle escalations from `RowingStatistics.js` (like reaching the end of the interval, or seeing the rower has stopped) and instruct the rest of the application, like the `WorkoutRecorder.js` accordingly. + +### RowingStatistics.js + +`RowingStatistics.js` recieves *currentDt* updates, forwards them to `Rower.js` and subsequently inspects `Rower.js` for the resulting strokestate and associated metrics. Based on this inspection, it updates the finite state machine of the sessionstate and the associated metrics (i.e. linear velocity, linear distance, power, etc.). + +#### sessionStates in RowingStatistics.js + +`RowingStatistics.js` maintains the following sessionstates: + +```mermaid +stateDiagram-v2 + [*] --> WaitingForStart + WaitingForStart --> Rowing: strokeState
is 'Drive' + state Rowing { + strokeState=Drive --> strokeState=Recovery + strokeState=Drive --> strokeState=Drive + strokeState=Recovery --> strokeState=Drive + strokeState=Recovery --> strokeState=Recovery + } + Rowing --> Paused: strokeState
is 'WaitingForDrive' + Paused --> Rowing: strokeState
is 'Drive' + Rowing --> Stopped + Stopped --> [*] +``` + +Please note: `handleRotationImpulse` implements all these state transitions, where the state transitions for the end of an interval and the end of a session are handled individually as the metrics updates differ slightly. + +#### metrics maintained in RowingStatistics.js + +The goal is to translate the linear rowing metrics into meaningful information for the consumers of data updating both session state and the underlying metrics. As `Rower.js` can only provide a limited set of absolute metrics at a time (as most are stroke state dependent) and is unaware of previous strokes and the context of the interval, `RowingStatistics.js` will consume this data, combine it with other datasources like the heartrate and transform it into a consistent and more stable set of metrics useable for presentation. As `RowingStatistics.js` also is the bridge between the interrupt-driven and time/state driven part of the application, it buffers data as well, providing a complete set of metrics regardless of stroke state. Adittionally, `RowingStatistics.js` also smoothens data across strokes to remove eratic behaviour of metrics due to small measurement errors. + +In a nutshell: + +* `RowingStatistics.js` is the bridge/buffer between the interrupt-drive processing of data and the time/state based reporting of the metrics, +* `RowingStatistics.js` maintains the session state, thus determines whether the rowing machine is 'Rowing', or 'WaitingForDrive', etc., +* `RowingStatistics.js` applies a moving median filter across strokes to make metrics less volatile and thus better suited for presentation, +* `RowingStatistics.js` calculates derived metrics (like Calories) and trands (like Calories per hour), +* `RowingStatistics.js` maintains the workout intervals, guards interval and session boundaries, and will chop up the metrics-stream accordingly, where `Rower.js` will just move on without looking at these artifical boundaries. + +In total, this takes full control of the displayed metrics in a specific interval. + +### Rower.js + +`Rower.js` recieves *currentDt* updates, forwards them to `Flywheel.js` and subsequently inspects `Flywheel.js` for the resulting state and angular metrics, transforming it to a strokestate and linear metrics. + +#### strokeStates in Rower.js + +`Rower.js` can have the following strokeStates: + +```mermaid +stateDiagram-v2 + [*] --> WaitingForDrive + WaitingForDrive --> Drive: Flywheel
is powered + Drive --> Recovery: Flywheel
is unpowered + Drive --> Drive: Flywheel
is powered + Recovery --> Drive: Flywheel
is powered + Recovery --> Recovery: Flywheel
is unpowered + Recovery --> WaitingForDrive: Last drive too
long ago + Drive --> Stopped + Recovery --> Stopped + Stopped --> [*] +``` + +Please note: the `Stopped` state is only used for external events (i.e. `RowingStatistics.js` calling the stopMoving() command), which will stop `Rower.js` from processing data. This is a different state than `WaitingForDrive`, which can automatically move into `Drive` by accelerating the flywheel. This is typically used for a forced exact stop of a rowing session (i.e. reaching the end of an interval). + +#### Linear metrics in Rower.js + +`Rower.js` inspects the flywheel behaviour on each impuls and translates the flywheel state into the strokestate (i.e. 'WaitingForDrive', 'Drive', 'Recovery', 'Stopped') through a finite state machine. Based on the angular metrics (i.e.e drag, angular velocity, angular acceleration) it also calculates the updated associated linear metrics (i.e. linear velocity, linear distance, power, etc.). As most metrics can only be calculated at (specific) phase ends, it will only report the metrics it can claculate. Aside temporal metrics (Linear Velocity, Power, etc.) it also maintains several absolute metrics (like total moving time and total linear distance travelled). It only updates metrics that can be updated meaningful, and it will not resend (potentially stale) data that isn't updated. + +### Flywheel.js + +`Flywheel.js` recieves *currentDt* updates and translates that into a state of the flywheel and associated angular metrics. It provides a model of the key parameters of the Flywheel, to provide the rest of OpenRowingMonitor with essential physical metrics and state regarding the flywheel, without the need for considering all kinds of parameterisation. Therefore, `Flywheel.js` will provide all metrics in regular physical quantities, abstracting away from the measurement system and the associated parameters, allowing the rest of OpenRowingMonitor to focus on processing that data. + +It provides the following types of information: + +* the state of the flywheel (i.e. is the flywheel powered, unpowered or even Dwelling) +* temporal metrics (i.e. Angular Velocity, Angular Acceleration, Torque, etc.) +* several absolute metrics (i.e. total elapsed time and total angular distance traveled) +* physical properties of the flywheel, (i.e. the flywheel drag and flywheel inertia) + +## Major design decissions + +### Staying close to *currentDt* + +*currentDt* is defined as the time between impulses, which is the core measurement of any rowing machine. These values tend to range between 0.050 and 0.005 seconds, and are subject to small measurement errors due to vibrations in the rower but also scheduling issues in the Raspberry Pi OS. + +Working with small numbers, and using the impulse time to calculate the angular velocity (i.e. dividing the angular distance travelled through currentDt), or even calculating angular acceleration (i.e. dividing angular velocity through currentDt) tends to enlarge these measurement errors. Therefore, whenever possible, calculations are based on the raw currentDt or Robust Regression methods, rather than numerical derived metrics, to prevent chaotic behaviour of OpenRowingMonitor. + +### Absolute approach in Rower.js + +`Rower.js` could report distance incrementally to `RowingStatistics.js`. However, we chose to report in absolute times and distances, making `Rower.js` in full control of these essential metrics. This way, `Rower.js` can report absolute times and distances, taking full control of the metrics regarding linear movement. This way, these metrics can be calculated temporarily for frequent updates, but calculated definitively when the phase completes. Any derived metrics for specific clients, and smoothing/buffering, is done by `RowingStatistics.js`. + +Adittional benefit of this approach is that it makes transitions in intervals more smooth: `RowingStatistics.js` can intersect stroke without causing any pause in metrics (as RowingEngine.js keeps reporting absolutes, intervals and laps become a view on the same data). + +## Open issues, Known problems and Regrettable design decissions + +Please see [Physics behind Open Rowing Monitor](physics_openrowingmonitor.md) diff --git a/docs/Improving_Raspberry_Performance.md b/docs/Improving_Raspberry_Performance.md new file mode 100644 index 0000000000..363b9e6657 --- /dev/null +++ b/docs/Improving_Raspberry_Performance.md @@ -0,0 +1,77 @@ +# Improving the performance of the Raspberry Pi + +Out of the box, Raspbian is configured to provide a decent experience while conserving energy. However, responding instantly to incoming measurements tends to be so deviating, that we need to do some extra work to get a system working well. + +## Signs your performance is insufficient + +A typical sign is that there is much noise in the data readings from the flywheel, lots of small deviations. This is typically the case when the signals are handled too late. + +## Things you can do at the OS + +Open Rowing Monitor does not exist in isolation, so the first step is to make sure the Operating System (OS) is cut out for the job. + +### Selecting the right kernel + +Normally, a Linux kernel is configured to do non-real-time work, and focusses on doing one task well for a prolonged period of time, reducing overhead. This is great for normal applications that process a lot of data. However, Open Rowing Monitor does not process much data, but does has to respond quickly to incoming signals (especially the impulses from the flywheel). The time it takes to respond to an incoming interrupt is called **latency**. For reducing noise in the readings, it is important that the latency does not vary too much. + +When installing Open Rowing Monitor, please use a low latency or real time kernel. Currently, the Raspbian 64Bit Lite kernel is a Preempt kernel, which aims at low latency. So using this is a great choice for the Operating System. + +### Kernel settings + +Aside from selecting the right OS and kernel, there are some settings that can be set at startup that reduce the latency of the kernel. + +One of these options is to turn off CPU exploit protection. This is a huge security risk as it removes security mitigations in the kernel, but it reduces latency. Given your specific network layout, this could be worth the effort. Add to `/boot/cmdline.txt` the following option, if you consider it responsible in your situation (this introduces a security risk): + +```zsh +mitigations=off +``` + +Another option is to dedicate a CPU to Open Rowing Monitor and run the CPU's in isolation. This avenue isn't explored fully, and the effects on Open Rowing Monitor are unknown, but [this text explains how it should work](https://forums.raspberrypi.com/viewtopic.php?t=228727). + +### CPU Scaling + +Typically, Raspbian is configured to reduce energy consumption, using the *ondemand* CPU governor. For low latency applications, this isn't sufficient. To get the most out of the CPU, we need to use the *performance* governor. + +First, Raspbian will interfere with settings, so we need to kill that functionality: + +```zsh +sudo systemctl disable raspi-config +``` + +Next, we need to istall cpufrequtils to allow control over the CPU governor: + +```zsh +sudo apt-get install cpufrequtils +``` + +Now, you can set the default governor by editing `/etc/default/cpufrequtils` so that it reads: + +```zsh +GOVERNOR="performance" +``` + +After a reboot, you can check the governor by executing: + +```zsh +sudo cpufreq-info +``` + +If all went well, your CPU is now in "Performance" mode. Please note that a Raspberry Pi will run hot and consume a lot more energy. + +### Services you can disable safely + +There are some services running that can be disabled safely. + +#### triggerhappy + +To disable triggerhappy, do the following: + +```zsh +sudo systemctl disable triggerhappy.service +``` + +There are some other services that can be stopped, but where the effects on Open Rowing Monitor are untested, [which can be found here](https://wiki.linuxaudio.org/wiki/raspberrypi). + +## Things you can do in OpenRowingMonitor + +One thing you can do to improve CPU performance is to reduce *flanklength*, which will reduce CPU-load. So running with unneccessary long *flanklength* isn't advised. diff --git a/docs/README.md b/docs/README.md index 7bbb6302ea..19c0ce633c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,88 +1,108 @@ # Open Rowing Monitor -[![Node.js CI](https://github.com/laberning/openrowingmonitor/actions/workflows/node.js.yml/badge.svg)](https://github.com/laberning/openrowingmonitor/actions/workflows/node.js.yml) -[![CodeQL](https://github.com/laberning/openrowingmonitor/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/laberning/openrowingmonitor/actions/workflows/codeql-analysis.yml) -[![pages-build-deployment](https://github.com/laberning/openrowingmonitor/actions/workflows/pages/pages-build-deployment/badge.svg)](https://github.com/laberning/openrowingmonitor/actions/workflows/pages/pages-build-deployment) +[![Node.js CI](https://github.com/JaapvanEkris/openrowingmonitor/actions/workflows/node.js.yml/badge.svg)](https://github.com/JaapvanEkris/openrowingmonitor/actions/workflows/node.js.yml) +[![CodeQL](https://github.com/JaapvanEkris/openrowingmonitor/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/JaapvanEkris/openrowingmonitor/actions/workflows/codeql-analysis.yml) - +Open Rowing Monitor logo -A free and open source performance monitor for rowing machines. It upgrades a rowing machine into a smart trainer that can be used with training applications and games. +Open Rowing Monitor is a free and open source performance monitor for rowing machines. It upgrades almost any rowing machine into a smart trainer that can be used with training applications and games. -Open Rowing Monitor is a Node.js application that runs on a Raspberry Pi and measures the rotation of the rower's flywheel (or similar) to calculate rowing specific metrics, such as power, split time, speed, stroke rate, distance and calories. +It is a Node.js application that runs on a Raspberry Pi and measures the rotation of the rower's flywheel (or similar) to calculate rowing specific metrics, such as power, split time, speed, stroke rate, distance and calories. It can share these metrics for controling games and record these metrics for further analysis. -It is currently developed and tested with a Sportstech WRX700 water-rower. But it should run fine with any rowing machine that uses some kind of damping mechanism, as long as you can add something to measure the speed of the flywheel. -It should also work well with DIY rowing machines like the [Openergo](https://openergo.webs.com). +Open Rowing Monitor should run fine with any rowing machine that uses some kind of damping mechanism, as long as you can add something to measure the speed of the flywheel. It has shown to work well with DIY rowing machines like the [Openergo](https://openergo.webs.com), providing the construction is decent. [You can find a full list of known and supported rowers here](Supported_Rowers.md). If your machine isn't listed, don't worry, it just means that you need to adjust the software settings following the [settings adjustment guide](rower_settings.md) yourself. And there is no reason to be anxious, in the [GitHub Discussions](https://github.com/laberning/openrowingmonitor/discussions) there always are friendly people to help you set up your machine and the settings. ## Features -The following items describe most of the current features, more functionality will be added in the future, check the [Development Roadmap](backlog.md) if you are curious. +Open Rowing Monitor aims to provide you with metrics directly, connect to watches, apps and games via bluetooth or ANT+ and allow you to export your data to the analysis tool of your choice. These features have been tested intensily, where most features have survived flawlessly over 3 million meters of rowing with different types of rowing machines. + + +Image showing the main Open Rowing Monitor screen
+ +The following items describe most of the current features in more detail. ### Rowing Metrics -Open Rowing Monitor implements a physics model to simulate the typical metrics of a rowing boat based on the pull on the handle. The physics model can be tuned to the specifics of a rower by changing some model parameters. +Open Rowing Monitor implements a physics model to simulate the typical metrics of a rowing boat based on the pull on the handle. The physics model can be tuned to the specifics of a rower by changing some model parameters in the configuration file, where we also provide these [settings for machines known to us](Supported_Rowers.md). The underlying V1 physics engine has been validated against a Concept2 PM5 in over 300 sessions (totalling 2.5 million meters), and results deviate less than 0.05% for every individual rowing session. -* Stroke detection +Open Rowing Monitor can display the following key metrics on the user interface: + +* Distance rowed (meters) +* Training Duration * Power (watts) -* Split time (/500m) -* Strokes per Minute +* Pace (/500m) +* Strokes per Minute (SPM) * Calories used (kcal) -* Training Duration +* Total number of strokes * Heart Rate (supports BLE and ANT+ heart rate monitors, ANT+ requires an ANT+ USB stick) +* Drag factor +* Drive duration (seconds) +* Drive length (meters) +* Recovery duration (seconds) +* Distance per stroke (meters) +* Force curve with Peak power (Newtons) -### Web Interface +It calculates and can export many other key rowing metrics, including Recovery Heart Rate, Average handle force (Newton), Peak handle force (Newton) and the associated handle force curve, handle velocity curve and handle power curve. -The web interface visualizes the rowing metrics on any device that can run a web browser (i.e. a smartphone that you attach to your rowing machine while training). It uses web sockets to show the rowing status in realtime. It can also be used to reset the training metrics and to select the BLE Rower. +### Web Interface -If you connect a screen to the Raspberry Pi, then this interface can also be directly shown on the device. The installation script can set up a web browser in kiosk mode that runs on the Raspberry Pi. +The web interface visualizes the basic rowing metrics on any device that can run a web browser (i.e. a smartphone that you attach to your rowing machine while training). It shows the rowing statistics in realtime. You can set up the user interface as you like, with the metrics you find important: -
+Image showing the metrics selection screen
+ +Via the Action tile, it can also be used to reset the training metrics and to select the type of bluetooth and ANT+ connection. + +If you connect a physical screen directly to the Raspberry Pi, then this interface can also be directly shown on the device. The installation script can set up a web browser in kiosk mode that runs on the Raspberry Pi. ### Bluetooth Low Energy (BLE) -Open Rowing Monitor also implements different Bluetooth Low Energy (BLE) protocols so you can use your rowing machine with different fitness applications. +Open Rowing Monitor can recieve recieve heartrate data via BLE. Asides this functionality, Open Rowing Monitor also implements different Bluetooth Low Energy (BLE) protocols so you can use your rowing machine to share rowing metrics with different fitness applications. Some apps use the Fitness Machine Service (FTMS), which is a standardized GATT protocol for different types of fitness machines. Other apps prefer to see a Concept 2 PM5. To help you connect to your app and game of choice, Open Rowing Monitor currently supports the following Bluetooth protocols: + +* **Concept2 PM**: Open Rowing Monitor implements part of the Concept2 PM Bluetooth Smart Communication Interface Definition. This is still work in progress and only implements the most common parts of the spec, so it is not guaranteed to work with all applications that support C2 rowing machines. Our interface currently can only report metrics, but can't recieve commands and session parameters from the app yet. It is known to work with [EXR](https://www.exrgame.com) and all the samples from [The Erg Arcade](https://ergarcade.com), for example you can [row in the clouds](https://ergarcade.github.io/mrdoob-clouds/). + +* **FTMS Rower**: This is the FTMS profile for rowing machines and supports all rowing specific metrics (such as stroke rate). So far not many training applications for this profile exist, but the market is evolving. We've successfully tested it with [EXR](https://www.exrgame.com) (preferred method), [MyHomeFit](https://myhomefit.de) and [Kinomap](https://www.kinomap.com). + +* **FTMS Indoor Bike**: This FTMS profile is used by Smart Bike Trainers and widely adopted by training applications for bike training. It does not support rowing specific metrics. But it can present metrics such as power and distance to the biking application and use cadence for stroke rate. So why not use your virtual rowing bike to row up a mountain in [Zwift](https://www.zwift.com), [Bkool](https://www.bkool.com), [The Sufferfest](https://thesufferfest.com) or similar :-) -Fitness Machine Service (FTMS) is a standardized GATT protocol for different types of fitness machines. Open Rowing Monitor currently supports the type **FTMS Rower** and simulates the type **FTMS Indoor Bike**. +* **BLE Cycling Power Profile**: This Bluetooth simulates a bike, which allows you to connect the rower to a bike activity on your (mostly Garmin) sportwatch. It will translate the rowing metrics to the appropriate fields. This profile is only supported by specific watches, so it might provide a solution. -**FTMS Rower:** This is the FTMS profile for rowing machines and supports all rowing specific metrics (such as stroke rate). So far not many training applications for this profile exist, but the market is evolving. I've successfully tested it with [EXR](https://www.exrgame.com), [MyHomeFit](https://myhomefit.de) and [Kinomap](https://www.kinomap.com). +* **BLE Cycling Speed and Cadence Profile**: used for older Garmin Forerunner and Garmin Venu watches and similar types, again simulating a bike activity. Please note to set the wheel circumference to 10mm to make this work well. -**FTMS Indoor Bike:** This FTMS profile is used by Smart Bike Trainers and widely adopted by training applications for bike training. It does not support rowing specific metrics. But it can present metrics such as power and distance to the biking application and use cadence for stroke rate. So why not use your virtual rowing bike to row up a mountain in [Zwift](https://www.zwift.com), [Bkool](https://www.bkool.com), [The Sufferfest](https://thesufferfest.com) or similar :-) +### ANT+ -**Concept2 PM:** Open Rowing Monitor also implements part of the Concept2 PM Bluetooth Smart Communication Interface Definition. This is still work in progress and only implements the most common parts of the spec, so it will not work with all applications that support C2 rowing machines. It currently works with all the samples from [The Erg Arcade](https://ergarcade.com), i.e. you can [row in the clouds](https://ergarcade.github.io/mrdoob-clouds/). This also works very well with [EXR](https://www.exrgame.com). +You can add a ANT+ USB-stick to your Raspberry Pi, which allows to to recieve data from your ANT+ heartrate monitor. On top of recieving the heartrate data, Open Rowing Monitor can also broadcast rowing metrics via ANT+, which can be recieved by the more expensive series of Garmin smartwatches, which then can calculate metrics like training load etc.. ### Export of Training Sessions -Open Rowing Monitor can create Training Center XML files (TCX). You can upload these files to training platforms like [Strava](https://www.strava.com), [Garmin Connect](https://connect.garmin.com) or [Trainingpeaks](https://trainingpeaks.com) to track your training sessions. +Open Rowing Monitor is based on the idea that metrics should be easily accessible for further analysis. Therefore, Open Rowing Monitor can create the following files: -Uploading your sessions to Strava is an integrated feature, for all other platforms this is currently a manual step. The installer can set up a network share that contains all training data so it is easy to grab the files from there and upload them to the training platform of your choice. +* **Training Center XML files (TCX)**: These are XML-files that contain the most essential metrics of a rowing session. Most training analysis tools will accept a tcx-file. You can upload these files to training platforms like [Strava](https://www.strava.com), [Garmin Connect](https://connect.garmin.com), [Intervals.icu](https://intervals.icu/), [RowsAndAll](https://rowsandall.com/) or [Trainingpeaks](https://trainingpeaks.com) to track your training sessions; -Open Rowing Monitor can also store the raw measurements of the flywheel into CSV files. These files are great to start your own exploration of your rowing style and also to learn about the specifics of your rowing machine (some Excel files that can help with this are included in the `docs` folder). +* **RowingData** files, which are comma-seperated files with all metrics Open Rowing Monitor can produce. These can be used with [RowingData](https://pypi.org/project/rowingdata/) to display your results locally, or uploaded to [RowsAndAll](https://rowsandall.com/) for a webbased analysis (including dynamic in-stroke metrics). The csv-files can also be processed manually in Excel, allowing your own custom analysis. Please note that for visualising in-stroke metrics in [RowsAndAll](https://rowsandall.com/) (i.e. force, power and handle speed curves), you need their yearly subscription; -## Installation +* **Raw flywheel measurements of the flywheel**, also in CSV files. These files are great to start to learn about the specifics of your rowing machine (some Excel visualistion can help with this). + +Uploading your sessions to Strava is an integrated feature, for all other platforms this is currently a manual step. Uploading to [RowsAndAll](https://rowsandall.com/) can be automated through their e-mail interface, see [this description](https://rowsandall.com/rowers/developers/). The Open rowing Monito installer can also set up a network share that contains all training data so it is easy to grab the files from there and manually upload them to the training platform of your choice. -You will need a Raspberry Pi Zero W, Raspberry Pi Zero 2 W, Raspberry Pi 3 or a Raspberry Pi 4 with a fresh installation of Raspberry Pi OS Lite for this. Connect to the device with SSH and initiate the following command to install Open Rowing Monitor as an automatically starting system service: +## Installation -```zsh -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/laberning/openrowingmonitor/HEAD/install/install.sh)" -``` +You will need a Raspberry Pi Zero 2 W, Raspberry Pi 3, Raspberry Pi 4 or Raspberry Pi 5 with a fresh installation of Raspberry Pi OS Lite for this (the 64Bit kernel is preferred). Connect to the device with SSH have a look at the [Detailed Installation Instructions](installation.md) for more information on the software installation and for instructions on how to connect the rowing machine. Don't have a Raspberry Pi, but do have an ESP32 lying about? No problem, our sister project ported [Open Rowing Monitor for the ESP32](https://github.com/Abasz/ESPRowingMonitor), which works well (although uses a bit less accurate math due to platform limitations). -Also have a look at the [Detailed Installation Instructions](installation.md) for more information on the software installation and for instructions on how to connect the rowing machine. +Please observe that active support for the Raspberry Pi Zero W has been dropped as of february 2024 (see [this discussion for more information](https://github.com/JaapvanEkris/openrowingmonitor/discussions/33)), and that installation on the latest version of Raspberry Pi OS Bookworm is impossible due to package conflicts beyond our control. We do maintain branch where we will backport functional improvements until April 2025. ## How it all started -I originally started this project, because my rowing machine (Sportstech WRX700) has a very simple computer and I wanted to build something with a clean interface that calculates more realistic metrics. Also, this was a good reason to learn a bit more about Bluetooth and all its specifics. +[Lars Berning](https://github.com/laberning) originally started this project, because his rowing machine (Sportstech WRX700) had a very simple monitor and he wanted to build something with a clean interface that calculates more realistic metrics. Also, this was a good reason to learn a bit more about Bluetooth and all its specifics. -The original proof of concept version started as a sketch on an Arduino, but when I started adding things like a web frontend and BLE I moved it to the much more powerful Raspberry Pi. Maybe using a Raspberry Pi for this small IoT-project is a bit of an overkill, but it has the capacity for further features such as syncing training data or rowing games. And it has USB-Ports that I can use to charge my phone while rowing :-) +The original proof of concept version started as a sketch on an Arduino, but the web frontend and BLE needed the much more powerful Raspberry Pi. Maybe using a Raspberry Pi for this small IoT-project was a bit of an overkill, but it has the capacity for more complex math and features such as syncing training data. There is a much appreciated sister project that ported [Open Rowing Monitor for the ESP32](https://github.com/Abasz/ESPRowingMonitor). ## Further information -This project is already in a very usable stage, but some things are still a bit rough on the edges. +This project is already in a very stable stage, as it is used daily by many rowers. You can see its development [here in the Release notes](Release_Notes.md). However, being open source, it might contain some things that are still a bit rough on the edges. More functionality will be added in the future, so check the [Development Roadmap](backlog.md) if you are curious. Contributions are welcome, please read the [Contributing Guidelines](CONTRIBUTING.md) first. -Feel free to leave a message in the [GitHub Discussions](https://github.com/laberning/openrowingmonitor/discussions) if you have any questions or ideas related to this project. +Feel free to leave a message in the [GitHub Discussions](https://github.com/JaapvanEkris/openrowingmonitor/discussions) if you have any questions or ideas related to this project. Check the advanced information on the [Physics behind Open Rowing Monitor](physics_openrowingmonitor.md). -I plan to add more features, here is the [Development Roadmap](backlog.md). Contributions are welcome, please read the [Contributing Guidelines](CONTRIBUTING.md) first. - This project uses some great work by others, see the [Attribution here](attribution.md). diff --git a/docs/Release_Notes.md b/docs/Release_Notes.md new file mode 100644 index 0000000000..78c482dafa --- /dev/null +++ b/docs/Release_Notes.md @@ -0,0 +1,66 @@ +# OpenRowingMonitor Release Notes + +## From 0.8.4 to 0.9.0 (January 2024) + +Main contributors: [Jaap van Ekris](https://github.com/JaapvanEkris) and [Abasz](https://github.com/Abasz) + +### New functionality in 0.9.0 + +- Added support for ANT+ rowing metrics broadcast +- Allow the user to change the GUI layout and metrics, including displaying the force curve +- Allow user to turn on or off ANT+ and BLE functionality and dynamically switch between ANT+ and BLE HR monitors from the GUI +- Added the option for more complex workouts, as a hook for the PM5 and webinterface (these are a ToDo where the PM5 workout interface is still in development) +- Added reporting of PM5 Interval-types to the PM5 + +### Bugfixes and robustness improvements in 0.9.0 + +- Added a configuration sanity check which logs obvious errors and (if possible) repairs settings, after several users messed up their config and got completely stuck. +- The configuration sanity check also provides an automated upgrade path for 0.8.2 (old config) users to 0.9.0 (new config), as all the newly added configuration items between these two versions are automatically detected, logged and repaired. +- Added restart limits to prevent infinite boot loops of the app crashing and rebooting when there is a config error +- Fixed the GPIO tick rollover, which led to a minor hickup in data in rows over 30 minutes +- Made Flywheel.js more robust against faulty GPIO data +- Fixed an application crash in the RowingData generation when the target directory doesn't exist yet +- Improved the structure of the peripherals to allow a more robust BLE and ANT use +- Improved the accuracy, responsiveness and efficiency of both the Linear and Quadratic the Theil-Sen algorithms. For larger 'flankLength' machines, this will result in 50% reduction in CPU use, while increasing the responsiveness and accuracy of the forcecurve and powercurve. +- Drag calculation and recovery slope calculation are now down with Linear Theil-Sen algorithm, making this calculation more robust against outliers +- Validation of the engine against a PM5 for over 3000KM, where the deviation is a maximum of 0.03% + +## From 0.8.2 to 0.8.4 (January 2023) + +Main contributors: [Jaap van Ekris](https://github.com/JaapvanEkris) and [Abasz](https://github.com/Abasz) + +### New Functionality in 0.8.4 + +- New Metrics: Force curve, Peak force, average force, power curve, handle speed curve, VO2Max (early beta), Heart Rate Recovery. All have over 1000 kilometers of testing under their belt, and have sown to work reliably; +- Improved metrics through BLE: Based on the new engine, many metrics are added to both FTMS Rower and PM5, making it as complete as it can be. Most metrics also have over a 1000 km of testing with EXR, and both types of interface have been used with EXR intensly. +- New export format: There is a RowingData export, which can export all metrics in .csv, which is accepted by both RowingData and RowsAndAll. It is also useable for users to read their data into Excel. This export brings the force curve to users, although it will require a small subscription to see it; +- Simpler set-up: a better out-of-the-box experience for new users. We trimmed the number of required settings, and for many cases we’ve succeeded: several settings are brought down to their key elements (like a minimal handle force, which can be set more easily fror all rowers) or can be told by looking at the logs (like the recovery slope). For several other settings, their need to set them perfectly has been reduced, requiring less tweaking before Open Rowing Monitor starts producing good data. To support this, there also is a new setup document, to help users set up their own rower; +- Switch to 64Bit: ORM supports the 64 Bit core, which has a PREEEMPT-kernel. The setup-script accepts this as well, as this should be the preferred kernel to use. The PREEMPT-kernel is optimized for low latency measurements, like IoT applications. As PREEMPT kernels can handle a lot higher priority for the GPIO-thread, this setting has been switched from a binary setting to a priority setting. +- An initial stub for session mangement: As a first step towards sessions and splits, a session object in Server.js is added as a placeholder for session targets. If unfilled, the code will act as in version 0.8.2: you can row without any limitations. If a target is set, it will termintate the session at the exact right time. As is with the PM5, ORM counts down if a target is set. The current stub isn't ideal yet, as we want the user to be able to set these targets through the webGUI or through BLE. However, it is a first step towards functional completeness as it lays a preliminary foundation for such functionality. + +### Bugfixes and robustness improvements in 0.8.4 + +- Totally renewed rowing engine: Linear and Quadratic Regression models are now the core of the rowing engine. This model is much more robust against noise, and thus removing the need for any noise filtering from OpenRowingMonitor for any of the known rowers. In the over 1000 kilometers of testing, it has proven to work extremely reliable and robust; +- Improved logging: the logging has been more focussed on helping the user fix a bad setting. I removed several metrics, but added several others as they tell much more about the underlying state of the engine and its settings (for example the drive time and drive length). Goal is to have users be able to tune their engine based on the log. +- Finite State Machine based state management: OpenRowingEngine will now maintain an explicit state for the rower, and RowingStatistics will maintain an explicit state for the session. Aside reducing the code complexity significantly, it greatly impoved robustness. +- Added a new GPIO-library, making measurement of the flywheel data much more accurate and allowing to "debounce" the measurements, as many sensors have this issue + +## From 0.8.1 to 0.8.2 (Febuary 2022) + +Main contributor: [Lars Berning](https://github.com/laberning) + +### New Functionality in 0.8.2 + +- Added Strava support + +## From 0.8.0 to 0.8.1 (September 2021) + +Main contributor: [Jaap van Ekris](https://github.com/JaapvanEkris) + +### Bugfixes and robustness improvements in 0.8.1 + +- Refactoring of the Rowing Engine, as [Dave Vernooy's engine (ErgWare)](https://dvernooy.github.io/projects/ergware/) is good, but its variable naming leaves a bit to be desired. + +## 0.7.0 (March 2021) + +Initial release, Main contributor: [Lars Berning](https://github.com/laberning), based on [Dave Vernooy's physics engine (ErgWare)](https://dvernooy.github.io/projects/ergware/) diff --git a/docs/Rowing_Settings_Analysis_Small.xlsx b/docs/Rowing_Settings_Analysis_Small.xlsx deleted file mode 100644 index c932151de5..0000000000 Binary files a/docs/Rowing_Settings_Analysis_Small.xlsx and /dev/null differ diff --git a/docs/Supported_Rowers.md b/docs/Supported_Rowers.md new file mode 100644 index 0000000000..f7666f5ace --- /dev/null +++ b/docs/Supported_Rowers.md @@ -0,0 +1,71 @@ +# Known rowers and their support status + +Open Rowing Monitor works with a very wide range of rowing machines. It is currently developed and tested with a Sportstech WRX700 water-rower and a Concept2 air-rower. In the past, it was also tested extensively on a NordicTrack RX-800 hybrid air/magnetic rower. But it should run fine with any rowing machine that uses some kind of damping mechanism, as long as you can add something to measure the speed of the flywheel. It has shown to work well with DIY rowing machines like the [Openergo](https://openergo.webs.com/), providing the construction is decent. + +The following rowers are known to work, or are even actively supported: + +| Brand | Type | Rower type | Measurement type | HW Modification needed | Support status | Rower profile | Basic Metrics | Advanced Metrics | Limitations | Remarks | +| ----- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---------------- | +| Abilica | Winrower 2.0 | Air rower | Handle drive wheel | No | Known to work | - | Yes | No | Static distance | see [this discussion](https://github.com/laberning/openrowingmonitor/discussions/48) | +| Concept 2 | Model B, C | Air rower | Flywheel | Modification to electrical signal | In development | - | - | - | - | See [this](https://github.com/laberning/openrowingmonitor/issues/77), [this](https://github.com/laberning/openrowingmonitor/discussions/38) and [this](https://github.com/laberning/openrowingmonitor/discussions/151) discussion| +| | Model D, E | Air rower | Flywheel | Modification to electrical signal | Active support | Concept2_RowErg | Yes | Yes | None | [Concept 2 Model D, Model E and RowErg setup](hardware_setup_Concept2_RowErg.md) | +| | RowErg | Air rower | Flywheel | Modification to electrical signal | Active support | Concept2_RowErg | Yes | Yes | None | [Concept 2 Model D, Model E and RowErg setup](hardware_setup_Concept2_RowErg.md) | +| Decathlon | Rower 120 | Physical friction | Flywheel | Adding sensor and adding magnets to the flywheel | In development | - | - | - | - | see [this discussion](https://github.com/laberning/openrowingmonitor/issues/110) | +| DKN | R-320 | Air Rower | Flywheel | No | Full support | DKN_R320 | Yes | No | Static drag | - | +| FDF | Neon Pro V | Air rower | Flywheel | Sensor replacement | Known to work | - | Yes | - | - | see [this](https://github.com/laberning/openrowingmonitor/discussions/87) and [this](https://github.com/JaapvanEkris/openrowingmonitor/discussions/11) discussion| +| ForceUSA | R3 | Air Rower | Flywheel | No | Supported | ForceUSA_R3 | Yes | Yes | None | - | +| Johnson | JAR5100 | Air Rower | Flywheel | Yes, add magnets and sensor | Configuration known | - | Yes | Yes | None | [this discussion](https://github.com/laberning/openrowingmonitor/discussions/139) | +| NordicTrack | RX800 | Hybrid Magnetic and Air rower | Flywheel | None | Full support | NordicTrack_RX800 | Yes | Yes | None | Also known under ProForm brand | +| Sportstech | WRX700 | Water rower | Impellor | Add one magnet | Active support | Sportstech_WRX700 | Yes | Yes | Static drag | see [Sportstech WRX700 setup](hardware_setup_WRX700.md) | +| White label | Air Rower | Air rower | Fywheel | None | Supported | Generic_Air_Rower | Yes | Yes | None | Sold under different brand names | +| Open ergo | - | Air rower | Flywheel | Addition of magnets en sensor | Known to work | - | Yes | Yes | None | Machine specific profile is needed, but is done before, see [example 1](https://github.com/laberning/openrowingmonitor/discussions/80), [example 2](https://github.com/laberning/openrowingmonitor/discussions/105) and [example 3](https://github.com/laberning/openrowingmonitor/discussions/115) | + +If your machine isn't listed, it just means that you need to [adjust the software settings following the settings adjustment guide](rower_settings.md) yourself. But don't worry, in the [GitHub Discussions](https://github.com/laberning/openrowingmonitor/discussions) there always are friendly people to help you set up your machine and the settings. + +## Support status + +In the table, the support status means the following: + +* **Active support**: These are the testmachines of the developers, these are tested almost on a daily basis. These settings are automatically modified to facilitate updates of the rowing engine; +* **Full support**: We actively maintain a the configuration, including automatically updating these settings to facilitate chages of the rowing engine, and are part of the automated regression test set. So as a user, you can be assured this setting will keep working; +* **Supported**: Users have reported a working configuration, and this configuration is part of `rowerProfiles.js`, but we lack the raw data samples to maintain the rower for future updates. This means that future support isn't guaranteed; +* **Configuration known**: Users have reported a working configuration, but it isn't actively supported by these users and it isn't on our rader to maintain. You need to add the configuration to your `config.js` manually and maintain it yourself when there are updates to the engine; +* **Known to work**: Users have reported that the rower is known to work, but the configuration is not known by us; +* **In development**: Users are known to be working to get the rower connected, but the configuration is not yet known by us. + +Please note: the support status largely depends on the willingness of users to report their settings and provide decent samples of their data. So when you have a machine, please provide this information. + +## Basic Metrics + +With basic metrics we mean: + +* Distance rowed, +* Training Duration, +* Power, +* Pace, +* Strokes per Minute, +* Drive time, +* Recovery Time, +* Calories used, +* Total number of strokes, +* Heart Rate + +## Extended Metrics + +With extended metrics, we mean: + +* Drag factor, +* Drive length, +* Average handle force, +* Peak handle force, +* Handle force curve, +* Handle velocity curve, +* Handle power curve. + +## Limitations + +With the limitation, we mean: + +* **None**: No limitations, drag calculation and distance per stroke are dynamic based on flywheel behaviour and automatically adapt to environmental conditions; +* **Static drag**: the drag calculation is fixed, so changes in air/water properties due to temperature or settings are not automatically adjusted; +* **Static distance**: the distance per impulse is fixed, thus making the measurement of a more forceful stroke impossible. This typically happens when the handle movement is measured, but not its effect on the flywheel. diff --git a/docs/attribution.md b/docs/attribution.md index 5e21640e46..909227f896 100644 --- a/docs/attribution.md +++ b/docs/attribution.md @@ -6,7 +6,7 @@ Open Rowing Monitor uses some great work by others. Thank you for all the great * Dave Vernooy's project description on [ErgWare](https://dvernooy.github.io/projects/ergware) has some good information on the maths involved in a rowing ergometer. -* Nomath has done a very impressive [Reverse engineering of the actual workings of the Concept 2 PM5](https://www.c2forum.com/viewtopic.php?f=7&t=194719), including experimentally checking drag calculations. +* Nomath has done a very impressive [Reverse engineering of the actual workings of the Concept 2 PM5](https://www.c2forum.com/viewtopic.php?f=7&t=194719), including experimentally checking drag calculations, which is at the base of our physics engine. * Bluetooth is quite a complex beast, luckily the Bluetooth SIG releases all the [Bluetooth Specifications](https://www.bluetooth.com/specifications/specs). @@ -14,4 +14,6 @@ Open Rowing Monitor uses some great work by others. Thank you for all the great * The frontend uses some icons from [Font Awesome](https://fontawesome.com/), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). -* Thank you to [Jaap van Ekris](https://github.com/JaapvanEkris) for his contributions to this project. +* Thank you to [Jaap van Ekris](https://github.com/JaapvanEkris) for his many contributions to this project, especially the physics engine and the file exports. + +* Thanks to [Abasz](https://github.com/Abasz) for his great contributions to the GUI, GPIO, BLE and Ant+ implementations, as well as the many constructive feedback that helped improve many areas of OpenRowingMonitor. diff --git a/docs/backlog.md b/docs/backlog.md index 50a35b1fa4..e9f5111948 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -7,8 +7,8 @@ If you would like to contribute to this project, please read the [Contributing G ## Soon * validate FTMS with more training applications and harden implementation (i.e. Holofit and Coxswain) -* add an option to select the damper setting in the Web UI -* add some more test cases to the rowing engine +* add possibility for user to define training interval timers (Server.js can already handle this, missing is the frontend to the web and PM5) +* add possibility for user to define workouts (i.e. training intervals with goals. Server.js can already handle this, missing is the frontend to the web and PM5) ## Later @@ -19,7 +19,4 @@ If you would like to contribute to this project, please read the [Contributing G ## Ideas -* add video playback to the Web UI -* implement or integrate some rowing games (i.e. a little 2D or 3D, game implemented as Web Component) -* add possibility to define training timers -* add possibility to define workouts (i.e. training intervals with goals) +* Add sounds and indicators for training zones diff --git a/docs/hardware_setup_Concept2_RowErg.md b/docs/hardware_setup_Concept2_RowErg.md new file mode 100644 index 0000000000..5db8d28e7f --- /dev/null +++ b/docs/hardware_setup_Concept2_RowErg.md @@ -0,0 +1,31 @@ +# Hardware set up of Open Rowing Monitor on a Concept 2 RowErg + +This guide explains how to set up Open Rowing Monitor for a Concept 2 RowErg. Please note that older Concept 2 models are NOT covered by this as the sensor on the flywheel has changed. + +## Hardware setup + +After the software installation, basically all that's left to do is hook up your sensor to the Raspberry Pi. However, the signal from a Concept 2 RowErg is a 15 Volt sinoid, which would destroy the Raspberry Pi's 3.3 Volts circuits. To isolate the circuits, we add an optocoupler in an non-destructive way, by rerouting the signal. Below is the wiring schematic of the Al-Zard DST-1R4P-P: + +![Optocoupler wiring connecting to the Raspberry Pi](img/Concept2_Optocoupler.jpg) +*Optocoupler wiring to the Raspberry Pi* + +On the left side, both the jack-plug and the jack-bus are 2.5mm, allowing the PM5 jackplug to be inserted and looped through if needed (allowing ORM to work side-by-side of the PM5). On the right, the connections to the Raspberry Pi are made. + +To get a stable reading you should add a pull-up resistor to that pin. I prefer to use the internal resistor of the Raspberry Pi to keep the wiring simple but of course you can also go with an external circuit. + +The internal pull-up can be enabled as described [here](https://www.raspberrypi.org/documentation/configuration/config-txt/gpio.md). So its as simple as adding the following to `/boot/config.txt` and then rebooting the device. + +``` Properties +# configure GPIO 17 as input and enable the pull-up resistor +gpio=17=pu,ip +``` + +## Rower Settings + +You should now adjust the rower specific parameters in `config/config.js` to suit your rowing machine. For the Concept 2 RowErg, there is a set of predefined parameters ready to use. So it suffices to add + +``` Properties +rowerSettings: Concept2_RowErg +``` + +to your `config/config.js` file. You can also look at `config/default.config.js` to see what other configuration parameters are available. diff --git a/docs/img/Concept2_Optocoupler.jpg b/docs/img/Concept2_Optocoupler.jpg new file mode 100644 index 0000000000..c9bec2dff5 Binary files /dev/null and b/docs/img/Concept2_Optocoupler.jpg differ diff --git a/docs/img/Concept2_RowErg_Construction_tolerances.jpg b/docs/img/Concept2_RowErg_Construction_tolerances.jpg new file mode 100644 index 0000000000..87601bf2df Binary files /dev/null and b/docs/img/Concept2_RowErg_Construction_tolerances.jpg differ diff --git a/docs/img/CurrentDt_With_Lots_Of_Bounce.jpg b/docs/img/CurrentDt_With_Lots_Of_Bounce.jpg new file mode 100644 index 0000000000..2b92517428 Binary files /dev/null and b/docs/img/CurrentDt_With_Lots_Of_Bounce.jpg differ diff --git a/docs/img/CurrentDt_curve.jpg b/docs/img/CurrentDt_curve.jpg new file mode 100644 index 0000000000..c199f36cfd Binary files /dev/null and b/docs/img/CurrentDt_curve.jpg differ diff --git a/docs/img/Metrics_Selection.png b/docs/img/Metrics_Selection.png new file mode 100644 index 0000000000..77730dec05 Binary files /dev/null and b/docs/img/Metrics_Selection.png differ diff --git a/docs/img/maximumTimeBetweenImpulses.jpg b/docs/img/maximumTimeBetweenImpulses.jpg new file mode 100644 index 0000000000..42b6dd155c Binary files /dev/null and b/docs/img/maximumTimeBetweenImpulses.jpg differ diff --git a/docs/installation.md b/docs/installation.md index 17126c4b7e..5f9b6443ef 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -19,34 +19,116 @@ This guide roughly explains how to set up the rowing software and hardware. ### Initialization of the Raspberry Pi -* Install **Raspberry Pi OS Lite** on the SD Card i.e. with the [Raspberry Pi Imager](https://www.raspberrypi.org/software) +* Install **Raspberry Pi OS Lite** on the SD Card i.e. with the [Raspberry Pi Imager](https://www.raspberrypi.org/software). Here, Raspberry Pi OS Lite 64 Bit is recommended as it is better suited for real-time environments. Please note that on a Raspberry Pi Zero or Zero 2, you need to increase the swap-size to 1024 otherwise the installation will fail (see [this manual how to do this](https://pimylifeup.com/raspberry-pi-swap-file/)); * Configure the network connection and enable SSH, if you use the Raspberry Pi Imager, you can automatically do this while writing the SD Card, just press `Ctrl-Shift-X`(see [here](https://www.raspberrypi.org/blog/raspberry-pi-imager-update-to-v1-6/) for a description), otherwise follow the instructions below * Connect the device to your network ([headless](https://www.raspberrypi.org/documentation/configuration/wireless/headless.md) or via [command line](https://www.raspberrypi.org/documentation/configuration/wireless/wireless-cli.md)) * Enable [SSH](https://www.raspberrypi.org/documentation/remote-access/ssh/README.md) +* Tune the OS if needed [by following our performance improvement guide](Improving_Raspberry_Performance.md) ### Installation of the Open Rowing Monitor Connect to the device with SSH and initiate the following command to set up all required dependencies and to install Open Rowing Monitor as an automatically starting system service: ```zsh -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/laberning/openrowingmonitor/HEAD/install/install.sh)" +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/JaapvanEkris/openrowingmonitor/v1beta_updates/install/install.sh)" ``` -### Updating to a new version - -Open Rowing Monitor does not provide proper releases (yet), but you can update to the latest development version with this command: - -```zsh -updateopenrowingmonitor.sh -``` - -### Running Open Rowing Monitor without root permissions (optional) - -The default installation will run Open Rowing Monitor with root permissions. You can also run it as normal user by modifying the following system services: - -#### To use BLE and open the Web-Server on port 80 - -Issue the following command: +### Check if OpenRowingMonitor runs without issue + +Next, check you need to do is to check the status of the Open Rowing Monitor service, which you can do with the command: + + ```zsh + sudo systemctl status openrowingmonitor + ``` + +Which typically results in the following response (with some additional logging): + + ```zsh + ● openrowingmonitor.service - Open Rowing Monitor + Loaded: loaded (/lib/systemd/system/openrowingmonitor.service; enabled; vendor preset: enabled) + Active: active (running) since Sun 2022-09-04 10:27:31 CEST; 12h ago + Main PID: 755 (npm start) + Tasks: 48 (limit: 8986) + CPU: 6min 48.869s + CGroup: /system.slice/openrowingmonitor.service + ├─755 npm start + ├─808 sh /tmp/start-6f31a085.sh + ├─809 node app/server.js + ├─866 /usr/bin/node ./app/gpio/GpioTimerService.js + └─872 /usr/bin/node ./app/ble/CentralService.js + ``` + +Please check if there are no errors reported. + +Please note that the process identification numbers will differ. + +You can also look at the the log output of the OpenRowingMonitor-service by putting the following in the command-line: + + ```zsh + sudo journalctl -u openrowingmonitor + ``` + +This allows you to see the current state of the rower. Typically this will show: + + ```zsh + Sep 12 20:37:45 roeimachine systemd[1]: Started Open Rowing Monitor. + Sep 12 20:38:03 roeimachine npm[751]: > openrowingmonitor@0.9.0 start + Sep 12 20:38:03 roeimachine npm[751]: > node app/server.js + Sep 12 20:38:06 roeimachine npm[802]: ==== Open Rowing Monitor 0.9.0 ==== + Sep 12 20:38:06 roeimachine npm[802]: Setting priority for the main server thread to -5 + Sep 12 20:38:06 roeimachine npm[802]: Session settings: distance limit none meters, time limit none seconds + Sep 12 20:38:06 roeimachine npm[802]: bluetooth profile: Concept2 PM5 + Sep 12 20:38:06 roeimachine npm[802]: webserver running on port 80 + Sep 12 20:38:06 roeimachine npm[862]: Setting priority for the Gpio-service to -7 + Sep 12 20:38:09 roeimachine npm[802]: websocket client connected + ``` + +Please check if there are no errors reported. The above snippet shows that OpenRowingMonitor is running, and that bluetooth and the webserver are alive, and that the webclient has connected. + +### Check if OpenRowingMonitor screen runs without issue (if installed) + +Next, check you need to do is to check the status of the Open Rowing Monitor service, which you can do with the command: + + ```zsh + sudo systemctl status webbrowserkiosk + ``` + +Which typically results in the following response (with some additional logging): + + ```zsh +● webbrowserkiosk.service - X11 Web Browser Kiosk + Loaded: loaded (/lib/systemd/system/webbrowserkiosk.service; enabled; vendor preset: enabled) + Active: active (running) since Wed 2024-01-31 23:46:27 CET; 11h ago + Main PID: 746 (xinit) + Tasks: 82 (limit: 8755) + CPU: 2min 50.292s + CGroup: /system.slice/webbrowserkiosk.service + ├─746 xinit /opt/openrowingmonitor/install/webbrowserkiosk.sh -- -nocursor + ├─747 /usr/lib/xorg/Xorg :0 -nocursor + ├─769 sh /opt/openrowingmonitor/install/webbrowserkiosk.sh + ├─774 /usr/bin/openbox --startup /usr/lib/aarch64-linux-gnu/openbox-autostart OPENBOX + ├─777 /usr/lib/chromium-browser/chromium-browser --enable-pinch --disable-infobars --disable-features=AudioServiceSandbox --kiosk --noerrdialogs --ignore-certificate-errors --disable-session-crashed-bubble --disable-pinch -> + ├─804 /usr/lib/chromium-browser/chrome_crashpad_handler --monitor-self --monitor-self-annotation=ptype=crashpad-handler --database=/home/pi/.config/chromium/Crash Reports --annotation=channel=Built on Debian , running on De> + ├─806 /usr/lib/chromium-browser/chrome_crashpad_handler --no-periodic-tasks --monitor-self-annotation=ptype=crashpad-handler --database=/home/pi/.config/chromium/Crash Reports --annotation=channel=Built on Debian , running > + ├─810 /usr/lib/chromium-browser/chromium-browser --type=zygote --no-zygote-sandbox --crashpad-handler-pid=0 --enable-crash-reporter=,Built on Debian , running on Debian 11 --noerrdialogs --change-stack-guard-on-fork=enable + ├─811 /usr/lib/chromium-browser/chromium-browser --type=zygote --crashpad-handler-pid=0 --enable-crash-reporter=,Built on Debian , running on Debian 11 --noerrdialogs --change-stack-guard-on-fork=enable + ├─820 /usr/lib/chromium-browser/chromium-browser --type=zygote --crashpad-handler-pid=0 --enable-crash-reporter=,Built on Debian , running on Debian 11 --noerrdialogs --change-stack-guard-on-fork=enable + ├─845 /usr/lib/chromium-browser/chromium-browser --type=gpu-process --enable-low-end-device-mode --ozone-platform=x11 --crashpad-handler-pid=0 --enable-crash-reporter=,Built on Debian , running on Debian 11 --noerrdialogs -> + ├─850 /usr/lib/chromium-browser/chromium-browser --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --ignore-certificate-errors --ignore-certificate-errors --crashpad-han> + ├─858 /usr/lib/chromium-browser/chromium-browser --type=utility --utility-sub-type=storage.mojom.StorageService --lang=en-US --service-sandbox-type=utility --ignore-certificate-errors --ignore-certificate-errors --crashpad-> + ├─877 /usr/lib/chromium-browser/chromium-browser --type=broker + └─884 /usr/lib/chromium-browser/chromium-browser --type=renderer --crashpad-handler-pid=0 --enable-crash-reporter=,Built on Debian , running on Debian 11 --noerrdialogs --change-stack-guard-on-fork=enable --first-renderer-p> + ``` + +Please check if there are no errors reported. + +Please note that the process identification numbers will differ. + +### To use BLE and open the Web-Server on port 80 + +#### Running Open Rowing Monitor without root permissions (optional) + +The default installation will run Open Rowing Monitor with root permissions. You can also run it as normal user by issueing the following command: ```zsh sudo setcap cap_net_bind_service,cap_net_raw=+eip $(eval readlink -f `which node`) @@ -67,8 +149,8 @@ Basically all that's left to do is hook up your sensor to the GPIO pins of the R Open Rowing Monitor reads the sensor signal from GPIO port 17 and expects it to pull on GND if the sensor is closed. To get a stable reading you should add a pull-up resistor to that pin. I prefer to use the internal resistor of the Raspberry Pi to keep the wiring simple but of course you can also go with an external circuit. -![Internal wiring of Raspberry Pi](img/raspberrypi_internal_wiring.jpg) -*Internal wiring of Raspberry Pi* + +Image showing the internal wiring of Raspberry Pi
The internal pull-up can be enabled as described [here](https://www.raspberrypi.org/documentation/configuration/config-txt/gpio.md). So its as simple as adding the following to `/boot/config.txt` and then rebooting the device. @@ -77,22 +159,65 @@ The internal pull-up can be enabled as described [here](https://www.raspberrypi. gpio=17=pu,ip ``` -How to connect this to your rowing machine is specific to your device. You need some kind of mechanism to convert the rotation of the flywheel into impulses. Some rowers have a reed sensor for this built-in, so hooking it up is as simple as connecting the cables. Such a sensor has one or more magnets on the wheel and each one gives an impulse when it passes the sensor. +How to connect this to your rowing machine is specific to your device. You need some kind of mechanism to convert the rotation of the flywheel into impulses. Some rowers have a reed sensor for this built-in, so hooking it up is as simple as connecting the cables. Such a sensor has one or more magnets on the wheel and each one gives an impulse when it passes the sensor. For a specific hardware-setup, please look at: -![Connecting the reed sensor](img/raspberrypi_reedsensor_wiring.jpg) -*Connecting the reed sensor* +* [Concept 2 RowErg](hardware_setup_Concept2_RowErg.md) +* [Sportstech WRX700](hardware_setup_WRX700.md) -For a specific hardware-setup, please look at: +If your machine isn't listed, you can still follow this generic manual for hardware setup, and [adjust the software settings following the settings adjustment guide](rower_settings.md). -* [Sportstech WRX700](hardware_setup_WRX700.md) + +Image showing the connection of the reed sensor
-If your machine isn't listed and does not have something like this or if the sensor is not accessible, you can still build something similar quite easily. Some ideas on what to use: +If you do not have and does not have something like this or if the sensor is not accessible, you can still build something similar quite easily. Some ideas on what to use: * Reed sensor (i.e. of an old bike tachometer) +* HAL effect sensor * PAS sensor (i.e. from an E-bike) * Optical chopper wheel ## Rower Settings -You should now adjust the rower specific parameters in `config/config.js` to suit your rowing machine. Have a look at `config/default.config.js` to see what config parameters are available. -Also check the [Guide for rower specific settings](rower_settings.md). +You should now adjust the rower specific parameters in `config/config.js` to suit your rowing machine. You should select a specific rower from the `rowerProfiles.js`, or create your own settings following this [guide for creating the rower specific settings](rower_settings.md). Also have a look at `config/default.config.js` to see what additional config parameters are available to suit your needs. + +Once all parameters are set, look at the the log output of the OpenRowingMonitor-service by putting the following in the command-line: + + ```zsh + sudo journalctl -u openrowingmonitor + ``` + +This allows you to see the current state of the rower. Typically this will show: + + ```zsh + Sep 12 20:37:45 roeimachine systemd[1]: Started Open Rowing Monitor. + Sep 12 20:38:03 roeimachine npm[751]: > openrowingmonitor@0.9.0 start + Sep 12 20:38:03 roeimachine npm[751]: > node app/server.js + Sep 12 20:38:06 roeimachine npm[802]: ==== Open Rowing Monitor 0.9.0 ==== + Sep 12 20:38:06 roeimachine npm[802]: Setting priority for the main server thread to -5 + Sep 12 20:38:06 roeimachine npm[802]: Session settings: distance limit none meters, time limit none seconds + Sep 12 20:38:06 roeimachine npm[802]: bluetooth profile: Concept2 PM5 + Sep 12 20:38:06 roeimachine npm[802]: webserver running on port 80 + Sep 12 20:38:06 roeimachine npm[862]: Setting priority for the Gpio-service to -7 + Sep 12 20:38:09 roeimachine npm[802]: websocket client connected + ``` + +Please check if there are no errors reported, especially for configuration parameters. OpenRowingMonitor will report if it detects abnormal or missing parameters. + +### Setting up Strava upload + +Part of the specific parameters in `config/config.js` are the Strava settings. To use this, you have to create a Strava API Application as described [here](https://developers.strava.com/docs/getting-started/#account) and use the corresponding values. When creating your Strava API application, set the "Authorization Callback Domain" to the IP address of your Raspberry Pi. + +Once you get your Strava credentials, you can add them in `config/config.js`: + +```js +stravaClientId: "StravaClientID", +stravaClientSecret: "client_secret_string_from_the_Strava_API", +``` + +## Updating OpenRowingMonitor to a new version + +Open Rowing Monitor does not provide proper releases (yet), but you can update to the latest development version with this command: + +```zsh +updateopenrowingmonitor.sh +``` diff --git a/docs/physics_openrowingmonitor.md b/docs/physics_openrowingmonitor.md index 6bb80f2984..7ffbd67ac0 100644 --- a/docs/physics_openrowingmonitor.md +++ b/docs/physics_openrowingmonitor.md @@ -1,26 +1,25 @@ # The physics behind Open Rowing Monitor -In this document we explain the physics behind the OpenRowing Monitor, to allow for independent review and software maintenance. This work wouldn't have been possible without some solid physics, described by some people with real knowledge of the subject matter. Please note that any errors in our implementation probably is on us, not them. When appropriate, we link to these sources. When possible, we also link to the source code. +In this document we explain the physics behind the Open Rowing Monitor, to allow for independent review and software maintenance. This work wouldn't have been possible without some solid physics, described by some people with real knowledge of the subject matter. Please note that any errors in our implementation probably is on us, not them. When appropriate, we link to these sources. When possible, we also link to the source code to allow further investigation and keep the link with the actual implementation. -## Leading principles +Please note that this text is used as a rationale for design decissions of the physics used in Open Rowing Monitor. So it is of interest for people maintaining the code (as it explains why we do things the way we do) and for academics to verify or improve our solution. For these academics, we conclude with a section of open design issues as they might provide avenues of future research. If you are interested in just using Open Rowing Monitor as-is, this might not be the text you are looking for. -The physics engine is the core of Open Rowing Monitor. In our design of the physics engine, we try to: +## Basic concepts -* stay as close to the original data as possible (thus depend on direct measurements as much as possible) instead of depending on derived data. This means that there are two absolute values we try to stay close to as much as possible: the **time between an impulse** and the **Number of Impulses** (their origin and meaning is later explained); +Before we analyze the physics of a rowing engine, we first need to define the basic concepts. First we identify the key physical systems at play, then we define the key phases in the rowing stroke. -* use robust calculations wherever possible (i.e. not depend on a single measurements, extrapolations or derivative functions, etc.) to reduce effects of measurement errors. +### Physical systems in a rower -## Phases, properties and concepts in the rowing cycle +A rowing machine effectively has two fundamental movements: -Before we analyze the physics of a rowing engine, we first need to define the basic concepts. +* a **linear** movement (the rowing person moving up and down the rail, or a boat moving forward) and +* a **rotational** movement where the energy that the rower inputs in the system is absorbed through a flywheel (either a solid one, or a liquid one) [[1]](#1). - +Physically these movements are related, as they are connected by a chain or belt, allowing the rowing person to move the flywheel. This is shown in the following figure: - -*A basic view of an indoor rower* - -A rowing machine effectively has two fundamental movements: a **linear** (the rower moving up and down, or a boat moving forward) and a **rotational** where the energy that the rower inputs in the system is absorbed through a flywheel (either a solid one, or a liquid one). +Image showing a rowing machine with its linear and rotational energy systems +A basic view of an indoor rower's energy systems The linear and rotational speeds are related: the stronger/faster you pull in the linear direction, the faster the flywheel will rotate. The rotation of the flywheel simulates the effect of a boat in the water: after the stroke, the boat will continue to glide only be dampened by the drag of the boat, so does the flywheel. @@ -32,244 +31,468 @@ There are several types of rowers: * **Magnetic resistance**: where the resistance is constant -There are also hybrid rowers, which combine air resistance and magnetic resistance. The differences in physical behavior can be significant, for example a magnetic rower has a constant resistance while a air/water rower's resistance is dependent on the flywheel's speed. As the key principle is the same for all these rowers (some mass is made to spin and drag brings its speed down), we treat them the same. +There are also hybrid rowers, which combine air resistance and magnetic resistance. The differences in physical behavior can be significant, for example a magnetic rower has a constant resistance while a air rower's resistance is dependent on the flywheel's speed. We suspect that on a water rower behaves slightly different from an air rower, as the rotated water mass changes shape when the rotational velocity changes. Currently for Open Rowing Monitor, we consider that the key principle is similar enough for all these rowers (some mass is made to spin and drag brings its speed down) to treat them all as an air rower as a first approximation. However, we are still investigating how to adapt for these specific machines. -Typically, measurements are done in the rotational part of the rower, on the flywheel. There is a reed sensor or optical sensor that will measure time between either magnets or reflective stripes, which gives an **Impulse** each time a magnet or stripe passes. Depending on the **number of impulse providers** (i.e. the number of magnets or stripes), the number of impulses per rotation increases, increasing the resolution of the measurement. By measuring the **time between impulses**, deductions about speed and acceleration of the flywheel can be made, and thus the effort of the rower. +### Phases in the rowing stroke -## What a rowing machine actually measures +What seperates rowing from many other sports is its discontinous nature: for example, in Cycling the force constantly shifts between left and right leg, but remains relatively constant throughout the rotation. In rowing, a stroke begins with a powerful *Drive* phase, which is followed by an unpowered *Recovery*. Visually, it can be depicted as follows: -As mentioned above, most rowers depend on measuring the **time between impulses**, triggered by some impulse giver (magnet or light) on the flywheel. For example, when the flywheel rotates on a NordicTrack RX800, the passing of a magnet on the flywheel triggers a reed-switch, that delivers a pulse to our Raspberry Pi. We measure the time between two subsequent impulses and call this *currentDt*: the time between two impulses. *currentDt* is the basis for all our calculations. +```mermaid +stateDiagram-v2 + direction LR + Drive --> Recovery + Recovery --> Drive +``` -The following picture shows the time between impulses through time: -![Measurements of flywheel](img/physics/flywheelmeasurement.png) -*Measurements of flywheel* +Basic phases of a rowing stroke + +On an indoor rower, the rowing cycle will always start with a stroke, followed by a recovery. We define them as follows: + +* The **Drive phase**, where the rower pulls on the handle -Here, it is clear that the flywheel first accelerates and then decelerates, which is typical for rowing. +* The **Recovery Phase**, where the rower returns to his starting position -Using *currentDt* means we can't measure anything directly aside from *angular displacement*, and that we have to accept some noise in measurements. For example, as we don't measure torque on the flywheel directly, we can't determine where the flywheel exactly accelerates/decelerates, we can only detect a change in the times between impulses. In essence, we only can conclude that an acceleration has taken place somewhere near a specific impulse, but we can't be certain about where the acceleration exactly has taken place and we can only estimate how big the force must have been. Additionally, small vibrations in the chassis due to tiny unbalance in the flywheel can lead to small deviations in measurements. This kind of noise in our measurement can make many subsequent derived calculation on this measurement too volatile, This is why we explicitly distinguish between *measurements* and *estimates* based on these measurements, to clearly indicate their potential volatility. +Combined, we define a *Drive* followed by a *Recovery* a **Stroke**. In the calculation of several metrics, the requirement is that it should include *a* *Drive* and *a* *Recovery*, but order isn't a strict requirement for some metrics [[2]](#2). We define such combination of a *Drive* and *Recovery* without perticular order a **Cycle**, which allows us to calculate these metrics twice per *stroke*. -Dealing with noise is an dominant issue, especially because we have to deal with many types of machines. Aside from implementing a lot of noise reduction, we also focus on using robust calculations: calculations that don't deliver radically different results when a small measurement error is introduced in the measurement of *currentDt*. We typically avoid things like derived functions when possible, as deriving over small values of *currentDt* typically produce huge deviations in the resulting estimate. We sometimes even do this at the cost of inaccuracy with respect to the perfect theoretical model, as long as the deviation is systematic in one direction, to prevent estimates to become too unstable for practical use. +## Leading design principles of the rowing engine -## The rowing cycle and detecting the stroke and recovery phase +As described in [the architecture](Architecture.md), the rowing engine is the core of Open Rowing Monitor and consists of three major parts: -On an indoor rower, the rowing cycle will always start with a stroke, followed by a recovery. Looking at a stroke, our monitor gets the following data from its sensor: +* `engine/Flywheel.js`, which determines rotational metrics, -![Impulses, impulse lengths and rowing cycle phases](img/physics/rowingcycle.png) -*Impulses, impulse lengths and rowing cycle phases* +* `engine/Rower.js`, which transforms rotational metrics in a rowing state and linear metrics, -Here, we plot the *currentDt* (time between impulses) against its sequence number. So, a high *currentDt* means a long time between impulses (so a low *angular velocity*), and a low *currentDt* means that there is a short time between impulses (so a high *angular velocity*). As this figure also shows, we split the rowing cycle in two distinct phases: +* `engine/RowingStatistics.js`, which manages session state, session metrics and optimizes metrics for presentation. -* The **Drive phase**, where the rower pulls on the handle +Although the physics is well-understood and even well-described publicly (see [[1]](#1),[[2]](#2),[[3]](#3) and [[4]](#4)), applying these formulae in a practical solution for multiple rowers delivering reliable results is quite challenging. Especially small errors, noise, tends to produce visible effects on the recorded metrics. Therefore, in our design of the physics engine, we obey the following principles (see also [the architecture document](Architecture.md)): -* The **Recovery Phase**, where the rower returns to his starting position +* stay as close to the original data as possible (thus depend on direct measurements as much as possible) instead of heavily depend on derived data. This means that there are two absolute values we try to stay close to as much as possible: the **time between an impulse** and the **Number of Impulses**, where we consider **Number of Impulses** most reliable, and **time between an impulse** reliable but containing noise (the origin and meaning of these metrics, as well the effects of this approach are explained later); + +* use robust calculations wherever possible (i.e. not depend on a single measurements, extrapolations, derivation, etc.) to reduce effects of measurement errors. A typical issue is the role of *CurrentDt*, which is often used as a divisor with small numers as Δt, increasing the effect of measurement errors in most metrics. When we do need to calculate a derived function, we choose to use a robust linear regression method to reduce the impact of noise and than use the function to calculate the derived function; + +* Be as close to the results of the Concept2 when possible and realistic, as they are considered the golden standard in indoor rowing metrics. + +## Relevant rotational metrics + +Typically, actual measurements are done in the rotational part of the rower, on the flywheel. We explicitly assume that Open Rowing Monitor measures the flywheel movement (directly or indirectly). Some rowing machines are known to measure the movement of the driving axle and thus the velocity and direction of the handle, and not the driven flywheel. This type of measurement blocks access to the physical behaviour of the flywheel (especially acceleration and coast down behaviour), thus making most of the physics engine irrelevant. Open Rowing Monitor can handle some of these rowing machines by fixing specific parameters, but as this measurement approach excludes any meaningful measurement, we will exclude it in the further description. + +In a typical rowing machine, there is a magnetic reed sensor or optical sensor that will measure time between either magnets or reflective stripes on the flywheel or impellor, which gives an **Impulse** each time a magnet or stripe passes. For example, when the flywheel rotates on a NordicTrack RX800, the passing of a magnet on the flywheel triggers a reed-switch, that delivers a pulse to our Raspberry Pi. + +Depending on the **number of impulse providers** (i.e. the number of magnets or stripes), the number of impulses per rotation increases, increasing the resolution of the measurement. As described in [the architecture](Architecture.md), Open Rowing Monitor's `GpioTimerService.js` measures the time between two subsequent impulses and reports as a *currentDt* value. The constant stream of *currentDt* values is the basis for all our angular calculations, which are typically performed in the `pushValue()` function of `engine/Flywheel.js`. + +Open Rowing Monitor needs to keep track of several metrics about the flywheel and its state, including: + +* The **Angular Distance** of the flywheel in Radians (denoted with θ): in essence the distance the flywheel has traveled (i.e. the number of Radians the flywheel has rotated) since the start of the session; -As the rowing cycle always follows this fixed schema, Open Rowing Monitor models it as a finite state machine (implemented in `handleRotationImpulse` in `engine/RowingEngine.js`). +* The **Time since start** of the flywheel in seconds (denoted with t): in essence the time the flywheel has been spinning since the start of the session; -![Finite state machine of rowing cycle](img/physics/finitestatemachine.png) -*Finite state machine of rowing cycle* +* The **Angular Velocity** of the flywheel in Radians \* s-1 (denoted with ω): in essence the number of (partial) rotations of the flywheel per second; -### Basic stroke detection +* The **Angular Acceleration** of the flywheel in Radians \* s-2 (denoted with α): the acceleration/deceleration of the flywheel; -Given that the *Angular Displacement* between impulses is fixed, we can deduct some things simply from looking at the subsequent *time between impulses*, *currentDt*. When the *currentDt* shortens, *Angular Velocity* is increasing, and thus the flywheel is accelerating (i.e. we are in the drive phase of the rowing cycle). When times between subsequent impulses become longer, the *Angular Velocity* is decreasing and thus the flywheel is decelerating (i.e. we are the recovery phase of the rowing cycle). This is the robust implementation of a stroke (implemented in MovingFlankDetector's implementation of isFlywheelPowered and isFlywheelUnpowered for naturalDeceleration = 0 in `engine/MovingFlankDetector.js`), which is similar to the implementation used by industry leaders like Concept2. Concept2 are generally considered the golden standard when it comes to metrics, and they state (see [this Concept2 FAQ](https://www.concept2.com/service/software/ergdata/faqs): 'Drive time is measured by the amount of time the flywheel is accelerating. Note: It is possible that the athlete may pull on the handle and not accelerate the flywheel due to no energy being put into it and therefore no effective effort. This portion of the handle pull is not measured.') +* The **flywheel inertia** of the flywheel in kg \* m2 (denoted with I): the resistance of the flywheel to acceleration/deceleration; -### Advanced stroke detection +* The *estimated* **drag factor** of the flywheel in N \* m \* s2 (denoted with k): the level of (air/water/magnet) drag encountered by the flywheel, as a result of a damper setting. -Looking at the average curve of an actual rowing machine (this example is based on averaging 300 strokes), we see the following: +* The **Torque** of the flywheel in kg \* m2 \* s-2 (denoted with τ): the momentum of force on the flywheel. -![Average curves of a rowing machine](img/physics/currentdtandacceleration.png) -*Average currentDt (red) and Acceleration (blue) of a single stroke on a rowing machine* +* Detecting power on the flywheel: whether there is a force on the flywheel. -In this graph, we plot *currentDt* against the time in the stroke, averaged over 300 strokes. As *currentDt* is (reversely) related to angular velocity, we can calculate the angular acceleration/deceleration. In essence, as soon as the acceleration becomes below the 0, the currentDt begins to lengthen again (i.e. the flywheel is decelerating). As indicated earlier, this is the trigger for the robust stroke detection algorithm (i.e. the one used when naturalDeceleration is set to 0): when the *currentDt* starts to lengthen, the drive-phase is considered complete. +Being limited to the time between impulses, *currentDt*, as only measurement means we can't measure any of these metrics directly, and that we have to accept some deviations in these measurements as they are reported in discrete intervals. -However, from the acceleration/deceleration curve it is also clear that despite the deceleration, there is still a force present: the deceleration-curve hasn't reached its stable minimum despite crossing 0. This is due to the pull still continuing through the arms: the net force is negative due to a part drive-phase (the arm-movement) delivering weaker forces than the drag-forces of the flywheel. Despite being weaker than the other forces on the flywheel, the rower is still working. In this specific example, at around 0.52 sec the rower's force was weaker than all drag-forces combined. However, only at 0,67 seconds (thus approx. 150 ms later) the net force reaches its stable bottom: the only force present is the drag from the flywheel. Getting closer to this moment is a goal. +Additionally, small mechanical deviations, vibrations in the chassis (due to tiny unbalance in the flywheel) and latency inside the software stack can lead to small deviations the measurement of *currentDt*. Dealing with these deviations is an dominant issue, especially because we have to deal with a wide range machines. Aside from implementing noise reduction, we also focus on using robust calculations: calculations that don't deliver radically different results when a small measurement error is introduced in the measurement of *currentDt*. We typically avoid things like direct deriviations based on single values, as directly deriving over small values of *currentDt* with small errors typically produce huge deviations in the resulting estimate. As an alternative, we use (robust) regression over multiple values, and use the deriviations of the resulting function instead. We do this at the cost of reducing the accuracy of the data, as this approach tends to dampen real occuring peaks in the stroke data. However, this inaccuracy with respect to the perfect theoretical model is needed to prevent estimates to become too unstable for practical use or which can only be used with heavy smoothing later on in the process (typically smoothing across strokes by `engine/RowingStatistics.js`). -By specifying the expected natural deceleration of the flywheel (naturalDeceleration, which in this case is around 8 Rad/S^2) in the configuration of the rower, the stroke starts earlier and ends later (implemented in MovingFlankDetector's implementation of isFlywheelPowered and isFlywheelUnpowered for naturalDeceleration < 0 in `engine/MovingFlankDetector.js`). Please note: as several derived metrics depend on the length of the drive phase or the exact timing of that moment (like the drag factor when calculated dynamically), these are likely to change when this setting is changed. For a more in-depth explanation, see [here for more details](physics_openrowingmonitor.md#a-closer-look-at-the-effects-of-the-various-drive-and-recovery-phase-detection). +### Determining the "Time since start" of the flywheel -Testing shows that setting a value close to the natural deceleration provides more accurate results very reliably. However, some rowers might contain a lot of noise in their data, making this approach infeasible (hence the fallback option of naturalDeceleration = 0) +This can easily be measured by summarising the **time between an impulse**. Noise has little to no impact to this metric as on average the noise cancels out. -This approach is a better approximation than the acceleration/deceleration approach, but still is not perfect. For example, drag-force of the the rower presented in the above graph slowly reduces. This is expected, as the drag-force is speed dependent. For a pure air-rower, the best theoretical approach would be to see if the drag-force is the only force present by calculating the expected drag-force using the current speed and the drag factor (making the stroke detection completely independent on speed). Testing has shown that this approach is too prone to errors, as it requires another derivation with respect to *currentDt*, making it too volatile. Above this, hybrid rower behave differently as well: dependent on the speed, the balance shifts between the speed-dependent air-resistance drag-force and the speed-independent magnetic resistance force. To make the solution robust and broadly applicable, this approach has been abandoned. +### Determining the "Angular Position" of the flywheel -## Key physical metrics during the rowing cycle +As the impulse-givers are evenly spread over the flywheel, this can be robustly measured by counting the total number of impulses, **Number of Impulses**, and multiply it with the **angular displacement** between two **impulses** (i.e. ${2π \over number of impulse providers on the flywheel}$). -There are several key metrics that underpin the performance measurement of a rowing stroke. Here, we distinguish the following concepts: +In theory, there are two threats here: -* The **Angular Displacement** of the flywheel in Radians: in essence the distance the flywheel has traveled (i.e. the number of Radians the flywheel has rotated). As the impulse-givers are evenly spread over the flywheel, the **angular displacement** between two **impulses** is 2π/(*number of impulse providers on the flywheel*). This can easily be measured by counting the number of impulses; +* Potentially missed impulses due to sticking sensors or too short intervals for the Raspberry Pi to detect them. So far, this hasn't happened. +* Ghost impulses, typically caused by **bounce** effects of the sensor where the same magnet is seen twice by the sensor. The best resolution is a better mechanical construction of magnets and sensors or adjust the **debounce filter**. -* The **Angular Velocity** of the flywheel in Radians/second: in essence the number of (partial) rotations of the flywheel per second. As the *Angular Displacement* is fixed for a specific rowing machine, the *Angular Velocity* is (*angular displacement between impulses*) / (time between impulses); +### Determining the "Angular Velocity" and "Angular Acceleration" of the flywheel -* The **Angular Acceleration** of the flywheel (in Radians/second^2): the acceleration/deceleration of the flywheel; +The traditional approach [[1]](#1), [[8]](#8), [[13]](#13) suggeste a numerical approach to Angular Velocity ω: -* The *estimated* **Linear Distance** of the boat (in Meters): the distance the boat is expected to travel; +$$ ω = {Δθ \over Δt} $$ -* *estimated* **Linear Velocity** of the boat (in Meters/Second): the speed at which the boat is expected to travel. +This formula is dependent on Δt, which is suspect to noise, making this numerical approach to the calculation of ω volatile. From a more robust perspective, we approach ω as the the first derivative of the function between *time since start* and the angular position θ, where we use a robust regression algorithm to determine the function and thus the first derivative. -## Measurements during the recovery phase +The traditional numerical approach [[1]](#1), [[8]](#8), [[13]](#13) Angular Acceleration α would be: -Although not the first phase in a cycle, it is an important phase as it deducts specific information about the flywheel properties [[1]](#1). During the recovery-phase, we can *measure* the number of impulses and the length of each impulse. Some things we can easily *estimate* with a decent accuracy based on the data at the end of the recovery phase: +$$ α = {Δω \over Δt} $$ -* The length of time between the start and end of the recovery phase +Again, the presence of Δt would make this alculation of α volatile. From a more robust perspective, we approach α as the the second derivative of the function between *time since start* and the angular position θ, where we use a robust regression algorithm to determine the function and thus the second derivative. -* The angular displacement between the start and end of the recovery - phase +Summarizing, both Angular Velocity ω and Angular Acceleration α are determined through the same regression algorithm based on the derivatives of the function between *time since start* and the angular position θ, where the first derivative of the function represents the Angular Velocity ω and the second derivative represents the Angular Acceleration α. -* The angular velocity at the beginning and end of the recovery phase +### Determining the "drag factor" of the flywheel -In the recovery phase, the only force exerted on the flywheel is the (air/water/magnetic)resistance. Thus we can calculate the Drag factor of the Flywheel based on the entire phase. +In the recovery phase, the only force exerted on the flywheel is the (air-/water-/magnetic-)resistance. Thus we can calculate the *drag factor of the flywheel* based on deceleration through the recovery phase [[1]](#1). This calculation is performed in the `markRecoveryPhaseCompleted()` function of `engine/Flywheel.js`. There are several approaches described in literature [[1]](#1), which Open Rowing Monitor extends to deliver a reliable and practically applicable approach. -As [[1]](#1) describes in formula 7.2, which is also experimentally verified by Nomath on a Concept 2 [[5]](#5): +A first numerical approach is presented by through [[1]](#1) in formula 7.2a: -> +$$ k = - I \* {Δω \over Δt} * {1 \over Δω^2} $$ -Or in more readable form: +Where the resulting k should be averaged across the rotations of the flywheel. The downside of this approach is that it introduces Δt in the divider of the drag calculation, making this calculation potentially volatile, especially in the presence of systematic errors in the flywheel construction (as is the case with Concept2 Model D and later). Our practical experience based on testing confirms this volatility. An alternative numerical approach is presented by through [[1]](#1) in formula 7.2b: -> +$$ k = -I \* {Δ({1 \over ω}) \over Δt} $$ -Looking at the linear speed, we use the following formula [[1]](#1), formula 9.3: +Where this is calculated across the entire recovery phase. Again, the presence of Δt in the divider potentially introduces a type of undesired volatility. Testing has shown that even when Δt is chosen to span the entire recovery phase, reducing the effect of single values of *CurrentDt*, the calculated drag factor is more stable but still is too unstable to be used as both the ω's used in this calculation still depend on single values of *CurrentDt*. Additionally, small errors in detection of the drive or recovery phase would change ω dramatically, throwing off the drag calculation significantly (see also [this elaboration](physics_openrowingmonitor.md#use-of-simplified-power-calculation)). Therefore, such an approach typically requires averaging across strokes to prevent drag poisoning (i.e. a single bad measurement of *currentDt* throwing off the drag factor significantly, and thus throwing off all dependent linear metrics significantly), which still lacks robustness of results as drag tends to fluctuate throughout a session. -> +To make this calculation more robust, we again turn to regression methods (as suggested by [[7]](#7)). We can transform formula 7.2 to the definition of the slope of a line, by doing the following: -Or in more readable form: +$$ { k \over I } = {Δ({1 \over ω}) \over Δt} $$ -> +Thus k/I represents the slope of the graph depicted by *time since start* on the *x*-axis and ${1 \over ω}$ on the *y*-axis, during the recovery phase of the stroke. However, this formula can be simplified further, as the angular velocity ω is determined by: -Looking at the linear speed, we use the following formula [[1]](#1), formula 9.2: +$$ ω = {({2π \over Impulses Per Rotation}) \over currentDt} $$ -> +thus making: -Or in more readable form: +$$ { k \over I } = {Δ({1 \over {({2π \over Impulses Per Rotation}) \over currentDt}}) \over Δt} $$ -> +removing the division, results in -## Measurements during the drive phase +$$ { k \over I } = {Δ(currentDt \* {Impulses Per Rotation \over 2π}) \over Δt} $$ -During the drive-phase, we again can *measure* the number of impulses and the length of each impulse. Some things we can easily *estimate* with a decent accuracy based on the data at the end of the drive phase: +Since we are multiplying *currentDt* with a constant factor (i.e. ${Impulses Per Rotation \over 2π}$), we can further simplify the formula by moving this multiplication outside the slope-calculation. Effectively, making the formula: -* The length of time between the start and end of the drive phase +$$ {k \* 2π \over I \* Impulses Per Rotation} = {ΔcurrentDt \over Δt} $$ -* The angular displacement between the start and end of the drive phase +As the left-hand of the equation only contains constants and the dragfactor, and the right-hand a division of two delta's, we can use regression to calculate the drag. As the slope of the line *currentDt* over *time since start* is equal to ${k \* 2π \over I \* Impulses Per Rotation}$, the drag thus can be determined through -* The angular velocity at the beginning and end of the drive phase +$$ k = slope \* {I \* Impulses Per Rotation \over 2π} $$ -Looking at the linear speed, we use the following formula [[1]](#1), formula 9.3: +As this formula shows, the drag factor is effectively determined by the slope of the line created by *time since start* on the *x*-axis and the corresponding *CurrentDt* on the *y*-axis, for each recovery phase. -> +This slope can be determined through linear regression (see [[5]](#5) and [[6]](#6)) for the collection of datapoints for a specific recovery phase. This approach also brings this calculation as close as possible to the raw data, and doesn't use individual *currentDt*'s as a divider, which are explicit design goals to reduce data volatility. For determining the slope, we use the linear Theil-Sen Estimator, which is sufficiently robust against noise, especially when filtering on low R2. On a Concept2, the typical R2 is around 0.96 (low drag) to 0.99 (high drag) for steady state rowing. The approach of using r2 has the benefit of completely relying on metrics contained in the algorithm itself for quality control: the algorithm itself signals a bad fit due to too much noise in the calculation. Additionally, as the drag does not change much from stroke to stroke, a running weighed average across several strokes is used, where the R2 is used as its weight. This has the benefit of favouring better fitting curves over less optimal fitting curves (despite all being above the R2 threshold set). Practical experiments show that this approach outperforms any other noise dampening filter. -Or in more readable form: +### Determining the "Torque" of the flywheel + +The torque τ on the flywheel can be determined based on formula 8.1 [[1]](#1): + +$$ τ = I \* ({Δω \over Δt}) + D $$ + +As ${Δω \over Δt}$ = α and D = k \* ω2 (formula 3.4, [[1]](#1)), we can simplify this further by: + +$$ τ = I \* α + k \* ω^2 $$ + +As α and ω have been derived in a robust manner, and there are no alternative more robust approaches to determining instant τ that allows for handle force curves, we consider this the best attainable result. Testing shows that the results are quite useable. + +## Detecting the stroke phase + +One of the key elements of rowing is detecting the stroke phases and thus calculate the associated metrics for that phase. Assuming that `engine/Flywheel.js` has determined whether there is a force present on the flywheel, `engine/Rower.js` can now transform this information into the phase of the rowing stroke. On an indoor rower, the rowing cycle will always start with a drive, followed by a recovery. This results in the follwing phases: + +* The **Drive phase**, where the rower pulls on the handle, some force on the flywheel is excerted and the flywheel is accelerating or at least not decelerating in accordance with the drag; + +* The **Recovery Phase**, where the rower returns to his starting position and the flywheel decelerates as the drag on the flywheel is slowing it down; + +As the rowing cycle always follows this fixed schema, Open Rowing Monitor models it as a finite state machine (implemented in `handleRotationImpulse` in `engine/Rower.js`). + +```mermaid +stateDiagram-v2 + direction LR + Drive --> Recovery: Flywheel
isn't powered + Drive --> Drive: Flywheel
is powered + Recovery --> Drive: Flywheel
is powered + Recovery --> Recovery: Flywheel
isn't powered +``` + +Finite state machine of rowing cycle + +From the perspective of Open Rowing Monitor, there only is a stream of *CurrentDt*'s, which should form the basis of this detection: + +The following picture shows the time between impulses through time: +Image showing the currentDt measurements of the flywheel through time +example currentDt Measurements of a flywheel -> +Open Rowing Monitor combines two types of force detection, which work independently: *basic force detection* and *advanced stroke detection*. Both can detect a stroke accuratly, and the combination has proven its use. -Looking at the linear speed, we use the following formula [[1]](#1), formula 9.2: +In `engine/Flywheel.js`, two functions provide force detection, which use the following criteria before attempting a stroke phase transition: -> +* `isPowered()`: which indicates a force is present, suggesting a drive phase. This is true when the slope of a series of *flankLength* times between impulses is below the **minumumRecoverySlope** (i.e. accelerating, as is the case in the measurements in above figure before the dotted line) AND the handleforce is above **minumumForceBeforeStroke** (i.e. the torque τ is above a certain threshold); -Or in more readable form: +* `isUnpowered()`: which indicates that there is no force present, suggesting a recovery phase. This is true when the slope of a series of *flankLength* times between impulses is above the **minumumRecoverySlope** (i.e. decelerating, as is the case in the measurements in above figure after the dotted line) where the goodness of fit of that slope exceeds the **minimumStrokeQuality** OR the handleforce is below **minumumForceBeforeStroke** (i.e. the torque τ is below a certain threshold) -> +The choice for the logical relations between the two types of force detection is based on testing: where a sudden presence of force on a flywheel (i.e. the start of a drive) is quite easily and consistently detected, its abscence has proven to be more difficult. In practice, the beginning of a drive is easily recognised as strong leg muscles excert much force onto the flywheel in a very short period of time, leading to an easily recognisable (large) torque τ and a sudden decrease in currentDt's. The end of the drive is more difficult to assess, as the dragforce of the flywheel increases with its speed, and the weaker arm muscles have taken over, making the transition to the recovery much harder to detect. In theory, in the end of the drive phase the drag force might be bigger than the force from the arms, resulting in an overall negative torque. -## Power calculation +In the remainder of this paragraph, we describe the underlying physics of both these force detection methods. -In the drive phase, the rower also puts a force on the flywheel, making it accelerate. +### Basic force detection through currentDt slope + +One of the key indicator is the acceleration/decelleration of the flywheel. Looking at a simple visualisation of the rowing stroke, we try to achieve the following: + +Image showing the relation between Impulses, impulse lengths and rowing cycle phases +Impulses, impulse lengths and rowing cycle phases + +Here we plot the *currentDt* against its sequence number. So, a high *currentDt* means a long time between impulses (so a low *angular velocity*), and a low *currentDt* means that there is a short time between impulses (so a high *angular velocity*). + +Here, it is clear that the flywheel first accelerates (i.e. the time between impulses become smaller), suggesting a powered flywheel. Next it decelerates (i.e. the time between impulses become bigger), which suggests an unpowered flywheel. This pattern is typical for the rowing motion. + +The simple force detection uses this approach by looking at the slope of *currentDt* over time. Given that the *Angular Displacement* between impulses is fixed, we can deduct some things simply from looking at the subsequent *time between impulses*, *currentDt*. When the *currentDt* shortens, *Angular Velocity* is increasing, and thus the flywheel is accelerating (i.e. we are in the drive phase of the rowing cycle). When times between subsequent impulses become longer, the *Angular Velocity* is decreasing and thus the flywheel is decelerating (i.e. we are the recovery phase of the rowing cycle). As a rough but very robust approximation, a descending (negative) slope indicates a powered flywheel, an (positive) ascending slope indicates an unpowered flywheel. This approach seems to be similar to the implementation used by industry leaders like Concept2. Concept2 are generally considered the golden standard when it comes to metrics, and they state (see [this Concept2 FAQ](https://www.concept2.com/service/software/ergdata/faqs): + +> Drive time is measured by the amount of time the flywheel is accelerating. Note: It is possible that the athlete may pull on the handle and not accelerate the flywheel due to no energy being put into it and therefore no effective effort. This portion of the handle pull is not measured. + +A more nuanced, but more vulnerable, approach is to compare the slope of this function with the typical slope encountered during the recovery phase of the stroke (which routinely is determined during the drag calculation). When the flywheel is unpowered, the slope will be close to the recovery slope, and otherwise it is powered. This is a more accurate, but more vulnerable, approach, as small deviations could lead to missed strokes. It is noted that practical testing has shown that this works reliably for many machines. + +In Open Rowing Monitor, the settings allow for using the more robust ascending/descending approach (by setting *minumumRecoverySlope* to 0), for a more accurate approach (by setting *minumumRecoverySlope* to a static value) or even a dynamic approach (by setting *autoAdjustRecoverySlope* to true) + +### Advanced force detection through torque τ + +The more advanced, but more vulnerable approach depends on the calculated torque. When looking at *CurrentDt* and Torque over time, we get the following picture: + +Image showing the average currentDt curves of a rowing machine +Average currentDt (red) and Acceleration (blue) of a single stroke on a rowing machine + +In this graph, we plot *currentDt* and Torque against the time in the stroke. As soon as the Torque of the flywheel becomes below the 0, the *currentDt* begins to lengthen again (i.e. the flywheel is decelerating). As indicated earlier, this is the trigger for the basic force detection algorithm (i.e. when *minumumRecoverySlope* is set to 0): when the *currentDt* starts to lengthen, the drive-phase is considered complete. + +However, from the acceleration/deceleration curve it is also clear that despite the deceleration, there is still a force present: the Torque-curve hasn't reached its stable minimum despite crossing 0. This is due to the pull still continuing through the arms: the net force is negative due to a part drive-phase (the arm-movement) delivering weaker forces than the drag-forces of the flywheel. Despite being weaker than the other forces on the flywheel, the rower is still working. In this specific example, at around 0.52 sec the rower's force was weaker than all drag-forces combined. However, only at 0,67 seconds (thus approx. 150 ms later) the net force reaches its stable bottom: the only force present is the drag from the flywheel. Getting closer to this moment is a goal. + +We do this by setting a minimum Torque (through setting *minumumForceBeforeStroke*) before a Drive phase can be initiated. + +#### A note about detection accuracy + +Open Rowing Monitor only will get impulses at discrete points in time. As Open Rowing Monitor doesn't measure torque on the flywheel directly, it can't determine where the flywheel exactly accelerates/decelerates as there is no continous measurement. Open Rowing Monitor can only detect a change in the times across several impulses, but it can't detect the exact time of torque change. In essence, at best we only can conclude that the torque has changes somewhere near a specific impulse, but we can't be certain about where the acceleration exactly has taken place and we can only estimate how big the force must have been. + +## Relevant linear metrics + +Knowing that *Time since start*, Angular Velocity ω, Angular Acceleration α, flywheel Torque τ and dragfactor k have been determined in a robust manner by `engine/Flywheel.js`, `engine/Rower.js` can now transform these key rotational metrics in linear metrics. This is done in the `handleRotationImpulse()` function of `engine/Rower.js`, where based on the flywheel state, the relevant metrics are calculated. The following metrics need to be determined: + +* The estimated **power produced** by the rower (in Watts, denoted with P): the power the rower produced during the stroke; + +* The estimated **Linear Velocity** of the boat (in Meters/Second, denoted with u): the speed at which the boat is expected to travel; + +* The estimated **Linear Distance** of the boat (in Meters, denoted with s): the distance the boat is expected to travel; + +* The estimated **Drive length** (in meters): the estimated distance travelled by the handle during the drive phase; + +* The estimated **speed of the handle** (in m/s): the speed the handle/chain/belt of the rower; + +* The estimated **force on the handle** (in Newtons): the force excerted on the handle/chain/belt of the rower; + +* The estimated **power on the handle** (in Watts): the power on the handle/chain/belt of the rower; + +### Power produced + +As the only source for adding energy to the rotational part of the rower is the linear part of the rower, the power calculation is the key calculation to translate between rotational and linear metrics. We can calculate the energy added to the flywheel through [[1]](#1), formula 8.2: -> +$$ ΔE = I \* ({Δω \over Δt}) \* Δθ + k \* ω^2 \* Δθ $$ + +The power then becomes [[1]](#1), formula 8.3: + +$$ P = {ΔE \over Δt} $$ + +Combining these formulae, makes -Or in more readable form for each measured displacement: +$$ P = I \* ({Δω \over Δt}) \* ω + k \* ω^3 $$ -> -> +Although this is an easy technical implementable algorithm by calculating a running sum of this function (see [[3]](#3), and more specifically [[4]](#4)). However, the presence of the many small ω's makes the outcome of this calculation quite volatile, even despite the robust underlying calculation for ω. Calculating this across the stroke might be an option, but the presence of Δω would make the power calculation highly dependent on both accurate stroke detection and the accurate determination of instantanous ω. -Where +An alternative approach is given in [[1]](#1), [[2]](#2) and [[3]](#3), which describe that power on a Concept 2 is determined through ([[1]](#1) formula 9.1), which proposes: -> +$$ \overline{P} = k \* \overline{\omega}^3 $$ -The power then becomes +Where $\overline{P}$ is the average power and $\overline{\omega}$ is the average angular velocity during the stroke. Here, the average speed can be determined in a robust manner (i.e. ${Δθ \over Δt}$ for sufficiently large Δt). -> +Dave Venrooy indicates that this formula is accurate with a 5% margin [[3]](#3). Testing this on live data confirms this behavior. Academic research on the accuracy of the Concept 2 RowErg PM5's power measurements [[15]](#15) shows that: -Although this is an easy implementable algorithm by calculating a running sum of this function (see [[3]](#3), and more specifically [[4]](#4)). However, the presence of the many Angular Velocities makes the outcome of this calculation quite volatile. The angulate velocity is measured through the formula: +* It seems that Concept 2 is also using this simplified formula, or something quite similar, in the PM5; -> +* For stable steady state rowing, the results of this approach are quite reliable; -As *currentDt* tends to be small (typically much smaller than 1, typically between 0,1 and 0,0015 seconds), small errors tend to increase the Angular Velocity significantly, enlarging the effect of an error and potentially causing this volatility. An approach is to use a running average on the presentation layer (in `RowingStatistics.js`). However, when this is bypassed, data shows significant spikes of 20Watts in quite stable cycles due to small changes in the data. +* For unstable rowing, the power calcuation is not reliable. The article seems to suggest that this is caused by ommitting the element of ${I \* ({Δω \over Δt}) \* ω}$, essentially assuming that Δω is near zero across strokes. This is problematic at moments of deliberate acceleration across strokes (like starts and sprints), where Δω can be very significant, and at unstable rowing, where there also can be a sigificant Δω present across strokes. -An alternative approach is given by [[3]](#3), which proposes +Still, we currently choose to use $\overline{P}$ = k \* $\overline{ω}$3 for all power calculations, for several reasons: -> +* Despite its flaws, Concept 2's PM5 is widely regarded as the golden standard in rowing. For us, we rather stay close to this golden standard than make a change without the guarantee of delivering more accurate and robust results than Concept 2's PM5; -Where P is the average power and ω is the average speed during the stroke. Here, the average speed can be determined in a robust manner (i.e. Angular Displacement of the Drive Phase / DriveLength). +* The simpler algorithm removes any dependence on instantaneous angular velocities ω at the flanks of the stroke from the power calculation and subsequent linear calculations. This makes the power calculation (and thus any subsequent calculations that are based on it) more robust against "unexpected" behavior of the rowing machine. There are several underlying causes for the need to remove this dependence: + * First of all, measurement errors in *CurrentDt* could introduce variations in Δω across the cycle and thus in all dependent linear metrics; + * Secondly, water rowers are known to experience cavitation effects at the end of the Drive Phase when used with sub-optimal technique, leading to extremely volatile results; + * Last, the determination of Δω across a stroke heavily depends on a very repeatable stroke detection that minimizes Δω to 0 during a stable series of stroke in steady state rowing. Such a repeatable stroke detection across the many types of rowing machines in itself is difficult to achieve; -As Dave Venrooy indicates this is accurate with a 5% margin. Testing this on live data confirms this behavior (tested with a *autoAdjustDragFactor* = true, to maximize noise-effects), with three added observations: +* As the *flywheelinertia* I is mostly guessed based on its effects on the Power outcome anyway (as most users aren't willing to take their rower apart for callibration purposses), a systematic error wouldn't matter much in most practical applications as it is corrected during the callibration of a monitor: the *flywheelinertia* will simply be modified to get to the correct power in the display. -* The robust algorithm is structurally below the more precise measurement when it comes to total power produced in a 30 minutes or 15 minutes row on a RX800 with any damper setting; +* It allows the user to removing/disabling all instantaneous angular velocities from linear metric calculations (i.e. only using average angular velocity calculated over the entire phase, which doesn't depend on a single measurement) by setting *autoAdjustDragFactor* to "false". This makes Open Rowing Monitor a viable option for rowers with noisy data or otherwise unstable/unreliable individual measurements; -* The robust algorithm is indeed much less volatile: the spikes found in the more precise algorithm are much bigger than the ones found in the robust algorithm +* Given the stability of the measurements, it might be a realistic option for users to remove the filter in the presentation layer completely by setting *numOfPhasesForAveragingScreenData* to 2, making the monitor much more responsive to user actions. -* A test with *numOfPhasesForAveragingScreenData* = 1 (practically bypassing the running average in the presentation layer) combined with the robust algorithm shows that the monitor is a bit more responsive but doesn't fluctuate unexpectedly. +Given these advantages and that in practice it won't have a practical implications for users, we have chosen to use the robust implementation. It should be noted that this definition is also robust against missed strokes: a missed drive or recovery phase will lump two strokes together, but as the Average Angular Velocity $\overline{ω}$ will average out across these strokes, it will not be affected in practice. -As the *flywheelinertia* is mostly guessed based on its effects on the Power outcome anyway (as nobody is willing to take his rower apart for this), the 5% error wouldn't matter much anyway: the *flywheelinertia* will simply become 5% more to get to the same power in the display. Therefore, we choose to use the simpler more robust algorithm, as it has some advantages: +### Linear Velocity -* In essence the instantaneous angular velocities at the flanks are removed from the power measurement, making it more robust against "unexpected" behavior of the rowers (like the cavitation-like effects found in LiquidFlywheel Rowers). Regardless of settings, only instantaneous angular velocities that affect displayed data are the start and begin of each phase; +In [[1]](#1) and [[2]](#2), it is described that power on a Concept 2 is determined through (formula 9.1): -* Setting *autoAdjustDragFactor* to "false" effectively removes/disables all calculations with instantaneous angular velocities (only average velocity is calculated over the entire phase, which typically is not on a single measurement), making Open Rowing Monitor an option for rowers with noisy data or otherwise unstable/unreliable measurements; +$$ \overline{P} = k \* \overline{\omega}^3 = c \* \overline{u}^3 $$ -* Given the stability of the measurements, it might be a realistic option for users to remove the filter in the presentation layer completely by setting *numOfPhasesForAveragingScreenData* to 1, making the monitor much more responsive to user actions. +Where c is a constant (2.8 according to [[1]](#1)), $\overline{\omega}$ the average angular velocity and $\overline{u}$ is the average linear velocity, making this formula the essential pivot between rotational and linear velocity and distance. -Given these advantages and that in practice it won't have a practical implications for users, we have chosen to use the robust implementation. +However, in [[1]](#1) and [[2]](#2), it is suggested that power on a Concept 2 might be determined through (formula 9.4, [[1]](#1)): -## Additional considerations for the frequency of the metrics calculations +$$ \overline{P} = 4.31 \* \overline{u}^{2.75} $$ -There are some additional options for the frequency of metric calculations: +Based on a simple experiment, downloading the exported data of several rowing sessions from Concept 2's logbook, and comparing the reported velocity and power, it can easily be determined that $\overline{P}$ = 2.8 \* $\overline{u}$3 offers a much better fit with the data than $\overline{P}$ = 4.31 \* $\overline{u}$2.75 provides. Therefore, we choose to use formula 9.1. Baed on this, we thus adopt formula 9.1 (from [[1]](#1)) for the calculation of linear velocity u: -* An option would be to update the metrics only updated at the end of stroke, which is once every 2 to 3 seconds. This is undesirable as a typical stroke takes around 2.5 seconds to complete and covers around 10 meters. It is very desirable to update typical end-criteria for trainings that change quite quickly (i.e. absolute distance, elapsed time) more frequently than that; +$$ \overline{u} = ({k \over C})^{1/3} * \overline{\omega} $$ -* We additionally update the metrics (when dependent on the stroke dependent parameters, like stroke length) both at the end of the Drive and Recovery Phases, as Marinus van Holst [[2]](#2) suggests that both are valid perspectives on the stroke. This allows for a recalculation of these metrics twice per stroke; +As both k and $\overline{\omega}$ can change from cycle to cycle, this calculation should be performed for each cycle. It should be noted that this formula is also robust against missed strokes: a missed drive or recovery phase will lump two strokes together, but as the Average Angular Velocity $\overline{\omega}$ will average out across these strokes. Although missing strokes is undesired behaviour in itself, this approach will isolate linear velocity calculations from errors in the stroke detection in practice. -* To allow for a very frequent update of the monitor, and allow for testing for typical end-criteria for trainings that change quite quickly (i.e. absolute distance, elapsed time), we calculate these for each new *currentDt*; +### Linear distance -* As we can only calculate the drag factor at the end of the recovery phase, we can only (retrospectively) apply it to the realized linear distance of that same recovery phase. Therefore, we we need to report absolute time and distance from the `RowingEngine` in `engine/RowingEngine.js`); +[[1]](#1)'s formula 9.3 provides a formula for linear distance: -## A closer look at the effects of the various Drive and Recovery phase detection +$$ s = ({k \over C})^{1/3} * θ $$ -In this section, we will answer the question whether Concept2 made a big error in their stroke detection, and thus that using *naturalDeceleration* is set to 0 is inferior to actually setting it to a different value. The short answer is that Concept2 has made a perfectly acceptable tradeoff between reliability of the stroke detection and precision of some metrics. +Here, as k can slightly change from cycle to cycle, this calculation should be performed at least once for each cycle. As θ isn't dependent on stroke state and changes constantly, it could be recalculated continously throughout the stroke, providing the user with direct feedback of his stroke. It should be noted that this formula is also robust against missed strokes: a missed drive or recovery phase will lump two strokes together, but as the angular displacement θ is stroke independent, it will not be affected by it at all. Although missing strokes is undesired behaviour, this approach isolates linear distance calculations from errors in the stroke detection in practice. -Effectively, Open Rowing Monitor can use two different methods of stroke detection. When *naturalDeceleration* is set to 0, it will detect an acceleration/deceleration directly based on *currentDt*, similar to Concept2. When *naturalDeceleration* is set to a negative number, it will consider that number as the minimum level of deceleration (in Rad/S^2). The later is more volatile, as described above, but some consider this desirable when possible. +### Drive length -Our practical experiments show that assuming the recovery-phase started too early doesn't affect measurements per se. In theory, the calculation of speed and power do not depend directly on phase detection, they do depend on the total number of impulses and the drag factor. It is in fact the automatic update of the drag factor that is dependent on the correct detection of the stroke. The drag factor can be pinned down if needed by setting *autoAdjustDragFactor* to "false". If set to true, it might affect measurements of both distance and power, where we will discuss the knock-on effects. +Given the distance travelled by the handle can be calculated from angular distance θ traveled by the sprocket during the Drive Phase. During the drive, the angular distance travelled by the flywheel is identical to the angular distance θ travelled by the flywheel during the drive phase. Thus -### Effects on the automatically calculated drag factor +$$ s_{Handle} = \text{number of rotations of the flywheel} \* \text{circumference of the sprocket} $$ -The most important measurement that is affected by stroke detection errors is the calculation of the drag factor. +As the number of rotations of the flywheel = ${\theta \over 2\pi}$ and the circumference of the sprocket = r * 2π, where r is the radius of the sprocket that is connected to the flywheel, we can translate this formula into: -Our robust implementation of the drag factor is: +$$ s_{Handle} = {\theta \over 2\pi} * r * 2\pi $$ -> +Which can be simplified into: -Looking at the effect of erroneously starting the recovery early and ending it late, it affects two variables: +$$ s_{Handle} = θ * r $$ -* Recovery length will *systematically* become too long (approx. 200 ms from our experiments) +Where r is the radius of the sprocket in meters and θ the angular distance travelled by the flywheel during the drive. -* The Angular Velocity will *systematically* become too high as the flywheel already starts to decelerate at the end of the drive phase, which we mistakenly consider the start of the recovery (in our tests this was approx. 83,2 Rad/sec instead of 82,7 Rad/sec). A similar thing can happen at the begin of the recovery phase when the rower doesn't have an explosive Drive. +### Handle Velocity -Example calculations based on several tests show that this results in a systematically too high estimate of the drag factor. As these errors are systematic, it is safe to assume these will be fixed by the calibration of the power and distance corrections (i.e. the estimate of the *FlywheelInertia* and the *MagicConstant*). Thus, as long as the user calibrates the rower to provide credible data for his setting of *naturalDeceleration*, there will be no issues. +As the distance travelled by the handle is ${u_{Handle} = θ * r}$, we can decuct: -Please note that this does imply that changing the *naturalDeceleration* when the *autoAdjustDragFactor* is set to true (thus drag is automatically calculated) will require a recalibration of the power and distance measurements. +$$ u_{Handle} = ω \* r $$ -### Knock-on Effects on the Power calculation +Here, ω can be the instantanous or average angular velocity of the flyhweel in Radians, and r is the radius of the sprocket (in meters). -Question is what the effect of this deviation of the drag factor is on the power calculation. The power is calculated as follows: +### Handle Force -> +From theory [[12]](#12)) and practical application [[7]](#7), we know the handle force is equal to: -Here, the drag factor is affected upwards. Here the average speed is determined by measuring the angular displacement and divided by the time, being affected in the following manner: +$$ F_{Handle} = {τ \over r} $$ -* Time spend in the Drive phase is *systematically* too short +Where r is the radius of the sprocket in meters. -* Angular displacement in the Drive phase will *systematically* be too short +### Handle Power -These effects do not cancel out: in essence the flywheel decelerates at the end of the drive phase, which we mistakenly include in the recovery phase. This means that on average, the average speed is systematically too high: it misses some slower speed at the end of the drive. As all factors of the power calculation are systematically overestimating, the result will be a systematic overestimation. +From theory [[13]](#13)), we know that the handle Power is -Again, this is a systematic (overestimation) of the power, which will be systematically corrected by the Inertia setting. +$$ P_{Handle} = τ * ω $$ + +## A mathematical perspective on key metrics + +### Noise Filtering algorithms applied + +For noise filtering, we use a moving median filter, which has the benefit of removing outliers completely. This is more robust than the moving average, where the effect of outliers is reduced, but not removed. + +### Linear regression algorithms applied for slope determination + +There are several different linear regression methods [[9]](#9). We have several requirements on the algorithm: + +* it has to delviver results in near-real-time scenarios in a datastream; + +* if possible, it has to be robust to outliers: an outlier shouldn't skew the results too much [[10]](#10). + +Ordinary Least Squares is by far the most efficient and can easily be applied to datastreams. However, it isn't robust. From a robustness perspective, most promissing methods are [least absolute deviations](https://en.wikipedia.org/wiki/Least_absolute_deviations), the [Theil–Sen estimator](https://en.wikipedia.org/wiki/Theil%E2%80%93Sen_estimator) and the [LASSO technique](https://en.wikipedia.org/wiki/Lasso_(statistics)). Most of these methods, except the Theil–Sen estimator, do not have a near-real-time solution. In the description of the linear regression methods, we describe the most promissing ones. + +#### Ordinary Least Squares (OLS) + +Ordinary Least Squares regression (see [[5]](#5)) and [[6]](#6)) produces results that are generally acceptable and the O(N) performance is well-suited for near-real-time calculations. When implemented in a datastream, the addition of a new datapoint is O(1), and the calculation of a slope also is O(1). When using a high-pass filter on the r2 to disregard any unreliably approximated data, it can also be used to produce reliable results. See `engine/utils/OLSLinearSeries.js` for more information about the implementation. + +#### Theil–Sen estimator (Linear TS) + +Although the Theil–Sen estimator has a O(N log(N)) solution available, however we could not find a readily available solution. We did manage to develop a solution that has a O(N) impact during the addition of an additional datapoint in a datastream with a fixed length window, and O(log(N)) impact when determining the slope. + +#### Incomplete Theil–Sen estimator (Inc Linear TS) + +There also is an Incomplete Theis-Sen estimator for Linear Regression [[11]](#11), which is O(1) for the addition of new datapoints in a datastream with a fixed length window, and O(log(N)) for the determination of the slope. Our tests on real-life data show that in several cases the Incomplete Theil-Sen delivers more robust results than the full Theil-Sen estimator. + +#### Quadratic Theil–Sen estimator (Quadratic TS) + +The Theil–Sen estimator can be expanded to apply to Quadratic functions, where the implementation is O(N2). Based on a Lagrange interpolation, we can calculate the coefficients of the formula quite effectively, resulting in a robust estimation more fitting the data. See `engine/utils/FullTSQuadraticSeries.js` for more information about the background of the implementation. + +### Choices for the specific algorithms + +#### Regression algorithm used for drag calculation + +For the drag-factor calculation (and the closely related recovery slope detection), we observe three things: + +* The number of datapoints in the recovery phase isn't known in advance, and is subject to significant change due to variations in recovery time (i.e. sprints), making the Incomplete Theil–Sen estimator incapable of calculating their slopes in the stream as the efficient implementations require a fixed window. OLS has a O(1) complexity for continous datastreams, and has proven to be sufficiently robust for most practical use. Using the Linear Theil-sen estimator results in a near O(N) calculation at the start of the *Drive* phase (where N is the length of the recovery in datapoints). The Quadratic Theil-sen estimator results in a O(N2) calculation at the start of the *Drive* phase. Given the number of datapoints often encountered (a recoveryphase on a Concept 2 contains around 200 datapoints), this is a significant CPU-load that could disrupt the application; + +* In non-time critical replays of earlier recorded rowing sessions, both the Incomplete Theil–Sen estimator performed worse than OLS: OLS with a high pass filter on r2 resulted in a much more stable dragfactor than the Incomplete Theil–Sen estimator did. The Theil–Sen estimator, in combination with a filter on r2 has shown to be even a bit more robust than OLS. This suggests that the OLS algorithm combined with a requirement for a sufficiently high r2 handles the outliers sufficiently to prevent drag poisoning and thus provide a stable dragfactor for all calculations. The Linear Theil-Sen estimator outperfomed OLS by a small margin, but noticeably improved stroke detection where OLS could not regardless of parameterisation. + +* Applying Quadratic OLS regression does not improve its results when compared to Linear OLS regression or Linear TS. For the drag (and thus recovery slope) calculation, the Linear Theil-Sen estimator has a slightly better performance then OLS, while keeping CPU-load acceptable for a data-intensive rowing machine (Concept 2, 12 datapoints flank, 200 datapoints in the recovery). A Quadratic theil-Sen based drag calculation has shown to be too CPU-intensive. For the stroke detection itself, OLS and Linear Theil-Sen deliver the same results, while OLS is less CPU intensive. + +Therefore, we choose to apply the Linear Theil-Sen estimator for the calculation of the dragfactor and the related recovery slope detection, and use OLS for the stroke detection. + +#### Regression algorithm used for Angular velocity and Angular Acceleration + +We determine the Angular Velocity ω and Angular Acceleration α based on the relation between θ and time. First of all, we observe that we use both the first derived function (i.e. ω) and the second derived function (i.e. α), making a quadratic or even a cubic regression algorithm more appropriate, as a liniear regressor would make the second derived function trivial. Practical testing has confirmed that Quadratic Theil-Senn outperformed all Linear Regression methods in terms of robustness and responsiveness. Based on extensive testing with multiple simulated rowing machines, Full Quadratic Theil-Senn has proven to deliver the best results and thus is selected to determine ω and α. + +## Open Issues, Known problems and Regrettable design decissions + +### Use of simplified power calculation + +The power calculation is the bridge connecting the linear and rotational energy systems of an ergometer. However, from a robustness perspective, we optimised this formula. The complete formula for power throughout a stroke can be deduced from formulae 8.2 and 8.3 [[1]](#1), which lead to: + +$$ P = I \* ({Δω \over Δt}) \* ω + k \* ω^3 $$ + +A simplified formula is provided by [[1]](#1) (formula 9.1), [[2]](#2) and [[3]](#3): + +$$ \overline{P} = k \* \overline{\omega}^3 $$ + +Open Rowing Monitor uses the latter simplified version. As shown by academic research [[15]](#15), this is sufficiently reliable and accurate providing that that ω doesn't vary much across subsequent strokes. When there is a significant acceleration or decelleration of the flywheel across subsequent strokes (at the start, during acceleration in sprints or due to stroke-by-stroke variation), the reported/calculated power starts to deviate from the externally applied power. + +Currently, this is an accepted issue, as the simplified formula has the huge benefit of being much more robust against errors in both the *CurrentDt*/ω measurement and the stroke detection algorithm. As Concept 2 seems to have taken shortcut in a thoroughly matured product [[15]](#15), we are not inclined to change this quickly. Especially as the robustness of both the ω calculation and stroke phase detection varies across types of rowing machines, it is an improvement that should be handled with extreme caution. + +### Use of Quadratic Theil-Senn regression for determining α and ω based on time and θ + +Abandoning the numerical approach for a regression based approach has resulted with a huge improvement in metric robustness. So far, we were able to implement Quadratic Theil-Senn regression and get reliable and robust results. Currently, the use of Quadratic Theil-Senn regression represents a huge improvement from both the traditional numerical approach (as taken by [[1]](#1) and [[4]](#4)) used by earlier approaches of Open Rowing Monitor. + +The (implied) underlying assumption underpinning the use of Quadratic Theil-Senn regression approach is that the Angular Accelration α is constant, or near constant by approximation in the flank under measurment. In essence, quadratic Theil-Senn regression would be fitting if the acceleration would be a constant, and the relation of θ, α and ω thus would be captured in θ = 1/2 \* α \* t2 + ω \* t. We do realize that in rowing the Angular Accelration α, by nature of the rowing stroke, will vary based on the position in the Drive phase: the ideal force curve is a heystack, thus the force on the flywheel varies in time. + +As the number of datapoints in a *Flanklength* in the relation to the total number of datapoints in a stroke is relatively small, we use quadratic Theil-Senn regression as an approximation on a smaller interval. In tests, quadratic regression has proven to outperform (i.e. less suspect to noise in the signal) both the numerical approach with noise filtering and the linear regression methods. When using the right efficient algorithm, this has the strong benefit of being robust to noise, at the cost of a O(n2) calculation per new datapoint (where n is the flanklength). Looking at the resulting fit of the Quadratic Theil-Sen estimator, we see that it consistently is above 0.98, which is an extremely good fit given the noise in the Concept 2 RowErg data. Therefore, we consider this is a sufficiently decent approximation while maintaining an sufficiently efficient algorithm to be able to process all data in the datastream in time. + +Although the determination of angular velocity ω and angular acceleration α based on Quadratic Theil-Senn regression over the time versus angular distance θ works decently, we realize it does not respect the true dynamic nature of angular acceleration α. From a pure mathematical perspective, a higher order polynomial would be more appropriate. A cubic regressor, or even better a fourth order polynomal have shown to be better mathematical approximation of the time versus distance function for a Concept2 RowErg. We can inmagine there are better suited third polynomal (cubic) approaches available that can robustly calculate α and ω as a function of time, based on the relation between time and θ. However, getting these to work in a datastream with very tight limitations on CPU-time and memory across many configurations is quite challenging. + +However, there are some current practical objections against using these more complex methods: + +* Higher order polynomials are less stable in nature, and overfitting is a real issue. As the displacement of magets can present itself as a sinoid-like curve (as the Concept 2 RowErg shows), 3rd or higher polynomials are inclined to follow that curve. As this might introduce wild shocks in our metrics, this might be a potential issue for application; +* A key limitation is the available number of datapoints. For the determination of a polynomial of the n-th order, you need at least n+1 datapoints (which in Open Rowing Monitor translates to a `flankLength`). Some rowers, for example the Sportstech WRX700, only deliver 5 to 6 datapoints for the entire drive phase, thus putting explicit limits on the number of datapoints available for such an approximation. +* Calculating a higher order polynomial in a robust way, for example by Theil-Senn regression, is CPU intensive. A quadratic approach requires a O(n2) calculation when a new datapoint is added to the sliding window (i.e. the flank). Our estimate is that with current known robust polynomial regression methods, a cubic approach requires at least a O(n3) calculation, and a 4th polynomial a O(n4) calculation. With smaller flanks (which determines the n) this has proven to be doable, but for machines which produce a lot of datapoints, and thus have more noise and a typically bigger `flankLength` (like the C2 RowErg and Nordictrack RX-800, both with a 12 `flankLength`), this becomes an issue: we consider completing 103 or even 104 complex calculations within the 5 miliseconds that is available before the next datapoint arrives, impossible. + +We also observe specific practical issues, which could result in structurally overfitting the dataset, nihilating its noise reduction effect. As the following sample of three rotations of a Concept2 flywheel shows, due to production tolerances or deliberate design constructs, there are **systematic** errors in the data due to magnet placement or magnet polarity. This results in systematic issues in the datastream: + +Image showing the sinoid measurement deviations of a Concept 2 RowErg over three full flywheel rotations +Deviation of the Concept 2 RowErg + +Fitting a quadratic curve with at least two full rotations of data (in this case, 12 datapoints) seems to reduce the noise to very acceptable levels. In our view, fitting a third-degree polynomial would result in a better fit with these systematic errors, but resulting in a much less robust signal. + +We also observe that in several areas the theoretical best approach did not deliver the best practical result (i.e. a "better" algorithm delivered a more noisy result for α and ω). Therefore, this avenue isn't investigated yet, but will remain a continuing area of improvement. + +This doesn't definitively exclude the use of more complex polynomial regression methods: alternative methods for higher polynomials within a datastream could be as CPU intensive as Theil-Senn Quadratic regression now, and their use could be isolated to specific combination of Raspberry hardware and settings. Thus, this will remain an active area of investigation for future versions. + +### Use of Quadratic Theil-Senn regression and a weighed average filter for determining α and ω + +For a specific flank, our quadratic regression algorithm calculates a single α for the entire flank and the individual ω's for each point on that flank. The flank acts like a sliding window: on each new datapoint the window slides one datapoint, and thus recalculates the critical parameters. Thus, as a datapoint will be part of several flank calculations, we obtain several α's and ω's that are valid approximations for that specific datapoint. Once the datapoint slides out of the sliding window, there are *flankLength* number of approximations for ω and α. A key question is how to combine these multiple approximations α and ω into a single true value for these parameters. + +To obtain the most stable result, a median of all valid values for α and ω can be used to calculate the definitive approximation of α and ω for that specific datapoint. Although this approach has proven very robust, and even necessary to prevent noise from disturbing powercurves, it is very conservative. For example, when compared to Concept 2's results, the powercurves have the same shape, but the peak values are considerable lower. It also has the downside of producing "blocky" force cuves. + +Using a weighed averager resulted in slightly more stable results and resulted in smoother force curves. The weight is based on the r2: better fitting curves will result in a heiger weigt in the calculation, thus preferring approximations that are a better fit with the data. This approach resulted in smoother (less blocky) force curves while retaining the responsiveness of the force curve. + +Reducing extreme values while maintaining the true data responsiveness is a subject for further improvement. ## References @@ -279,6 +502,26 @@ Again, this is a systematic (overestimation) of the power, which will be systema [3] Dave Vernooy, "Open Source Ergometer ErgWare" -[4] +[4] Dave Vernooy, ErgWare source code + +[5] Wikipedia, "Simple Linear Regression" + +[6] University of Colorado, "Simple Linear Regression" + +[7] Nomath, "Fan blade Physics and a Peek inside C2's Black Box" + +[8] Glenn Elert, The Physics Hypertextbook, "Rotational Kinematics" + +[9] Wikipedia, "Linear regression" + +[10] Wikipedia, "Robust regression" + +[11] Incomplete Theil-Sen Regression + +[12] Glenn Elert, The Physics Hypertextbook, "Rotational Dynamics" + +[13] Glenn Elert, The Physics Hypertextbook, "Rotational Energy" + +[14] Dave Vernooy, ErgWare source code V0.6 -[5] Fan blade Physics and a Peek inside C2's Black Box, Nomath +[15] Gunnar Treff, Lennart Mentz, Benjamin Mayer and Kay Winkert, "Initial Evaluation of the Concept-2 Rowing Ergometer's Accuracy Using a Motorized Test Rig" diff --git a/docs/rower_settings.md b/docs/rower_settings.md index 53783bde7e..0fcde99a36 100644 --- a/docs/rower_settings.md +++ b/docs/rower_settings.md @@ -1,76 +1,405 @@ # Guide for rower specific settings -This guide helps you to adjust the rowing monitor specifically for a new type of rower or even for your specific use, when the default rowers don't suffice. + +This guide helps you to adjust the rowing monitor specifically for a new type of rower or even for your specific use, when the supported rowers don't suffice (you can [find a list of supported rowers here](Supported_Rowers.md)). In this manual, we will guide you through the settings needed to get your machine working. This is a work in progress, and please get in touch through the [GitHub Discussions](https://github.com/JaapvanEkris/openrowingmonitor/discussions) when you run into problems. + +In this manual, we cover the following topics: + +* Why have specific settings in the first place + +* Check that Open Rowing Monitor works + +* Making sure the hardware is connected correctly and works as intended + +* Setting up a more detailed logging for a better insight into Open Rowing Monitor + +* Setting GPIO parameters to get a clean signal (general settings) + +* Critical parameters you must change or review for noise reduction + +* Critical parameters you must change or review for stroke detection + +* What reliable stroke detection should look like in the logs + +* Settings required to get the basic metrics right + +* Settings you COULD change for a new rower + +* Settings you can tweak + +* Sending in a rower profile to us ## Why we need rower specific settings -No rowing machine is the same, and some physical construction parameters are important for the Rowing Monitor to be known to be able to understand your rowing stroke. By far, the easiest way is to select your rower profile from `config/rowerProfiles.js` and put its name in `config.js` (i.e. `rowerSettings: rowerProfiles.WRX700`). The rowers mentioned there are maintained by us for OpenRowingMonitor and are structurally tested with changes typically automatically implemented. +No rowing machine is the same, and some physical construction parameters are important for OpenRowingMonitor to be known to be able to understand your rowing stroke. By far, the easiest way to configure your rower is to select your rower profile from `config/rowerProfiles.js` and put its name in `config/config.js` (i.e. `rowerSettings: rowerProfiles.Concept2_RowErg`). The rowers mentioned there are maintained by us for OpenRowingMonitor and we also structurally test OpenRowingMonitor with samples of these machines and updates setings when needed. For you as a user, this has the benefit that updates in our software are automatically implemented, including updating the settings. So if you make a rower profile for your machine, please send the profile and some raw data (explained below) to us as well so we can maintain it for you. + +If you want something special, or if your rower isn't in there, this guide will help you set it up. Please note that determining these settings is quite labor-intensive, and typically some hard rowing is involved. As said, if you find suitable settings for a new type of rower, please send in the data and settings, so we can add it to OpenRowingMonitor and make other users happy as well. + +## Check that Open Rowing Monitor works + +First check you need to do is to check the status of the Open Rowing Monitor service, which you can do with the command: + + ```zsh + sudo systemctl status openrowingmonitor + ``` + +Which typically results in the following response (with some additional logging): + + ```zsh + ● openrowingmonitor.service - Open Rowing Monitor + Loaded: loaded (/lib/systemd/system/openrowingmonitor.service; enabled; vendor preset: enabled) + Active: active (running) since Sun 2022-09-04 10:27:31 CEST; 12h ago + Main PID: 755 (npm start) + Tasks: 48 (limit: 8986) + CPU: 6min 48.869s + CGroup: /system.slice/openrowingmonitor.service + ├─755 npm start + ├─808 sh /tmp/start-6f31a085.sh + ├─809 node app/server.js + ├─866 /usr/bin/node ./app/gpio/GpioTimerService.js + └─872 /usr/bin/node ./app/ble/CentralService.js + ``` + +Please note that the process identification numbers will differ. + +## Making sure the hardware is connected correctly and works as intended + +Because any system follows the mantra "Garbage in is garbage out", we first make sure that the signals Open Rowing Monitor recieves are decent. First we check the physical properties, then the electrical properties and last we check the quality of the incoming signal. + +**Please check and fix any mechanical/electrical/quality issues before proceeding, as the subsequent steps depend on a signal with decent quality!!** + +### Checking the physical properties of the rower + +One thing to check is what the original sensor actually measures. You can physically look in the rower, but most manuals also include an exploded view of all parts in the machine. There you need to look at the placement of the sensor and the magnets. Most air-rowers measure the flywheel speed, but most water-rowers measure the handle speed and direction. Open Rowing Monitor is best suited for handling a spinning flywheel or water impellor, or anything directly attached to that. If your machine measures the impellor or flywheel directly, please note the number of magnets per rotation, as you need that parameter later on. So when you encounter a handle-connected machine and it is possible and within your comfort zone, try to add sensors to the flywheel or impellor as it results in much better metrics. + +If you are uncomfortable modifying you machine, you can still make OpenRowingMonitor work, but with a loss of data quality. Where a flywheel or impellor can give information about the force and speeds created, the alternative can not. So you end up with a fixed distance per stroke, but you can connect to tools like EXR and the like. By setting *autoAdjustDragFactor* to false, *autoAdjustRecoverySlope* to false, *minumumRecoverySlope* to 0, *minimumStrokeQuality* to 0.01 and other parameters like dragFactor to a realistic well-choosen value (to make the metrics look plausible), OpenRowingMonitor will essentially calculate distance based on impulses encountered. Although not ideal for metrics, this can result in a working solution. Please note that the distance per stroke is essentially fixed, so many more advanced metrics are not relevant and stroke detection might be a bit vulnerable. + +### Checking the electrical properties of the rower + +Before you physically connect anything to anything else, **check the electric properties of the rower** you are connecting to. Skipping this might destroy your Raspberry Pi as some rowers are known to exceed the Raspberry Pi electrical properties. For example, a Concept 2 RowErg provides 15V signals to the monitor, which will destroy the GPIO-ports. Other rowers provide signals aren't directly detectable by the raspberry Pi. For example, the Concept 2 Model C provides 0.2V pulses, thus staying below the detectable 1.8V treshold that the Raspberry Pi uses. Using a scope or a voltmeter is highly recommended. Please observe that the maximum input a Raspberry Pi GPIO pin can handle is 3.3V and 0.5A, and it will switch at 1.8V (see [this overview of the Raspberry Pi electrical properties](https://raspberrypi.stackexchange.com/questions/3209/what-are-the-min-max-voltage-current-values-the-gpio-pins-can-handle)). In our [GitHub Discussions](https://github.com/laberning/openrowingmonitor/discussions) there are some people who are brilliant with electrical connections, so don't be affraid to ask for help there. When you have a working solution, please report it so that we can include it in the documentation, allowing us to help others. + +### Checking signal and measurement quality + +Next, when the electric connection has been made, we need to look if the data is recieved well and has sufficient quality to be used. You can change `config/config.js` by + + ```zsh + sudo nano /opt/openrowingmonitor/config/config.js + ``` + +Here, you can change the setting for **createRawDataFiles** by setting/adding the following BEFORE the rowerSettings element (so outside the rowerSettings scope): + + ```js + createRawDataFiles: true, + ``` + +You can use the following commands on the command line to restart after a config change (to activate new settings): + + ```zsh + sudo systemctl restart openrowingmonitor + ``` + +After rowing a bit, there should be a csv file created with raw data. If no strokes or pauses are detected, you can force the writing of these files by pushing the reset button on the GUI. Please read this data in Excel (it is in US format, so you might need to adapt it to your local settings), to check if it is sufficiently clean. After loading it into Excel, you can visualise it, and probably see something similar to the following: + +A curve showing the typical progress of currentDt + +When the line goes up, the time between impulses from the flywheel goes up, and thus the flywheel is decellerating. When the line goes down, the time between impulses decreases, and thus the flywheel is accelerating. In the first decellerating flank, we see some noise, which Open Rowing Monitor an deal with perfectly. However, looking at the bottom of the first acceleration flank, we see a series of heavy downward spikes. This could be start-up noise, but it also could be systematic across the rowing session. This is problematic as it throws off both stroke detection and many metrics. Typically, it signals an issue in the mechanical construction of the sensor: the frame and sensor vibrate at high speeds, resulting in much noise. Fixing this type of errors is key. We adress two familiar measurement quality issues: + +* Switch bounce: where a single magnet triggers multiple signals +* Magnet placement errors: where the timing of magnets is off + +#### Fixing switch bounce + +A specific issue to be aware of is *switch bounce*, which typically is seen as a valid signal followed by a very short spike. When looking at a set of plotted signals in Excel, it manafests itself as the following: + +A scatter plot showing the typical progress of currentDt with switch bounce + +As this example scatterplot curve shows, you can vaguely recognize the typical rowing curve in the measurements between 0.02 and 0.08 seconds. However, you also see a lot of very small spikes where the measurements are below 0.01 seconds. Actually there are so many spikes that it masks the real signal completely for Open Rowing Monitor. It contains sections where the time between pulses is 0.0001 seconds, which would mean that the flywheel would be spinning at 120.000 RPM, which physically is impossible for a simple bicycle wheel used in this example. This type of scater plot and the underlying data clearly suggests that the sensor picks up the magnet twice or more. This is a measurement quality issue that must be adressed. + +The preferred solution is to fix the physical underlying cause, this is a better alignment of the magnet or replacing the sensor for a more advanced model that only picks up specific signals. Using smaller but more powerful magnets also tends to help. However, this might not be practical: some flywheels are extremely well-balanced, and moving or replacing magnets might destroy that balance. To fix that type of error, there are two options: + +* Changing the **gpioMinimumPulseLength** setting allows you to require a minimal signal length, most likely removing these ghost readings. This is a bit of a try and error process: you'll need to row and increase the value **gpioMinimumPulseLength** further with steps of 50 us when you still see ghost readings, and repeat this process until the noise is acceptable. +* Another aveue to persue is to change the detected flank from the default 'Up' to 'Down' in the **gpioTriggeredFlank**, as sometimes the downward flank might be less affected by this issue. + +#### Fixing magnet placement errors + +Another specific issue to watch out for are systemic errors in the magnet placement. For exmple, these 18 pulses from a Concept2 RowErg show a nice clean signal, but also a systematic error, that follows a 6 impulse cycle. As the RowErg has 6 magnets, it is very likely that it is caused by magnets not being perfectly aligned (for example due to production tollerances): + +A curve showing the typical Concept 2 RowErg Sinoid deviation of currentDt for three cycles + +In some cases, changing the magnet placing or orientation can fix this completely (see for example [this discussion](https://github.com/laberning/openrowingmonitor/discussions/87)), which yields very good results and near-perfect data. Sometimes, you can't fix this or you are unwilling to physically modify the machine. Open Rowing Monitor can handle this kind of systematic error, as long as the *flankLength* (described later) is set to at least two full rotations (in this case, 12 impulses *flankLength* for a 6 magnet machine). + +**Please fix any mechanical/electrical/quality issues before proceeding, as the subsequent steps depend on a signal with decent quality!!** + +## Setting up a more detailed logging for a better insight into Open Rowing Monitor + +When installed, OpenRowingMonitor will not flood the log with messages. However, when testing it is great to see what OpenRowingMonitor is doing. So first thing to do is to set the following in the settings: + + ```js + // Available log levels: trace, debug, info, warn, error, silent + loglevel: { + // The default log level + default: 'info', + // The log level of of the rowing engine (stroke detection and physics model) + RowingEngine: 'debug' + }, + ``` + +You can look at the the log output of the OpenRowingMonitor-service by putting the following in the command-line: -If you want something special, or if your rower isn't in there, this guide will help you set it up. Please note that determining these settings is quite labor-intensive, and typically some hard rowing is involved. If you find suitable settings for a new type of rower, please send in the data and settings, so we can add it to OpenRowingMonitor and make other users happy as well. + ```zsh + sudo journalctl -u openrowingmonitor + ``` -## Settings you must change for a new rower +This allows you to see the current state of the rower. Typically this will show: -The key feature for Open Rowing Monitor is to reliably produce metrics you see on the monitor, share via Bluetooth with games and share with Strava and the like. Typically, these metrics are reported on a per-stroke basis. So, to be able to use these metrics for these goals, you need two key parts of the settings right: + ```zsh + Sep 12 20:37:45 roeimachine systemd[1]: Started Open Rowing Monitor. + Sep 12 20:38:03 roeimachine npm[751]: > openrowingmonitor@0.8.2 start + Sep 12 20:38:03 roeimachine npm[751]: > node app/server.js + Sep 12 20:38:06 roeimachine npm[802]: ==== Open Rowing Monitor 0.8.2 ==== + Sep 12 20:38:06 roeimachine npm[802]: Setting priority for the main server thread to -5 + Sep 12 20:38:06 roeimachine npm[802]: Session settings: distance limit none meters, time limit none seconds + Sep 12 20:38:06 roeimachine npm[802]: bluetooth profile: Concept2 PM5 + Sep 12 20:38:06 roeimachine npm[802]: webserver running on port 80 + Sep 12 20:38:06 roeimachine npm[862]: Setting priority for the Gpio-service to -7 + Sep 12 20:38:09 roeimachine npm[802]: websocket client connected + ``` -* Stroke detection -* Physical metrics (like distance, power and speed) +This shows that OpenRowingMonitor is running, and that bluetooth and the webserver are alive, and that the webclient has connected. We will use this to get some grip on Open Rowing Monitor's settings throughout the process. -### Getting stroke detection right +## Critical parameters you must change or review for noise reduction -A key element in getting rowing data right is getting the stroke detection right, as we report many metrics on a per-stroke basis. The **Impulse Noise reduction settings** reduce the level of noise on the level of individual impulses. You should change these settings if you experience issues with stroke detection or the stability of the drag factor calculation. The stroke detection consists out of three types of filters: +Open Rowing Monitor needs to understand normal rower behaviour, so it needs some information about the typical signals it should expect. -* A smoothing filter, using a running average. The **smoothing** setting determines the length of the running average for the impulses, which removes the height of the peaks, removes noise to a certain level but keeps the stroke detection responsive. Smoothing typically varies between 1 to 4, where 1 effectively turns it off. -* A high-pass/low-pass filter, based on **minimumTimeBetweenImpulses** (the shortest allowable time between impulses) and **maximumTimeBetweenImpulses** (the longest allowed time between impulses). Combined, they remove any obvious errors in the duration between impulses (in seconds) during *active* rowing. Measurements outside of this range are filtered out to prevent the stroke detection algorithm to get distracted. This setting is highly dependent on the physical construction of your rower, so you have to determine it yourself without any hints. The easiest way to determine this is by visualizing your raw recordings in Excel. -* A maximum change filter, which based on the the previous impulse determines the maximum amount of change (percentage) through the **maximumDownwardChange** and **maximumUpwardChange** settings. +### Setting gpioPriority, gpioPollingInterval, gpioTriggeredFlank, gpioMinimumPulseLength (general settings) -By changing the noise reduction settings, you can remove any obvious errors. You don't need to filter everything: it is just to remove obvious errors that might frustrate the stroke detection, but in the end you can't prevent every piece of noise out there. Begin with the noise filtering, when you are satisfied, you can adjust the rest of the stroke detection settings. +When you look at the raw dump of *CurrentDT*, it should provide a nice curve. When this curve is erratic, it could help to increase the priority of the GPIO polling process by changing its nice-level via the *gpioPriority*. On a Rapberyy Pi 4, a nice-level of -1 seems to be the maximum for the normal kernel, on a PREEMPT kernel, everything to -7 seems to work. Going beyond these values typically leads to eratic behaviour of the timing (some kernel timing processes seem to be disrupted). -Another set of settings are the **flankLength** and **numberOfErrorsAllowed** setting, which determine the condition when the stroke detection is sufficiently confident that the stroke has started/ended. In essence, the stroke detection looks for a consecutive increasing/decreasing impulse lengths, and the **flankLength** determines how many consecutive flanks have to be seen before the stroke detection considers a stroke to begin or end. Please note that making the flank longer does *not* change your measurement in any way: the algorithms always rely on the beginning of the flank, not at the current end. Generally, a **flankLength** of 2 to 3 typically works. Sometimes, a measurement is too noisy, which requires some errors in the flanks to be ignored, which can be done through the **numberOfErrorsAllowed** setting. For example, the NordicTrack RX-800 successfully uses a **flankLength** of 9 and a **numberOfErrorsAllowed** of 2, which allows quite some noise but forces quite a long flank. This setting requires a lot of tweaking and rowing. +Another option is to change the *gpioPollingInterval*, which determines how accurate the measurements are. Please note that increasing this will increase the CPU load, so setting it to 1us might come at a price. Setting this from the default value of 5us to 1us might increase precission, but it could disrupt the entire process as the CPU might get overloeded. So experimenting with this value is key. -At the level of the stroke detection, there is some additional noise filtering, preventing noise to start a drive- or recovery-phase too early. The settings **minimumDriveTime** and **minimumRecoveryTime** determine the minimum times (in seconds) for the drive and recovery phases. Generally, the drive phase lasts at least 0.500 second, and the recovery phase 1.250 second for recreational rowers. +**gpioTriggeredFlank** and **gpioMinimumPulseLength** are typically used to prevent bounces in the signal: magnets passing could trigger a reed switch twice (as described above). The logs provide help here, as the logs indicate abnormal short and long times between impulses (via the minimumTimeBetweenImpulses and maximumTimeBetweenImpulses settings). Please note that during a first stroke, the **CurrentDt** values obviously are longer. -For the noise reduction settings and stroke detection settings, you can use the Excel tool. When OpenRowingMonitor records a log (set setting createRawDataFiles to `true`), you can paste the values in the first column of the "Raw Data" tab (please observe that the Raspberry uses a point as separator, and your version of Excel might expect a comma). From there, the Excel file simulates the calculations the OpenRowingMonitor makes, allowing you to play with these settings. +### Setting minimumTimeBetweenImpulses and maximumTimeBetweenImpulses -Please note that changing the noise filtering and stroke detection settings will affect your calculated dragFactor. So it is best to start with rowing a few strokes to determine settings for noise filtering and stroke detection, and then move on to the other settings. +**minimumTimeBetweenImpulses** and **maximumTimeBetweenImpulses** provide a bandwith where values are deemed credible during an active session. The goal here is to help you detect and log any extremely obvious errors. So take a look at the raw datafiles for several damper settings (if available on your rower) and make sure that normal rowing isn't hindered by these settings (i.e. all normal values should fall within *minimumTimeBetweenImpulses* and *maximumTimeBetweenImpulses*). Here, you should rather allow too much noise, than hurt real valid signal, as Open Rowing Monitor can handle a lot of noise by itself. -### Getting the metrics right +A good quality curve of the time between impulses (as captured in the raw datafiles) looks like this: -There are some parameters you must change to get Open Rowing Monitor to calculate the real physics with a rower. These are: +A curve showing the typical progress of currentDt throughout several strokes -* **numOfImpulsesPerRevolution**: this tells Open Rowing Monitor how many impulses per rotation of the flywheel to expect. An inspection of the flywheel could reveal how many magnets it uses (typically a rower has 2 to 4 magnets). Although sometimes it is well-hidden, you can sometimes find it in the manual under the parts-list of your rower. -* **dragFactor**: tells Open Rowing Monitor how much damping and thus resistance your flywheel is offering. This is typically also dependent on your damper-setting (if present). Regardless if you use a static or dynamically calculated drag factor, this setting is needed as the first stroke also needs it to calculate distance, speed and power. Just as a frame of reference: the Concept2 can display this factor from the menu. Please note that the drag factor is much dependent on the physical construction of the flywheel and mechanical properties of the transmission of power to the flywheel. For a new Concept2, the Drag Factor ranges between 80 (Damper setting 1) and 220 (Damper setting 10). The NordicTrack RX-800 ranges from 150 to 450, where the 150 feels much lighter than a 150 on the Concept2. +Here, aside from the startup and spindown, the blue line shows that the impulses typically vary between 0.035 and 0.120 seconds. The red line depicts the *maximumTimeBetweenImpulses*, which is set to 0.120 seconds. When using the raw datafiles, realise that the goal is to distinguish good normal strokes from noise. So at startup it is quite accepted that the flywheel starts too slow to produce valid data during the biggest part of the first drive phase. Also at the end of a session the flywheel should spin down out of valid ranges again. So *maximumTimeBetweenImpulses* could be set lower, sometimes even hitting the "peaks" of the curves, without causing issues in normal use of Open Rowing Monitor (it will add warnings in the logs). Similarily, *minimumTimeBetweenImpulses* could be slightly increased to include some valleys, without causing much issues. -Here, some rowing and some knowledge about your rowing gets involved. Setting your damping factor is done by rowing a certain number of strokes and then seeing how much you have rowed and at what pace. If you know these metrics by hart, it just requires some rowing and adjusting to get them right. If you aren't that familiar with rowing, a good starting point is that a typical distance covered by a single stroke at 20 strokes per minute (SPM) is around 10 meters. So when you row a minute, you will have 20 strokes recorded and around 200 meters rowed. When possible, we use the [Concept Model D (or RowerErg)](https://www.concept2.com/indoor-rowers/concept2-rowerg) as a "Golden standard": when you know your pace on that machine, you can try to mimic that pace on your machine. Most gym's have one, so trying one can help you a lot in finding the right settings for your machine. +An important note is that *maximumTimeBetweenImpulses* is also used to detect wether the flywheel is spinning down due to lack of user input. Open Rowing Monitor pauses/stops the row when: + +* the start of the last drive is at least *maximumStrokeTimeBeforePause* ago; +* the entire flank (i.e. the last *flankLength* of measurements) contains only values above *maximumTimeBetweenImpulses*; +* the flywheel is decelerating throughout the flank. + +So setting the value for *maximumTimeBetweenImpulses* too high might block this behaviour as there aren't enough measurements to fill the flank. Although most air-based rowers have a spin down time of around 2 minutes, water rowers typically stop quite fast (think seconds). Therefore, especially the stop behaviour of water rowers requires a bit more attention. Again looking at the behaviour of the curve and the raw data might help here: looking how many residual samples follow after *maximumTimeBetweenImpulses* is exceeded (there should be more than *flankLength*) and how much time it spans since the last drive (exceeding *maximumStrokeTimeBeforePause*) is critical here. + +### Review smoothing + +**smoothing** is the ultimate fallback mechanism for rowers with very noisy data. Please refrain from using it, unless as a last resort (typically increasing *flankLength* is more effective and leads to better results). For all known rowers currently maintained by Open Rowing Monitor, **NONE** needed this, so only start working with this when the raw files show you have a very noisy signal, physical measures don't work and you can't get your stroke detection to work with other means (please note that we design the mechanisms here to be robust, so they can take a hit). + +This is a running median filter, effectively killing any extreme values. By default, it is set to 1 (off). A value of 3 will allow it to completely ignore any single extreme values, which should do the trick for most rowers. + +## Critical parameters you must change or review for stroke detection + +The key feature for Open Rowing Monitor is to reliably produce metrics you see on the monitor, share via Bluetooth with games and share with Strava and the like. Typically, these metrics are reported on a per-stroke basis, so a key element in getting rowing data right is getting the stroke detection right. It must be noted that we calculate most essential metrics in such a way that missing an individual stroke isn't a big deal, it will not even give hickups when this happens. However, the more advanced metrics (like drive length, stroke length, powercurves) won't provide any useful data when the stroke or stroke phase isn't detected properly. There are several critical parameters that are required for Open Rowing Monitor's stroke detection to work. In this section, we help you set the most critical ones. + +### setting numOfImpulsesPerRevolution + +**numOfImpulsesPerRevolution** tells Open Rowing Monitor how many impulses per rotation of the flywheel to expect. An inspection of the flywheel could reveal how many magnets it uses (typically a rower has 2 to 4 magnets). Although sometimes it is well-hidden, you can sometimes find it in the manual under the parts-list of your rower. + +### review sprocketRadius and minumumForceBeforeStroke + +**sprocketRadius** tells Open Rowing Monitor how big the sprocket is that attaches your belt/chain to your flywheel (in centimeters). This setting is used in calculating the handle force for stroke detection. **minumumForceBeforeStroke*** describes the minimum force (in Newtons) should be present on the handle before it will consider moving to a drive phase. The default values will work OK for most rowers, but sometimes it needs to be changed for a specific rower. On most rowers, there always is some noise present at the end of the drive section, and tuning these two parameters might help you remove that noisy tail. + +Their accuracy isn't super-critical. In later sections, we will describe how to optimally tune it as the *sprocketRadius* affects quite some metrics. Here your first goal is to get a working stroke detection. You can change these settings afterwards to something more accurate quite easily, but remember that when the *sprocketRadius* doubles, so should the *minumumForceBeforeStroke*. + +### setting flankLength and minimumStrokeQuality + +These settings are the core of the stroke detection and are the ones that require the most effort to get right. The most cricial settings are the *flankLength* and *minimumStrokeQuality*, where other metrics are much less critical. + +In a nutshell, a rowingstroke contains a drive phase and a recovery phase, and OpenRowingMonitor needs to recognise both reliably to work well. Please note, that for an actual transition to another phase respectively **minimumDriveTime** or **minimumRecoveryTime** have to be exceeded as well. + +To detect strokes, OpenRowingMonitor uses the following criteria before attempting a stroke phase transition: + +* a drive is detected when the handleforce is above **minumumForceBeforeStroke** AND the slope of a series of *flankLength* times between impulses is below the **minumumRecoverySlope** (i.e. accelerating) +* a recovery is detected when the handleforce is below **minumumForceBeforeStroke** AND the slope of a series of *flankLength* times between impulses is above the **minumumRecoverySlope** (i.e. decelerating) where the goodness of fit of that slope exceeds the **minimumStrokeQuality** + +**minimumStrokeQuality** is a setting that defines the minimal goodness of fit of the beforementioned minumumRecoverySlope with the datapoints. When the slope doesn't fit the data well, this will block moving to the next phase. A value of 0.1 is extrmely relaxed, where 0.95 would be extremely tight. This is set to 0.34 for most rowers, which is a working setting for all maintained rowers to date. The accuracy of this setting isn't super critical for stroke detection to work: for example, on a Concept2 values between 0.28 to 0.42 are known to give reliable stroke detection. Setting this too relaxed will result in earlier phase changes, settng this too strict will delay phase detection. But stroke detection will work. This setting is primarily used to optimise the stroke detection for advanced metrics (like drive time, drive length, force curves), so unless it gets in the way, there is no immediate need to change it. Please note that setting this value to 1, it will effectively disable half of the criteria for detecting a recovery, effectively making it completely handle-force based. + +The **flankLength** and **minumumRecoverySlope** settings determine the condition when the stroke detection is sufficiently confident that the stroke has started/ended. In essence, the stroke detection looks for a consecutive increasing/decreasing impulse lengths (with slope **minumumRecoverySlope**), and the **flankLength** determines how many consecutive flanks have to be seen before the stroke detection considers a stroke to begin or end. Setting these paramters requires some trial and error: + +* **minumumRecoverySlope** can be set to 0, where OpenRowingMonitor will use a quite robust selection on an accelerating or decelerating flywheel. This is recomended as a starting point for getting stroke detection to work. It can be further optimised later (see the later section on advanced stroke detection); +* Generally, a *flankLength* of 3 to 4 typically works. The technical minimum is 3, the maximum is limited by CPU-time. Please note that making the flank longer does *not* change your measurement in any way: the algorithms always rely on the beginning of the flank, not at the current end. If any, increasing the *flanklength* has the side-effect that some calculations are performed with more rigour, making them more precise as they get more data. Please note that the rower itself might limit the *flankLength*: some rowers only have 4 or 5 datapoints in a drive phase, naturally limiting the number of datapoints that can be used for stroke phase detection. Increasing this number too far (beyond a significant part of the stroke) will remove the fluctuations in the flywheel speed needed for stroke detections, so there is a practical upper limit to what the value of *flankLength* can be for a specific rower. Please note that a longer *flankLength* also requires more CPU time, where the calculation grows exponentially as *flankLength* becomes longer. On a Raspberry Pi 4B, a *flankLength* of 18 has been succesfully used without issue. What the practical limit on a Rapberry Pi Zero 2 W is, is still a matter of investigation. + +To make life a bit easier, it is possible to replay a raw recording of a previous rowing session. To do this, uncomment and modify the following lines in `server.js`: + + ```js +replayRowingSession(handleRotationImpulse, { + filename: 'recordings/2021/04/rx800_2021-04-21_1845_Rowing_30Minutes_Damper8.csv', // 30 minutes, damper 10 + realtime: false, + loop: false +}) +``` + +After changing the filename to a file that is your raw recording of your rower, you can replay it as often as you want by restarting the service. This will allow you to modify these settings and get feedback in seconds on the effects on Open Rowing Monitor. + +### minimumDriveTime and minimumRecoveryTime + +At the level of the stroke detection, there is some additional noise filtering, preventing noise to start a drive- or recovery-phase too early. The settings **minimumDriveTime** and **minimumRecoveryTime** determine the minimum times (in seconds) for the drive and recovery phases. Generally, the drive phase lasts at least 0.500 second, and the recovery phase 0.900 second for recreational rowers. These settings are good for most rowers, but running in to these filters might indicate that your settings aren't perfect yet. Please note that even on well-tuned machines, sometimes noise can cause this filter to kick in. So finding several of these reports in a session, might indicate that the above settings might need adjustment. + +When this kicks in, you will find this in your log: + + ```zsh + Sep 12 20:46:24 roeimachine npm[839]: Time: 1012.1644 sec: Delta Time trend is upwards, suggests no power, but waiting for drive phase length (0.0107 sec) to exceed mininimumDriveTime (0.40 sec) + ``` + +This typically suggests, as the lack of power occurs at 0.01 seconds into the drive time, that the drive phase is started too early. An other error you might find is the following one: + + ```zsh + Aug 16 22:38:07 roeimachine npm[1003]: Time: 25.5091 sec: Delta Time trend is downwards, suggesting power, but waiting for recoveryDuration suggests no power, but waiting for recovery phase length (0.4572 sec) to exceed minimumRecoveryTime (0.90 sec) + ``` + +This typically suggests, as the issue is so late in the recovery phase, that either the recovery started to late (this typically results in a stream of similar line following this one) or it is noise. + +## What reliable stroke detection should look like in the logs + +When you look in the logs, you hopefully find this: + + ```zsh + Sep 12 20:45:36 roeimachine npm[802]: stroke: 0, dist: 0.0m, speed: 0.00m/s, pace: Infinity/500m, power: 0W, drive length: 1.10 m, SPM: 0.0, drive dur: NaNs, rec. dur: NaNs + Sep 12 20:45:38 roeimachine npm[802]: *** RECOVERY phase started at time: 1.5644 sec + Sep 12 20:45:40 roeimachine npm[802]: *** DRIVE phase started at time: 3.6013 sec + Sep 12 20:45:40 roeimachine npm[802]: *** Calculated drag factor: 105.6459, no. samples: 143, Goodness of Fit: 0.9435 + Sep 12 20:45:40 roeimachine npm[802]: *** Calculated recovery slope: 0.001089, Goodness of Fit: 0.9435 + Sep 12 20:45:40 roeimachine npm[802]: stroke: 1, dist: 7.5m, speed: 1.80m/s, pace: 4:38/500m, power: 17W, drive length: 1.09 m, SPM: 23.2, drive dur: 1.56s, rec. dur: 2.04s + Sep 12 20:45:41 roeimachine npm[802]: *** RECOVERY phase started at time: 4.5018 sec + Sep 12 20:45:43 roeimachine npm[802]: *** DRIVE phase started at time: 6.5907 sec + Sep 12 20:45:43 roeimachine npm[802]: *** Calculated drag factor: 104.1759, no. samples: 196, Goodness of Fit: 0.9675 + Sep 12 20:45:43 roeimachine npm[802]: *** Calculated recovery slope: 0.001074, Goodness of Fit: 0.9675 + Sep 12 20:45:43 roeimachine npm[802]: stroke: 2, dist: 17.1m, speed: 2.93m/s, pace: 2:51/500m, power: 73W, drive length: 1.18 m, SPM: 20.2, drive dur: 1.23s, rec. dur: 2.06s + Sep 12 20:45:44 roeimachine npm[802]: *** RECOVERY phase started at time: 7.3455 sec + Sep 12 20:45:46 roeimachine npm[802]: *** DRIVE phase started at time: 9.5219 sec + Sep 12 20:45:46 roeimachine npm[802]: *** Calculated drag factor: 103.9705, no. samples: 214, Goodness of Fit: 0.9731 + Sep 12 20:45:46 roeimachine npm[802]: *** Calculated recovery slope: 0.001072, Goodness of Fit: 0.9731 + Sep 12 20:45:46 roeimachine npm[802]: stroke: 3, dist: 27.2m, speed: 3.39m/s, pace: 2:28/500m, power: 109W, drive length: 1.23 m, SPM: 20.8, drive dur: 0.83s, rec. dur: 2.13s + Sep 12 20:45:46 roeimachine npm[802]: *** RECOVERY phase started at time: 10.2020 sec + Sep 12 20:45:49 roeimachine npm[802]: *** DRIVE phase started at time: 12.4851 sec + Sep 12 20:45:49 roeimachine npm[802]: *** Calculated drag factor: 103.7254, no. samples: 232, Goodness of Fit: 0.9770 + Sep 12 20:45:49 roeimachine npm[802]: *** Calculated recovery slope: 0.001069, Goodness of Fit: 0.9770 + Sep 12 20:45:49 roeimachine npm[802]: stroke: 4, dist: 37.7m, speed: 3.51m/s, pace: 2:23/500m, power: 121W, drive length: 1.16 m, SPM: 20.6, drive dur: 0.72s, rec. dur: 2.23s + Sep 12 20:45:49 roeimachine npm[802]: *** RECOVERY phase started at time: 13.1464 sec + Sep 12 20:45:52 roeimachine npm[802]: *** DRIVE phase started at time: 15.3739 sec + Sep 12 20:45:52 roeimachine npm[802]: *** Calculated drag factor: 103.9593, no. samples: 226, Goodness of Fit: 0.9775 + Sep 12 20:45:52 roeimachine npm[802]: *** Calculated recovery slope: 0.001072, Goodness of Fit: 0.9775 + Sep 12 20:45:52 roeimachine npm[802]: stroke: 5, dist: 48.0m, speed: 3.56m/s, pace: 2:21/500m, power: 126W, drive length: 1.11 m, SPM: 20.6, drive dur: 0.67s, rec. dur: 2.26s + Sep 12 20:45:52 roeimachine npm[802]: *** RECOVERY phase started at time: 16.1629 sec + Sep 12 20:45:55 roeimachine npm[802]: *** DRIVE phase started at time: 18.3141 sec + Sep 12 20:45:55 roeimachine npm[802]: *** Calculated drag factor: 103.3899, no. samples: 216, Goodness of Fit: 0.9764 + Sep 12 20:45:55 roeimachine npm[802]: *** Calculated recovery slope: 0.001066, Goodness of Fit: 0.9764 + Sep 12 20:45:55 roeimachine npm[802]: stroke: 6, dist: 58.4m, speed: 3.55m/s, pace: 2:21/500m, power: 126W, drive length: 1.22 m, SPM: 20.1, drive dur: 0.73s, rec. dur: 2.19s + Sep 12 20:45:55 roeimachine npm[802]: *** RECOVERY phase started at time: 19.0078 sec + Sep 12 20:45:58 roeimachine npm[802]: *** DRIVE phase started at time: 21.3977 sec + Sep 12 20:45:58 roeimachine npm[802]: *** Calculated drag factor: 102.3463, no. samples: 236, Goodness of Fit: 0.9348 + Sep 12 20:45:58 roeimachine npm[802]: *** Calculated recovery slope: 0.001055, Goodness of Fit: 0.9348 + Sep 12 20:45:58 roeimachine npm[802]: stroke: 7, dist: 69.1m, speed: 3.49m/s, pace: 2:23/500m, power: 119W, drive length: 1.23 m, SPM: 20.2, drive dur: 0.74s, rec. dur: 2.27s + Sep 12 20:45:58 roeimachine npm[802]: *** RECOVERY phase started at time: 21.9592 sec + Sep 12 20:46:00 roeimachine npm[802]: *** DRIVE phase started at time: 24.1939 sec + Sep 12 20:46:00 roeimachine npm[802]: *** Calculated drag factor: 103.4389, no. samples: 225, Goodness of Fit: 0.9705 + Sep 12 20:46:00 roeimachine npm[802]: *** Calculated recovery slope: 0.001066, Goodness of Fit: 0.9705 + Sep 12 20:46:00 roeimachine npm[802]: stroke: 8, dist: 78.9m, speed: 3.50m/s, pace: 2:23/500m, power: 120W, drive length: 1.04 m, SPM: 20.9, drive dur: 0.63s, rec. dur: 2.31s + Sep 12 20:46:01 roeimachine npm[802]: *** RECOVERY phase started at time: 24.8737 sec + Sep 12 20:46:03 roeimachine npm[802]: *** DRIVE phase started at time: 27.1559 sec + Sep 12 20:46:03 roeimachine npm[802]: *** Calculated drag factor: 103.4498, no. samples: 228, Goodness of Fit: 0.9070 + Sep 12 20:46:03 roeimachine npm[802]: *** Calculated recovery slope: 0.001066, Goodness of Fit: 0.9070 + ``` + +When stroke detection works well, and you row consistently on the rower with a consistent catch, the values of "SPM" (Strokes per Minute), "drive dur" (drive duration) and "rec. dur" (recovery duration) will remain relatively stable across strokes. + +## Settings required to get the basic metrics right + +After getting the stroke detection right, we now turn to getting the basic linear metrics (i.e. distance, speed and power) right. There are some parameters you must change to get Open Rowing Monitor to calculate the real physics with a rower. + +### Setting the dragfactor + +**dragFactor** tells Open Rowing Monitor how much damping and thus resistance your flywheel is offering, which is an essential ingredient in calculating Power, Distance, Speed and thus pace. This is typically also dependent on your damper-setting (if present). Regardless if you use a static or dynamically calculated drag factor, this setting is needed as the first stroke also needs it to calculate distance, speed and power. Here, some rowing and some knowledge about your rowing gets involved. Setting your damping factor is done by rowing a certain number of strokes and then seeing how much you have rowed and at what pace. If you know these metrics by hart, it just requires some rowing and adjusting to get them right. If you aren't that familiar with rowing, a good starting point is that a typical distance covered by a single stroke at 20 strokes per minute (SPM) is around 10 meters. So when you row a minute, you will have 20 strokes recorded and around 200 meters rowed. When possible, we use the [Concept Model D (or RowerErg)](https://www.concept2.com/indoor-rowers/concept2-rowerg) as a "Golden standard": when you know your pace on that machine, you can try to mimic that pace on your machine. Most gym's have one, so trying one can help you a lot in finding the right settings for your machine. + +This results in a number, which works and can't be compared to anything else on the planet as that drag factor is highly dependent on the physical construction of the flywheel and mechanical properties of the transmission of power to the flywheel. For example, the Drag Factor for a Concept 2 ranges between 69 (Damper setting 1) and 220 (Damper setting 10). The NordicTrack RX-800 ranges from 150 to 450, where the 150 feels much lighter than a 150 on the Concept2. The Sportstech WRX700 water rower has a drag factor of 32000. + +### Setting the flywheel inertia + +**flywheelInertia** is the moment of inertia of the flywheel (in kg\*m2), which in practice influences the dynamically calculated dragfactor (and thus power, distance, speed and pace), but also the calculated force and power on the handle. A formal way to measure it is outlined in [Flywheel moment of inertia](https://dvernooy.github.io/projects/ergware/). However, the most practical way to set it is by rowing and see if the calculated drag factor approximates the previously set dragfactor needed to get a certain pace. + +The easiest way to test this value is by rowing (or simulating rowing): in the logs, the following lines will appear in debug mode: + + ```zsh + Sep 13 20:25:24 roeimachine npm[839]: *** Calculated drag factor: 103.5829, slope: 0.001064, Goodness of Fit: 0.9809, not used because autoAdjustDragFactor is not true + ``` + +If your flywheel inertia is set correctly, the calculated drag factor will be very close to the drag factor you set manually. Please look at the "Goodness of Fit" before using the data. Due to noise, the dragfactor sometimes can't be calculated accurately, which is reflected in this Goodness of Fit being low. So when comparing the calculated dragfactor with the manually determned dragfactor, use the calculated dragffactors where the Goodness of Fit is highest. + +Please note that this logmessage will change when autoAdjustDragFactor is set to true, but this content will always be reported in debug mode. ## Settings you COULD change for a new rower -In the previous section, we've guided you to set up a real robust working rower, but it will result in more crude data. To improve the accuracy of many measurements, you could switch to a more accurate and dynamic rower. This does require a more sophisticated rower: you need quite a few data points per stroke, with much accuracy, to get this working reliably. And a lot of rowing to get these settings right is involved. +In the previous section, we've guided you to set up a very robust working rower, but it will result in more crude data. To improve the accuracy of many measurements, you could switch to a more accurate and dynamic metric calculation. This does require a more sophisticated rower: you need quite a few data points per stroke, with much accuracy, to get this working reliably. So this setup certainly isn't for every rowing machine out there, although some options might just work. And again a lot of rowing to get these settings right is involved. + +### More accurate static stroke detection -### More accurate stroke detection +When **minumumRecoverySlope** is set to 0, the stroke detection looks for a consecutive increasing/decreasing impulse lengths which is extremely robust. When set to a higher value, it will detect the recovery only when that certain slope is reached or exceeded. This is relevant for more advanced metrics, like drive time, stroke length and the force curve. If your stroke detection roughly works and your logging level is set to debug, you will see lines like this in your log: -The **naturalDeceleration** setting determines the natural deceleration. This setting is used to distinguish between a powered and unpowered flywheel. This must be a negative number and indicates the level of deceleration required to interpret it as a free spinning flywheel. The best way to find the correct value for your rowing machine is a try and error approach. You can also set this to zero (or positive), to use the more robust, but not so precise acceleration-based stroke detection algorithm. Setting it to -0.1 enables the alternative less robust algorithm. By seeing how your stroke detection behaves during a row, you can slowly lower this number until you start missing strokes. + ```zsh + Sep 13 20:25:24 roeimachine npm[839]: *** Calculated drag factor: 103.5829, slope: 0.001064, Goodness of Fit: 0.9809, not used because autoAdjustDragFactor is not true + ``` -Please note that changing this stroke detection will affect your calculated dragFactor. +Here, the reported slope is the calculated slope during the recovery, and the goodness of fit the quality of the data match. A higher value (say above 0.80) suggestst that the data sufficiently. The easiest way to further optimise these settings is to select the lowest damper setting, and copy the lowest reported slope with a credible goodness of fit, with some margin (say 5%), making the minumumRecoverySlope 0.0010108. ### Dynamically adapting the drag factor -In reality, the drag factor of a rowing machine isn't static: it depends on air temperature, moisture, dust, (air)obstructions of the flywheel cage and sometimes even speed of the flywheel. So using a static drag factor is reliable, it isn't accurate. Open Rowing Monitor can automatically calculate the drag factor on-the-fly based on the recovery phase (see [this description of the underlying physics](physics_openrowingmonitor.md)). To do this, you need to set the following settings: +In reality, the drag factor of a rowing machine isn't static: it depends on air temperature, moisture, dust, (air)obstructions of the flywheel cage and sometimes even speed of the flywheel. So using a static drag factor is robust, but it isn't accurate. Open Rowing Monitor can automatically calculate the drag factor on-the-fly based on the recovery phase (see [this description of the underlying physics](physics_openrowingmonitor.md)). To do this, you need to set several settings. -* **autoAdjustDragFactor**: the Drag Factor can be calculated automatically. Setting it to true, will allow Open Rowing Monitor to automatically calculate the drag factor based on the **flywheelInertia** and the on the measured values in the stroke recovery phase. -* **flywheelInertia**: The moment of inertia of the flywheel (in kg\*m^2), which in practice influences your power values and distance. A formal way to measure it is outlined in [Flywheel moment of inertia](https://dvernooy.github.io/projects/ergware/). However, the most practical way to set it is by rowing and see what kind of power is displayed on the monitor. Typical ranges are weight dependent (see [this explanation](https://www.rowingmachine-guide.com/tabata-rowing-workouts.html)), and it helps if you know your times on a reliable machine like the Concept2. +It must be noted that you have to make sure that your machine's measurements are sufficiently free of noise: noise in the drag calculation can have a strong influence on your speed and distance calculations and thus your results. If your rower produces stable drag factor values, then this could be a good option to dynamically adjust your measurements to the damper setting of your rower as it takes in account environmental conditions. When your machine's power and speed readings are too volatile because of this dynamic calculation, it is wise to turn it off. -Please note that you don't need to use the dynamic drag factor to test your settings. To see the calculated drag factor for your rowing machine, please ensure that the logging level of the RowingEngine is set to 'info' or higher. Then do some strokes on the rower and observe the calculated drag factor in the logging. +First to change is **autoAdjustDragFactor** to "true", which tells Open Rowing Monitor that the Drag Factor must be calculated automatically. Setting it to true, will allow Open Rowing Monitor to automatically calculate the drag factor based on the already set *flywheelInertia* and the on the measured values in the stroke recovery phase. -It must be noted that you have to make sure that your machine's measurements are sufficiently free of noise: noise in the drag calculation can have a strong influence on your speed and distance calculations and thus your results. If your rower produces stable damping values, then this could be a good option to dynamically adjust your measurements to the damper setting of your rower as it takes in account environmental conditions. When your machine's power and speed readings are too volatile it is wise to turn it off +Each time the drag is calculated, we also get a quality indication from that same calculation: the "Goodness of Fit". Based on this quality indication (1.0 is best, 0.1 pretty bad), low quality drag factors are rejected to prevent the drag from being poisoned with bad data, throwing off all metrics. **minimumDragQuality** determines the minimum level of quality needed to The easiest way to set this, is by looking at the logs: + + ```zsh + Sep 13 20:25:24 roeimachine npm[839]: *** Calculated drag factor: 103.5829, slope: 0.001064, Goodness of Fit: 0.9809, not used because autoAdjustDragFactor is not true + ``` + +By selecting all these lines, you can see the "Goodness of Fit" for all calculations, see what the typical variation in "Goodness of Fit" is, and when a "Goodness of Fit" signals a deviant drag factor. Based on the logs, you should be able to set a minimumDragQuality. Please note: rejecting a dragfactor isn't much of an issue, as Open Rowing Monitor always retains the latest reliable dragfactor. + +Another measure to prevent sudden drag changes, is **dragFactorSmoothing**: this setting applies a median filter on a series of valid drag factors, further reducing the effect of outliers. Typically this is set to 5 strokes, but it could set to a different value if the drag calculation results in a wildly varying drag factor. + +### Dynamically adapting the recovery slope + +For a more accurate stroke detection, the *minumumRecoverySlope* is a crucial parameter. Open Rowing Monitor can automatically calculate the this recovery slope and adjust it dynamically. For this to work, *autoAdjustDragFactor* **MUST** be set to true, as the recovery slope is dependent on this automatic dragfactor calculation. If you set *autoAdjustDragFactor* to true, this option can be activated by setting *autoAdjustRecoverySlope* to "true". + +Setting *autoAdjustRecoverySlope* to "true" also activates one additional setting **autoAdjustRecoverySlopeMargin**. This is the margin used between the automatically calculated recovery slope and a next recovery slope. 5% (i.e. 0.05) is a pretty good margin and works well for most rowers. + +### sprocketRadius (revisited) + +**sprocketRadius** tells Open Rowing Monitor how big the sprocket is that attaches your belt/chain to your flywheel. Aside from being used in all handle force and speed calculations, it is also used in the drive length calculation. + +```zsh + Sep 12 20:46:00 roeimachine npm[802]: stroke: 8, dist: 78.9m, speed: 3.50m/s, pace: 2:23/500m, power: 120W, drive length: 1.04 m, SPM: 20.9, drive dur: 0.63s, rec. dur: 2.31s +``` + +Another use is found in the RowingData export (if used), where the peak handle force, average handle force, handle force curve, handle velocity curve and handle power curve depend on this. For the configuration we are quite focussed on drive length. Drive length is the length of the drive, in meters. For most people, it is about ${2 \over 3}$ of your own length. So changing it to fit that would be a good approach. Please note that increasing the drive length will reduce the handle force and handle power, so you can't increase this without restraint. Also remember that when the sprocket radius doubles, the *minumumForceBeforeStroke* should also be doubled. ## Settings you can tweak Some people want it all, and we're happy to give to you when your rower and your Raspberry Pi can handle the pain. Some interesting settings: -* **maximumImpulseTimeBeforePause** determines the maximum time between impulses before the rowing engine considers it a pause. * **magicConstant** is a constant that is commonly used to convert flywheel revolutions to a rowed distance and speed (see [the physics of ergometers](http://eodg.atm.ox.ac.uk/user/dudhia/rowing/physics/ergometer.html#section9)). Concept2 seems to use 2.8, which they admit is an arbitrary number which came close to their expectations of a competetion boat. As this setting only affects speed/distance, this setting typically is used to change the power needed to row a certain distance or reach a certain speed. So changing this can make your rower's metrics act as sluggish as an oil tanker (much power needed for little speed), or more like a smooth eight (less power needed for more speed). So for your rower, you could set your own plausible distance for the effort you put in. Please note that the rowed distance also depends on **flywheelInertia**, so please calibrate that before changing this constant. Another note: increasing this number decreases your rowed meters, but not in a linear fashion. -* **webUpdateInterval**: normally set at 1000 milliseconds, but for a more smoother experience on your monitor you can go as low as 100 ms. This makes the transition of the distance and time quite smooth, but at the price of some more network and CPU-load. -* **numOfPhasesForAveragingScreenData**: we average the data from several stroke phases to prevent the monitor and Bluetooth devices to become fidgety. Typically, we set this value to 6, which means 3 strokes (there are two phases in each stroke). However, some Bluetooth devices do their own calculations. And sometimes you really want the feedback on your individual strokes without any punches hold back. Setting this to 1 will result in a very volatile, but direct feedback mechanism on your stroke. -* **dampingConstantSmoothing** determines the smoothing of the dragfactor across strokes (if autoAdjustDragFactor is set to true). Normally set at 5 strokes, which prevents wild values to throw off all your measurements. If you have rower that produces very little noise in the data, then it could be an option to reduce. If your machine produces noisy data, this is the one to increase before anything else. -* **dampingConstantMaxChange** determines the maximum change between a currently determined dragfactor and the current average of the previous dampingConstantSmoothing strokes (if autoAdjustDragFactor is set to true). This filter reduces spikes in the calculation and thus makes the dragfactor less responsive to changes. The default value of 0.10 implies that the maximum upward/downward change is an increase of the drag with 10%. Please note that this filter still allows changes, it just limits their impact to this percentage. Most rower's dragfactor is relatively constant, however certain hyrid rower's dragfactor changes when the speed changes. To allow for bigger changes within a stroke, increase this setting. When the expected changes are small, set this setting small. When your rower is a hybrid or when you have one configuration for all your damper settings, this should be a bit wider. -* **createRawDataFiles**: This is as raw as it gets, as setting this to `true` makes Open Rowing Monitor dump the raw impulse-lengths to a file (see [how we interpret this data](physics_openrowingmonitor.md)). + +## Sending in a rower profile to us + +Sending in a rower profile helps other users, but also helps yourself as we structurally test and update the knowwn configurations. We need the following things from you to maintain a rower profile: + +* The **profile** itself: these are the settings you used to get the machine working. +* A **raw datafile** (described above) of a session, preferably with your distance and time. This allows us to test if newer versions of OpenRowingMonitor will deliver similar results to you. diff --git a/install/config.js b/install/config.js index 35482b2524..1b05522f6e 100644 --- a/install/config.js +++ b/install/config.js @@ -14,25 +14,29 @@ import rowerProfiles from './rowerProfiles.js' export default { - /* // example: change the default log level: loglevel: { default: 'debug' }, - // example: set a rower profile: - rowerSettings: rowerProfiles.DKNR320 - - // example: set custom rower settings: - rowerSettings: { - numOfImpulsesPerRevolution: 1, - dragFactor: 0.03, - flywheelInertia: 0.3 - } - - // example: set a rower profile, but overwrite some settings: - rowerSettings: Object.assign(rowerProfiles.DKNR320, { - autoAdjustDragFactor: true - }) - */ + // The rower specific settings. Either choose a profile from config/rowerProfiles.js (see + // https://github.com/laberning/openrowingmonitor/blob/main/docs/Supported_Rowers.md) or define + // the settings manually (see https://github.com/laberning/openrowingmonitor/blob/main/docs/rower_settings.md + // on how to do this). If you find good settings for a new rowing device please send them to us (together + // with a raw recording of at least 10 strokes) so we can add the device to the profiles. + + // EXAMPLE ROWER CONFIG : using a DKN R-320 Air Rower as is + // rowerSettings: rowerProfiles.DKN_R320 + + // EXAMPLE ROWER CONFIG: Just set custom rower settings to make it work + // rowerSettings: { + // numOfImpulsesPerRevolution: 1, + // dragFactor: 0.03, + // flywheelInertia: 0.3 + // } + + // EXAMPLE ROWER CONFIG: set a rower profile, but overwrite some settings: + // rowerSettings: Object.assign(rowerProfiles.DKN_R320, { + // autoAdjustDragFactor: true + // }) } diff --git a/install/install.sh b/install/install.sh index ec4dbf05e6..ead797763b 100755 --- a/install/install.sh +++ b/install/install.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Open Rowing Monitor, https://github.com/laberning/openrowingmonitor +# Open Rowing Monitor, https://github.com/JaapvanEkris/openrowingmonitor # # Installation script for Open Rowing Monitor, use at your own risk! # @@ -50,7 +50,7 @@ ask() { CURRENT_DIR=$(pwd) INSTALL_DIR="/opt/openrowingmonitor" -GIT_REMOTE="https://github.com/laberning/openrowingmonitor.git" +GIT_REMOTE="https://github.com/JaapvanEkris/openrowingmonitor.git" print "This script will set up Open Rowing Monitor on one of the following devices" print " Raspberry Pi Zero W or WH" @@ -73,9 +73,16 @@ if [[ $MODEL != Raspberry* ]]; then fi VERSION=$(grep -oP '(?<=^VERSION=).+' /etc/os-release | tr -d '"') +if [[ $VERSION == "12 (bookworm)" ]]; then + print + print "Error: So far this install script is not capable of installing on Raspberry Pi OS 12 (bookworm)." + print "Please use Raspberry Pi 11 (bullseye), which can be installed via the Raspberry Manager via the 'Raspberry Pi OS (other)' option, under the Legacy versions" + exit 1 +fi + if [[ $VERSION != "10 (buster)" ]] && [[ $VERSION != "11 (bullseye)" ]]; then print - print "Warning: So far this install script has only been tested with Raspberry Pi OS 10 (buster) and OS 11 (bullseye)." + print "Warning: So far this install script has only been tested with Raspberry Pi OS 10 (buster), 11 (bullseye)" if ! ask "You are running Raspberry Pi OS $VERSION, are you sure that you want to continue?" N; then exit 1 fi @@ -115,13 +122,19 @@ sudo apt-get -y update sudo apt-get -y dist-upgrade sudo systemctl disable bluetooth sudo apt-get -y install bluetooth bluez libbluetooth-dev libudev-dev git +sudo apt-get -y install pigpio +# We disable the pigpio service explicity, as the JS wrapper is alergic to the deamon +sudo systemctl mask pigpiod.service print ARCHITECTURE=$(uname -m) if [[ $ARCHITECTURE == "armv6l" ]]; then print "You are running a system with ARM v6 architecture. Official support for Node.js has been discontinued" - print "for ARM v6. Installing experimental unofficial build of Node.js..." + print "for ARM v6." + print "Due to package conflicts, support for this version of OpenRowingMonitor will cease in April 2025" + print + print "Installing experimental unofficial build of Node.js..." # we stick to node 14 as there are problem with WebAssembly on node 16 on the armv6l architecture NODEJS_VERSION=v14.18.3 @@ -139,7 +152,7 @@ then sudo ln -sfn /opt/nodejs/bin/npm /usr/local/bin/npm else print "Installing Node.js..." - curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash - + curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt-get install -y nodejs fi @@ -155,20 +168,23 @@ cd $INSTALL_DIR # get project code from repository sudo git init -q # older versions of git would use 'master' instead of 'main' for the default branch -sudo git checkout -q -b main +sudo git checkout -q -b v1beta_updates sudo git config remote.origin.url $GIT_REMOTE sudo git config remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # prevent altering line endings sudo git config core.autocrlf false sudo git fetch --force origin sudo git fetch --force --tags origin -sudo git reset --hard origin/main +sudo git reset --hard origin/v1beta_updates # add bin directory to the system path echo "export PATH=\"\$PATH:$INSTALL_DIR/bin\"" >> ~/.bashrc # otherwise node-gyp would fail while building the system dependencies -sudo npm config set user 0 +# On newer nodejs versions (> Node 16) we solve this via the .npmrc file +if [[ $ARCHITECTURE == "armv6l" ]]; then + sudo npm config set user 0 +fi print print "Downloading and compiling Runtime dependencies..." diff --git a/install/openrowingmonitor.service b/install/openrowingmonitor.service index 71ece090c4..3e42478381 100644 --- a/install/openrowingmonitor.service +++ b/install/openrowingmonitor.service @@ -1,6 +1,8 @@ [Unit] Description=Open Rowing Monitor After=multi-user.target +StartLimitIntervalSec=60 +StartLimitBurst=5 [Service] Type=simple diff --git a/install/webbrowserkiosk.service b/install/webbrowserkiosk.service index de6f2a4e48..519ac55531 100644 --- a/install/webbrowserkiosk.service +++ b/install/webbrowserkiosk.service @@ -1,6 +1,8 @@ [Unit] Description=X11 Web Browser Kiosk After=multi-user.target +StartLimitIntervalSec=60 +StartLimitBurst=5 [Service] Type=simple diff --git a/install/webbrowserkiosk.sh b/install/webbrowserkiosk.sh index 057c7322c2..1eb5680b6c 100644 --- a/install/webbrowserkiosk.sh +++ b/install/webbrowserkiosk.sh @@ -12,4 +12,4 @@ openbox-session & # Start Chromium in kiosk mode sed -i 's/"exited_cleanly":false/"exited_cleanly":true/' ~/.config/chromium/'Local State' sed -i 's/"exited_cleanly":false/"exited_cleanly":true/; s/"exit_type":"[^"]\+"/"exit_type":"Normal"/' ~/.config/chromium/Default/Preferences -chromium-browser --disable-infobars --disable-features=AudioServiceSandbox --kiosk --noerrdialogs --disable-session-crashed-bubble --disable-pinch --check-for-update-interval=604800 --app="http://127.0.0.1/?mode=kiosk" +chromium-browser --disable-infobars --disable-features=AudioServiceSandbox --kiosk --noerrdialogs --ignore-certificate-errors --disable-session-crashed-bubble --disable-pinch --enable-low-end-device-mode --disable-site-isolation-trials --renderer-process-limit=2 --check-for-update-interval=604800 --app="http://127.0.0.1/?mode=kiosk" diff --git a/package-lock.json b/package-lock.json index 33e4e08427..84a674d177 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,26 +1,28 @@ { "name": "openrowingmonitor", - "version": "0.8.2", + "version": "0.9.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "openrowingmonitor", - "version": "0.8.2", + "version": "0.9.0", "license": "GPL-3.0", "dependencies": { - "@abandonware/bleno": "0.5.1-4", - "@abandonware/noble": "1.9.2-15", - "ant-plus": "0.1.24", - "finalhandler": "1.1.2", + "@abandonware/bleno": "^0.6.1", + "@abandonware/noble": "^1.9.2-23", + "chart.js": "^4.4.1", + "chartjs-plugin-datalabels": "^2.2.0", + "finalhandler": "^1.2.0", "form-data": "4.0.0", - "lit": "2.1.3", - "loglevel": "1.8.0", + "incyclist-ant-plus": "^0.3.1", + "lit": "^2.8.0", + "loglevel": "^1.9.1", "nosleep.js": "0.12.0", - "onoff": "6.0.3", - "serve-static": "1.14.2", - "ws": "8.5.0", - "xml2js": "0.4.23" + "pigpio": "3.3.1", + "serve-static": "^1.15.0", + "ws": "^8.16.0", + "xml2js": "^0.6.2" }, "devDependencies": { "@babel/eslint-parser": "7.17.0", @@ -41,27 +43,27 @@ "eslint-plugin-wc": "1.3.2", "http2-proxy": "5.0.53", "markdownlint-cli2": "0.4.0", - "nodemon": "2.0.15", + "nodemon": "^3.0.3", "npm-run-all": "4.1.5", "rollup": "2.67.2", "rollup-plugin-summary": "1.3.0", "rollup-plugin-terser": "7.0.2", - "simple-git-hooks": "2.7.0", + "simple-git-hooks": "^2.9.0", "snowpack": "3.8.8", - "tar": "6.1.11", - "uvu": "0.5.3" + "tar": "^6.2.0", + "uvu": "^0.5.6" }, "engines": { "node": ">=14" }, "optionalDependencies": { - "@abandonware/bluetooth-hci-socket": "0.5.3-7" + "@abandonware/bluetooth-hci-socket": "^0.5.3-10" } }, "node_modules/@abandonware/bleno": { - "version": "0.5.1-4", - "resolved": "https://registry.npmjs.org/@abandonware/bleno/-/bleno-0.5.1-4.tgz", - "integrity": "sha512-2K/gbDxh4l4TV8xT/XUCwCT3e5aGDGmYad8gxt19CEvBMCs0+JScZ7roNyX0Jzice5rrR5RETcsMwIjJSzbeCQ==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@abandonware/bleno/-/bleno-0.6.1.tgz", + "integrity": "sha512-UYJhfOlRpg+o6H9VCL3xvBBchEV3mfGS+/ESmFd4cTy/HmoBTofFTecBMvV79/AZP5CSmQ9Qifx03kPBA4PgsQ==", "hasInstallScript": true, "os": [ "darwin", @@ -71,23 +73,28 @@ "win32" ], "dependencies": { - "debug": "^4.3.1", + "debug": "^4.3.4", "napi-thread-safe-callback": "0.0.6", - "node-addon-api": "^3.1.0" + "node-addon-api": "^6.1.0" }, "engines": { - "node": ">=6" + "node": ">=14" }, "optionalDependencies": { - "@abandonware/bluetooth-hci-socket": "^0.5.3-7", - "bplist-parser": "0.3.0", - "xpc-connect": "^2.0.0" + "@abandonware/bluetooth-hci-socket": "^0.5.3-10", + "bplist-parser": "0.3.2", + "xpc-connect": "^3.0.0" } }, + "node_modules/@abandonware/bleno/node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" + }, "node_modules/@abandonware/bluetooth-hci-socket": { - "version": "0.5.3-7", - "resolved": "https://registry.npmjs.org/@abandonware/bluetooth-hci-socket/-/bluetooth-hci-socket-0.5.3-7.tgz", - "integrity": "sha512-CaGDBeXEooRjaVJlgmnaWeI+MXlEBVN9705tp2GHCF2IFARH3h15lqf6eHjqFsdpQOiMWiBa/QZUAOGjzBrhmA==", + "version": "0.5.3-10", + "resolved": "https://registry.npmjs.org/@abandonware/bluetooth-hci-socket/-/bluetooth-hci-socket-0.5.3-10.tgz", + "integrity": "sha512-wXHiGmHaHz1pUR6Ix6uut24uTYCWZJL68aP9F056jnnl+U3KrjLT77g9dZHWDAId+9yQN54lLO4MEOIV0z9fUg==", "hasInstallScript": true, "optional": true, "os": [ @@ -97,18 +104,18 @@ "win32" ], "dependencies": { - "debug": "^4.3.1", - "nan": "^2.14.2", - "node-pre-gyp": "^0.17.0" + "@mapbox/node-pre-gyp": "^1.0.10", + "debug": "^4.3.4", + "nan": "^2.17.0" }, "optionalDependencies": { - "usb": "^1.6.3" + "usb": "^1.9.2" } }, "node_modules/@abandonware/noble": { - "version": "1.9.2-15", - "resolved": "https://registry.npmjs.org/@abandonware/noble/-/noble-1.9.2-15.tgz", - "integrity": "sha512-qD9NN5fzvbtHdWYFPDzxY2AveILvDSRX/PTdL0V+CUfyF70ggIJtLBc1WW1hbVMIpu8rZylYgrK+PUEBwIpjCg==", + "version": "1.9.2-23", + "resolved": "https://registry.npmjs.org/@abandonware/noble/-/noble-1.9.2-23.tgz", + "integrity": "sha512-BPb/a2s+t6SIZRU4oNfY61cPM91/+dH0t8Ulb4QpQ1zBfKtR+n4r/r6j+vPcnAOiQ5hWC2lDj+Mc/iiZPAYLRw==", "hasInstallScript": true, "os": [ "darwin", @@ -117,35 +124,16 @@ "win32" ], "dependencies": { - "debug": "^4.3.1", - "node-addon-api": "^3.2.0" + "debug": "^4.3.4", + "napi-thread-safe-callback": "^0.0.6", + "node-addon-api": "^4.3.0", + "node-gyp-build": "^4.5.0" }, "engines": { "node": ">=6" }, "optionalDependencies": { - "@abandonware/bluetooth-hci-socket": "^0.5.3-8" - } - }, - "node_modules/@abandonware/noble/node_modules/@abandonware/bluetooth-hci-socket": { - "version": "0.5.3-8", - "resolved": "https://registry.npmjs.org/@abandonware/bluetooth-hci-socket/-/bluetooth-hci-socket-0.5.3-8.tgz", - "integrity": "sha512-JIUkTZpAo6vKyXd94OasynjnmAxgCvn3VRrQJM/KXBKbm/yW59BMK6ni1wLy/JLM4eFhsLkd2S907HJnXBSWKw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "linux", - "android", - "freebsd", - "win32" - ], - "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.5", - "debug": "^4.3.2", - "nan": "^2.15.0" - }, - "optionalDependencies": { - "usb": "^1.7.2" + "@abandonware/bluetooth-hci-socket": "^0.5.3-10" } }, "node_modules/@ampproject/remapping": { @@ -1869,21 +1857,34 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz", + "integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==" + }, "node_modules/@lit/reactive-element": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.2.2.tgz", - "integrity": "sha512-HkhTTO2rT8jlf4izz7ME/+YUjqz+ZHgmnOKorA+7tkDmQDg6QzDpWSFz//1YyiL193W4bc7rlQCiYyFiZa9pkQ==" + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz", + "integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.0.0" + } }, "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz", - "integrity": "sha512-CMGKi28CF+qlbXh26hDe6NxCd7amqeAzEqnS6IHeO6LoaKyM/n+Xw3HT1COdq8cuioOdlKdqn/hCmqPUOMOywg==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", "optional": true, "dependencies": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", "make-dir": "^3.1.0", - "node-fetch": "^2.6.5", + "node-fetch": "^2.6.7", "nopt": "^5.0.0", "npmlog": "^5.0.1", "rimraf": "^3.0.2", @@ -1907,6 +1908,15 @@ "node": ">=10" } }, + "node_modules/@mapbox/node-pre-gyp/node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/@mapbox/node-pre-gyp/node_modules/gauge": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", @@ -2551,9 +2561,15 @@ } }, "node_modules/@types/trusted-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", - "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + }, + "node_modules/@types/w3c-web-usb": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.6.tgz", + "integrity": "sha512-cSjhgrr8g4KbPnnijAr/KJDNKa/bBa+ixYkywFRvrhvi9n1WEl7yYbtRyzE6jqNQiSxxJxoAW3STaOQwJHndaw==", + "optional": true }, "node_modules/@web/parse5-utils": { "version": "1.3.0", @@ -2736,17 +2752,6 @@ "node": ">=4" } }, - "node_modules/ant-plus": { - "version": "0.1.24", - "resolved": "https://registry.npmjs.org/ant-plus/-/ant-plus-0.1.24.tgz", - "integrity": "sha512-otEIAN+9jtu/1mwHL81au0sO7muJT1QrWOm9j29NEg3x1Y+ZsSIYX+LSdY+BBr2mLA4hT/vpqv5pWX9KDWceVg==", - "dependencies": { - "usb": "^1.6.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -2770,7 +2775,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", - "devOptional": true, + "dev": true, "dependencies": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -3130,9 +3135,9 @@ } }, "node_modules/bplist-parser": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.0.tgz", - "integrity": "sha512-zgmaRvT6AN1JpPPV+S0a1/FAtoxSreYDccZGIqEMSvZl9DMe70mJ7MFzpxa1X+gHVdkToE2haRUHHMiW1OdejA==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.2.tgz", + "integrity": "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==", "optional": true, "dependencies": { "big-integer": "1.6.x" @@ -3410,6 +3415,25 @@ "node": ">=6" } }, + "node_modules/chart.js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", + "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=7" + } + }, + "node_modules/chartjs-plugin-datalabels": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz", + "integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==", + "peerDependencies": { + "chart.js": ">=3.0.0" + } + }, "node_modules/cheerio": { "version": "1.0.0-rc.10", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", @@ -3495,12 +3519,6 @@ "node": ">=10" } }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, "node_modules/cjs-module-lexer": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", @@ -3674,23 +3692,6 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "devOptional": true }, - "node_modules/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -3733,7 +3734,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "devOptional": true + "dev": true }, "node_modules/cosmiconfig": { "version": "7.0.1", @@ -3783,15 +3784,6 @@ "node": ">= 8" } }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/css-select": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.2.1.tgz", @@ -3844,10 +3836,18 @@ "node": ">=0.10" } }, + "node_modules/dbly-linked-list": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/dbly-linked-list/-/dbly-linked-list-0.3.4.tgz", + "integrity": "sha512-327vOlwspi9i1T3Kc9yZhRUR8qDdgMQ4HmXsFDDCQ/HTc3sNe7gnF5b0UrsnaOJ0rvmG7yBZpK0NoOux9rKYKw==", + "dependencies": { + "lodash.isequal": "^4.5.0" + } + }, "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { "ms": "2.1.2" }, @@ -3896,15 +3896,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "devOptional": true, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4000,6 +3991,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true, "engines": { "node": ">= 0.6" } @@ -4014,20 +4006,12 @@ } }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "optional": true, - "bin": { - "detect-libc": "bin/detect-libc.js" - }, + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "engines": { - "node": ">=0.10" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/detect-port": { @@ -4170,30 +4154,12 @@ "tslib": "^2.0.3" } }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -4277,19 +4243,6 @@ "node": ">=6" } }, - "node_modules/epoll": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/epoll/-/epoll-4.0.1.tgz", - "integrity": "sha512-BgCq0nEsk+XI7y9qjrRtt9uXsyFEdvevvq42xl6t/hKZjxLSDZreD9rTZ0pU40V//c3Zzk2PZGuIsn8YJHSJ4g==", - "hasInstallScript": true, - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.14.2" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", @@ -4381,15 +4334,6 @@ "node": ">=6" } }, - "node_modules/escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -5221,16 +5165,16 @@ } }, "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "engines": { @@ -5343,7 +5287,7 @@ "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "engines": { "node": ">= 0.6" } @@ -5396,7 +5340,7 @@ "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "devOptional": true, + "dev": true, "dependencies": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -5412,7 +5356,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.10.0" } @@ -5421,7 +5365,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "devOptional": true, + "dev": true, "dependencies": { "ansi-regex": "^2.0.0" }, @@ -5530,30 +5474,6 @@ "node": ">=10.13.0" } }, - "node_modules/global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dev": true, - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/global-dirs/node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -5721,15 +5641,6 @@ "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "devOptional": true }, - "node_modules/has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -5792,18 +5703,26 @@ "dev": true }, "node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dependencies": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" } }, "node_modules/http-proxy-agent": { @@ -5894,18 +5813,6 @@ "ms": "^2.0.0" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/icss-replace-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", @@ -5943,7 +5850,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", - "devOptional": true, + "dev": true, "dependencies": { "minimatch": "^3.0.4" } @@ -5964,15 +5871,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -5982,6 +5880,41 @@ "node": ">=0.8.19" } }, + "node_modules/incyclist-ant-plus": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/incyclist-ant-plus/-/incyclist-ant-plus-0.3.1.tgz", + "integrity": "sha512-v/O7EQiwHQrtqUeh5rechAUfpZ5dl30Ty1stie4Y9XdcI93v1JKeNrHuyWZptatBsyYY1+rUTusrISICrqfFWg==", + "dependencies": { + "queue-fifo": "^0.2.6" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "usb": "^2.7.0" + } + }, + "node_modules/incyclist-ant-plus/node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "optional": true + }, + "node_modules/incyclist-ant-plus/node_modules/usb": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/usb/-/usb-2.8.0.tgz", + "integrity": "sha512-umwfJjG3ADI+0xO+7pkrblX2+2BYDgzJTgWrSoxisXncsA2zW30VX2yly5W2U/gqldx6x2sn9b1Uk2gZht6JBQ==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@types/w3c-web-usb": "^1.0.6", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.5.0" + }, + "engines": { + "node": ">=10.20.0 <11.x || >=12.17.0 <13.0 || >=14.0.0" + } + }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -6012,12 +5945,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "devOptional": true - }, "node_modules/internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -6096,18 +6023,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-core-module": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", @@ -6183,22 +6098,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", @@ -6223,18 +6122,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6259,24 +6146,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", @@ -6416,17 +6285,11 @@ "node": ">=8" } }, - "node_modules/is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "devOptional": true + "dev": true }, "node_modules/isbinaryfile": { "version": "4.0.8", @@ -6658,18 +6521,6 @@ "node": ">=6" } }, - "node_modules/latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "dependencies": { - "package-json": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -6699,28 +6550,29 @@ } }, "node_modules/lit": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.1.3.tgz", - "integrity": "sha512-46KtKy7iDoY3wZ5VSqBlXll6J/tli5gRMPFRWi5qQ01lvIqcO+dYQwb1l1NYZjbzcHnGnCKrMb8nDv7/ZE4Y4g==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz", + "integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==", "dependencies": { - "@lit/reactive-element": "^1.1.0", - "lit-element": "^3.1.0", - "lit-html": "^2.1.0" + "@lit/reactive-element": "^1.6.0", + "lit-element": "^3.3.0", + "lit-html": "^2.8.0" } }, "node_modules/lit-element": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.1.2.tgz", - "integrity": "sha512-5VLn5a7anAFH7oz6d7TRG3KiTZQ5GEFsAgOKB8Yc+HDyuDUGOT2cL1CYTz/U4b/xlJxO+euP14pyji+z3Z3kOg==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz", + "integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==", "dependencies": { - "@lit/reactive-element": "^1.1.0", - "lit-html": "^2.1.0" + "@lit-labs/ssr-dom-shim": "^1.1.0", + "@lit/reactive-element": "^1.3.0", + "lit-html": "^2.8.0" } }, "node_modules/lit-html": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.1.3.tgz", - "integrity": "sha512-WgvdwiNeuoT0mYEEJI+AAV2DEtlqzVM4lyDSaeQSg5ZwhS/CkGJBO/4n66alApEuSS9WXw9+ADBp8SPvtDEKSg==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz", + "integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==", "dependencies": { "@types/trusted-types": "^2.0.2" } @@ -6771,7 +6623,13 @@ "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -6780,9 +6638,9 @@ "dev": true }, "node_modules/loglevel": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", - "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", + "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", "engines": { "node": ">= 0.6.0" }, @@ -7051,9 +6909,9 @@ } }, "node_modules/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "devOptional": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -7066,7 +6924,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "devOptional": true + "dev": true }, "node_modules/minipass": { "version": "3.1.6", @@ -7168,18 +7026,6 @@ "node": ">= 8" } }, - "node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "optional": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/mkdirp-infer-owner": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz", @@ -7221,9 +7067,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", + "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==" }, "node_modules/nanoid": { "version": "3.2.0", @@ -7248,32 +7094,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "node_modules/needle": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", - "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", - "optional": true, - "dependencies": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "bin": { - "needle": "bin/needle" - }, - "engines": { - "node": ">= 4.4.x" - } - }, - "node_modules/needle/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "optional": true, - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -7300,9 +7120,9 @@ } }, "node_modules/node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" }, "node_modules/node-fetch": { "version": "2.6.7", @@ -7349,9 +7169,9 @@ } }, "node_modules/node-gyp-build": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -7388,127 +7208,6 @@ "node": ">=10" } }, - "node_modules/node-pre-gyp": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.17.0.tgz", - "integrity": "sha512-abzZt1hmOjkZez29ppg+5gGqdPLUuJeAEwVPtHYEJgx0qzttCbcKFpxrCQn2HYbwCv2c+7JwH4BgEzFkUGpn4A==", - "deprecated": "Please upgrade to @mapbox/node-pre-gyp: the non-scoped node-pre-gyp package is deprecated and only the @mapbox scoped package will recieve updates in the future", - "optional": true, - "dependencies": { - "detect-libc": "^1.0.3", - "mkdirp": "^0.5.5", - "needle": "^2.5.2", - "nopt": "^4.0.3", - "npm-packlist": "^1.4.8", - "npmlog": "^4.1.2", - "rc": "^1.2.8", - "rimraf": "^2.7.1", - "semver": "^5.7.1", - "tar": "^4.4.13" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, - "node_modules/node-pre-gyp/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "optional": true - }, - "node_modules/node-pre-gyp/node_modules/fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "optional": true, - "dependencies": { - "minipass": "^2.6.0" - } - }, - "node_modules/node-pre-gyp/node_modules/minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "optional": true, - "dependencies": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "node_modules/node-pre-gyp/node_modules/minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "optional": true, - "dependencies": { - "minipass": "^2.9.0" - } - }, - "node_modules/node-pre-gyp/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "optional": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/node-pre-gyp/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true - }, - "node_modules/node-pre-gyp/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "optional": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/node-pre-gyp/node_modules/tar": { - "version": "4.4.19", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", - "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", - "optional": true, - "dependencies": { - "chownr": "^1.1.4", - "fs-minipass": "^1.2.7", - "minipass": "^2.9.0", - "minizlib": "^1.3.3", - "mkdirp": "^0.5.5", - "safe-buffer": "^5.2.1", - "yallist": "^3.1.1" - }, - "engines": { - "node": ">=4.5" - } - }, - "node_modules/node-pre-gyp/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "optional": true - }, "node_modules/node-releases": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", @@ -7516,63 +7215,46 @@ "dev": true }, "node_modules/nodemon": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", - "integrity": "sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.3.tgz", + "integrity": "sha512-7jH/NXbFPxVaMwmBCC2B9F/V6X1VkEdNgx3iu9jji8WxWcvhMWkmhNWhI5077zknOnZnBzba9hZP6bCPJLSReQ==", "dev": true, - "hasInstallScript": true, "dependencies": { "chokidar": "^3.5.2", - "debug": "^3.2.7", + "debug": "^4", "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "pstree.remy": "^1.1.8", - "semver": "^5.7.1", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", "supports-color": "^5.5.0", "touch": "^3.1.0", - "undefsafe": "^2.0.5", - "update-notifier": "^5.1.0" + "undefsafe": "^2.0.5" }, "bin": { "nodemon": "bin/nodemon.js" }, "engines": { - "node": ">=8.10.0" + "node": ">=10" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/nodemon" } }, - "node_modules/nodemon/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/nodemon/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "optional": true, "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" + "lru-cache": "^6.0.0" }, "bin": { - "nopt": "bin/nopt.js" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/normalize-package-data": { @@ -7626,7 +7308,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", - "devOptional": true, + "dev": true, "dependencies": { "npm-normalize-package-bin": "^1.0.1" } @@ -7662,7 +7344,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "devOptional": true + "dev": true }, "node_modules/npm-package-arg": { "version": "8.1.5", @@ -7705,17 +7387,6 @@ "node": ">=10" } }, - "node_modules/npm-packlist": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", - "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", - "optional": true, - "dependencies": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, "node_modules/npm-pick-manifest": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz", @@ -7868,7 +7539,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "devOptional": true, + "dev": true, "dependencies": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -7969,9 +7640,9 @@ } }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { "ee-first": "1.1.1" }, @@ -8003,18 +7674,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/onoff": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/onoff/-/onoff-6.0.3.tgz", - "integrity": "sha512-xtVlwRDzswYM69bzzIui/qzu7QHsFnjsQiCV1iYVA/HXt5xdc9utc97SYAlXzK8wAhIN7+H7MaVqh2vpfdKk9A==", - "dependencies": { - "epoll": "^4.0.1", - "lodash.debounce": "^4.0.8" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/open": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", @@ -8053,30 +7712,11 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "optional": true, + "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "optional": true, - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", @@ -8165,191 +7805,10 @@ "node_modules/p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json/node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json/node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "dependencies": { - "defer-to-connect": "^1.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json/node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json/node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json/node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/package-json/node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, - "node_modules/package-json/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json/node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/package-json/node_modules/got/node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/package-json/node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, - "node_modules/package-json/node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.0" - } - }, - "node_modules/package-json/node_modules/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json/node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json/node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "dependencies": { - "lowercase-keys": "^1.0.0" - } - }, - "node_modules/package-json/node_modules/responselike/node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, "node_modules/pacote": { @@ -8591,6 +8050,19 @@ "node": ">=4" } }, + "node_modules/pigpio": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/pigpio/-/pigpio-3.3.1.tgz", + "integrity": "sha512-z7J55K14IwWkA+oW5JHzWcgwThFAuJ7IzV3A2//yRm4jJ2DTU0DHIy91DB0siOi12rvvlrIhRetEuAo0ztF/vQ==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.14.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -8797,15 +8269,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/printable-characters": { "version": "1.0.42", "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", @@ -8822,7 +8285,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "devOptional": true + "dev": true }, "node_modules/promise-all-reject-late": { "version": "1.0.1", @@ -8892,18 +8355,6 @@ "node": ">=6" } }, - "node_modules/pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, - "dependencies": { - "escape-goat": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", @@ -8913,6 +8364,14 @@ "node": ">=0.6" } }, + "node_modules/queue-fifo": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/queue-fifo/-/queue-fifo-0.2.6.tgz", + "integrity": "sha512-rwlnZHAaTmWEGKC7ziasK8u4QnZW/uN6kSiG+tHNf/1GA+R32FArZi18s3SYUpKcA0Y6jJoUDn5GT3Anoc2mWw==", + "dependencies": { + "dbly-linked-list": "0.3.4" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -8962,30 +8421,6 @@ "node": ">= 0.6" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "devOptional": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/read-cmd-shim": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz", @@ -9035,7 +8470,7 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "devOptional": true, + "dev": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -9132,30 +8567,6 @@ "node": ">=4" } }, - "node_modules/registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/regjsgen": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", @@ -9531,36 +8942,24 @@ "semver": "bin/semver.js" } }, - "node_modules/semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "dependencies": { - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dependencies": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" @@ -9577,7 +8976,15 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } }, "node_modules/send/node_modules/ms": { "version": "2.1.3", @@ -9594,14 +9001,14 @@ } }, "node_modules/serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.18.0" }, "engines": { "node": ">= 0.8.0" @@ -9666,15 +9073,42 @@ "devOptional": true }, "node_modules/simple-git-hooks": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/simple-git-hooks/-/simple-git-hooks-2.7.0.tgz", - "integrity": "sha512-nQe6ASMO9zn5/htIrU37xEIHGr9E6wikXelLbOeTcfsX2O++DHaVug7RSQoq+kO7DvZTH37WA5gW49hN9HTDmQ==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/simple-git-hooks/-/simple-git-hooks-2.9.0.tgz", + "integrity": "sha512-waSQ5paUQtyGC0ZxlHmcMmD9I1rRXauikBwX31bX58l5vTOhCEcBC5Bi+ZDkPXTjDnZAF8TbCqKBY+9+sVPScw==", "dev": true, "hasInstallScript": true, "bin": { "simple-git-hooks": "cli.js" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/skypack": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/skypack/-/skypack-0.3.2.tgz", @@ -10172,11 +9606,11 @@ } }, "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/string_decoder": { @@ -10348,20 +9782,29 @@ } }, "node_modules/tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", "devOptional": true, "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", + "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" }, "engines": { - "node": ">= 10" + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "devOptional": true, + "engines": { + "node": ">=8" } }, "node_modules/tar/node_modules/mkdirp": { @@ -10431,15 +9874,6 @@ "node": ">=4" } }, - "node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -10678,18 +10112,6 @@ "imurmurhash": "^0.1.4" } }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -10710,119 +10132,6 @@ "node": ">=0.10.0" } }, - "node_modules/update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "dev": true, - "dependencies": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/update-notifier/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/update-notifier/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-notifier/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/update-notifier/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -10832,23 +10141,12 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/usb": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/usb/-/usb-1.9.2.tgz", "integrity": "sha512-dryNz030LWBPAf6gj8vyq0Iev3vPbCLHCT8dBw3gQRXRzVNsIdeuU+VjPp3ksmSPkeMAl1k+kQ14Ij0QHyeiAg==", "hasInstallScript": true, + "optional": true, "dependencies": { "node-addon-api": "^4.2.0", "node-gyp-build": "^4.3.0" @@ -10857,11 +10155,6 @@ "node": ">=10.16.0" } }, - "node_modules/usb/node_modules/node-addon-api": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", - "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" - }, "node_modules/utf-8-validate": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.8.tgz", @@ -10907,9 +10200,9 @@ } }, "node_modules/uvu": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.3.tgz", - "integrity": "sha512-brFwqA3FXzilmtnIyJ+CxdkInkY/i4ErvP7uV0DnUVxQcQ55reuHphorpF+tZoVHK2MniZ/VJzI7zJQoc9T9Yw==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", "dev": true, "dependencies": { "dequal": "^2.0.0", @@ -11189,15 +10482,15 @@ } }, "node_modules/ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -11208,19 +10501,10 @@ } } }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -11294,52 +10578,47 @@ }, "dependencies": { "@abandonware/bleno": { - "version": "0.5.1-4", - "resolved": "https://registry.npmjs.org/@abandonware/bleno/-/bleno-0.5.1-4.tgz", - "integrity": "sha512-2K/gbDxh4l4TV8xT/XUCwCT3e5aGDGmYad8gxt19CEvBMCs0+JScZ7roNyX0Jzice5rrR5RETcsMwIjJSzbeCQ==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@abandonware/bleno/-/bleno-0.6.1.tgz", + "integrity": "sha512-UYJhfOlRpg+o6H9VCL3xvBBchEV3mfGS+/ESmFd4cTy/HmoBTofFTecBMvV79/AZP5CSmQ9Qifx03kPBA4PgsQ==", "requires": { - "@abandonware/bluetooth-hci-socket": "^0.5.3-7", - "bplist-parser": "0.3.0", - "debug": "^4.3.1", + "@abandonware/bluetooth-hci-socket": "^0.5.3-10", + "bplist-parser": "0.3.2", + "debug": "^4.3.4", "napi-thread-safe-callback": "0.0.6", - "node-addon-api": "^3.1.0", + "node-addon-api": "^6.1.0", "xpc-connect": "npm:debug" + }, + "dependencies": { + "node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" + } } }, "@abandonware/bluetooth-hci-socket": { - "version": "0.5.3-7", - "resolved": "https://registry.npmjs.org/@abandonware/bluetooth-hci-socket/-/bluetooth-hci-socket-0.5.3-7.tgz", - "integrity": "sha512-CaGDBeXEooRjaVJlgmnaWeI+MXlEBVN9705tp2GHCF2IFARH3h15lqf6eHjqFsdpQOiMWiBa/QZUAOGjzBrhmA==", + "version": "0.5.3-10", + "resolved": "https://registry.npmjs.org/@abandonware/bluetooth-hci-socket/-/bluetooth-hci-socket-0.5.3-10.tgz", + "integrity": "sha512-wXHiGmHaHz1pUR6Ix6uut24uTYCWZJL68aP9F056jnnl+U3KrjLT77g9dZHWDAId+9yQN54lLO4MEOIV0z9fUg==", "optional": true, "requires": { - "debug": "^4.3.1", - "nan": "^2.14.2", - "node-pre-gyp": "^0.17.0", - "usb": "^1.6.3" + "@mapbox/node-pre-gyp": "^1.0.10", + "debug": "^4.3.4", + "nan": "^2.17.0", + "usb": "^1.9.2" } }, - "@abandonware/noble": { - "version": "1.9.2-15", - "resolved": "https://registry.npmjs.org/@abandonware/noble/-/noble-1.9.2-15.tgz", - "integrity": "sha512-qD9NN5fzvbtHdWYFPDzxY2AveILvDSRX/PTdL0V+CUfyF70ggIJtLBc1WW1hbVMIpu8rZylYgrK+PUEBwIpjCg==", - "requires": { - "@abandonware/bluetooth-hci-socket": "^0.5.3-8", - "debug": "^4.3.1", - "node-addon-api": "^3.2.0" - }, - "dependencies": { - "@abandonware/bluetooth-hci-socket": { - "version": "0.5.3-8", - "resolved": "https://registry.npmjs.org/@abandonware/bluetooth-hci-socket/-/bluetooth-hci-socket-0.5.3-8.tgz", - "integrity": "sha512-JIUkTZpAo6vKyXd94OasynjnmAxgCvn3VRrQJM/KXBKbm/yW59BMK6ni1wLy/JLM4eFhsLkd2S907HJnXBSWKw==", - "optional": true, - "requires": { - "@mapbox/node-pre-gyp": "^1.0.5", - "debug": "^4.3.2", - "nan": "^2.15.0", - "usb": "^1.7.2" - } - } + "@abandonware/noble": { + "version": "1.9.2-23", + "resolved": "https://registry.npmjs.org/@abandonware/noble/-/noble-1.9.2-23.tgz", + "integrity": "sha512-BPb/a2s+t6SIZRU4oNfY61cPM91/+dH0t8Ulb4QpQ1zBfKtR+n4r/r6j+vPcnAOiQ5hWC2lDj+Mc/iiZPAYLRw==", + "requires": { + "@abandonware/bluetooth-hci-socket": "^0.5.3-10", + "debug": "^4.3.4", + "napi-thread-safe-callback": "^0.0.6", + "node-addon-api": "^4.3.0", + "node-gyp-build": "^4.5.0" } }, "@ampproject/remapping": { @@ -12550,21 +11829,34 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, + "@lit-labs/ssr-dom-shim": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz", + "integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==" + }, "@lit/reactive-element": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.2.2.tgz", - "integrity": "sha512-HkhTTO2rT8jlf4izz7ME/+YUjqz+ZHgmnOKorA+7tkDmQDg6QzDpWSFz//1YyiL193W4bc7rlQCiYyFiZa9pkQ==" + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz", + "integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==", + "requires": { + "@lit-labs/ssr-dom-shim": "^1.0.0" + } }, "@mapbox/node-pre-gyp": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz", - "integrity": "sha512-CMGKi28CF+qlbXh26hDe6NxCd7amqeAzEqnS6IHeO6LoaKyM/n+Xw3HT1COdq8cuioOdlKdqn/hCmqPUOMOywg==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", "optional": true, "requires": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", "make-dir": "^3.1.0", - "node-fetch": "^2.6.5", + "node-fetch": "^2.6.7", "nopt": "^5.0.0", "npmlog": "^5.0.1", "rimraf": "^3.0.2", @@ -12582,6 +11874,12 @@ "readable-stream": "^3.6.0" } }, + "detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "optional": true + }, "gauge": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", @@ -13095,9 +12393,15 @@ } }, "@types/trusted-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", - "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + }, + "@types/w3c-web-usb": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.6.tgz", + "integrity": "sha512-cSjhgrr8g4KbPnnijAr/KJDNKa/bBa+ixYkywFRvrhvi9n1WEl7yYbtRyzE6jqNQiSxxJxoAW3STaOQwJHndaw==", + "optional": true }, "@web/parse5-utils": { "version": "1.3.0", @@ -13237,14 +12541,6 @@ "color-convert": "^1.9.0" } }, - "ant-plus": { - "version": "0.1.24", - "resolved": "https://registry.npmjs.org/ant-plus/-/ant-plus-0.1.24.tgz", - "integrity": "sha512-otEIAN+9jtu/1mwHL81au0sO7muJT1QrWOm9j29NEg3x1Y+ZsSIYX+LSdY+BBr2mLA4hT/vpqv5pWX9KDWceVg==", - "requires": { - "usb": "^1.6.0" - } - }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -13265,7 +12561,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", - "devOptional": true, + "dev": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -13552,9 +12848,9 @@ } }, "bplist-parser": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.0.tgz", - "integrity": "sha512-zgmaRvT6AN1JpPPV+S0a1/FAtoxSreYDccZGIqEMSvZl9DMe70mJ7MFzpxa1X+gHVdkToE2haRUHHMiW1OdejA==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.2.tgz", + "integrity": "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==", "optional": true, "requires": { "big-integer": "1.6.x" @@ -13761,6 +13057,20 @@ "integrity": "sha512-Y4kiDb+AM4Ecy58YkuZrrSRJBDQdQ2L+NyS1vHHFtNtUjgutcZfx3yp1dAONI/oPaPmyGfCLx5CxL+zauIMyKQ==", "dev": true }, + "chart.js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", + "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", + "requires": { + "@kurkle/color": "^0.3.0" + } + }, + "chartjs-plugin-datalabels": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz", + "integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==", + "requires": {} + }, "cheerio": { "version": "1.0.0-rc.10", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", @@ -13822,12 +13132,6 @@ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "devOptional": true }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, "cjs-module-lexer": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", @@ -13961,20 +13265,6 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "devOptional": true }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - } - }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -14012,7 +13302,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "devOptional": true + "dev": true }, "cosmiconfig": { "version": "7.0.1", @@ -14052,12 +13342,6 @@ "which": "^2.0.1" } }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true - }, "css-select": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.2.1.tgz", @@ -14092,10 +13376,18 @@ "assert-plus": "^1.0.0" } }, + "dbly-linked-list": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/dbly-linked-list/-/dbly-linked-list-0.3.4.tgz", + "integrity": "sha512-327vOlwspi9i1T3Kc9yZhRUR8qDdgMQ4HmXsFDDCQ/HTc3sNe7gnF5b0UrsnaOJ0rvmG7yBZpK0NoOux9rKYKw==", + "requires": { + "lodash.isequal": "^4.5.0" + } + }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { "ms": "2.1.2" } @@ -14123,12 +13415,6 @@ } } }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "devOptional": true - }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -14204,7 +13490,8 @@ "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true }, "dequal": { "version": "2.0.2", @@ -14213,15 +13500,9 @@ "dev": true }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "optional": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "detect-port": { "version": "1.3.0", @@ -14331,27 +13612,12 @@ "tslib": "^2.0.3" } }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "requires": { - "is-obj": "^2.0.0" - } - }, "duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -14425,15 +13691,6 @@ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true }, - "epoll": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/epoll/-/epoll-4.0.1.tgz", - "integrity": "sha512-BgCq0nEsk+XI7y9qjrRtt9uXsyFEdvevvq42xl6t/hKZjxLSDZreD9rTZ0pU40V//c3Zzk2PZGuIsn8YJHSJ4g==", - "requires": { - "bindings": "^1.5.0", - "nan": "^2.14.2" - } - }, "err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", @@ -14506,12 +13763,6 @@ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, - "escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -15136,16 +14387,16 @@ } }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "dependencies": { @@ -15225,7 +14476,7 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "fs-minipass": { "version": "2.1.0", @@ -15265,7 +14516,7 @@ "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "devOptional": true, + "dev": true, "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -15281,13 +14532,13 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "devOptional": true + "dev": true }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "devOptional": true, + "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -15368,23 +14619,6 @@ "is-glob": "^4.0.3" } }, - "global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dev": true, - "requires": { - "ini": "2.0.0" - }, - "dependencies": { - "ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true - } - } - }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -15505,12 +14739,6 @@ "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "devOptional": true }, - "has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true - }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -15557,15 +14785,22 @@ "dev": true }, "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "requires": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } } }, "http-proxy-agent": { @@ -15637,15 +14872,6 @@ "ms": "^2.0.0" } }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, "icss-replace-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", @@ -15675,7 +14901,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", - "devOptional": true, + "dev": true, "requires": { "minimatch": "^3.0.4" } @@ -15690,18 +14916,40 @@ "resolve-from": "^4.0.0" } }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true - }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "incyclist-ant-plus": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/incyclist-ant-plus/-/incyclist-ant-plus-0.3.1.tgz", + "integrity": "sha512-v/O7EQiwHQrtqUeh5rechAUfpZ5dl30Ty1stie4Y9XdcI93v1JKeNrHuyWZptatBsyYY1+rUTusrISICrqfFWg==", + "requires": { + "queue-fifo": "^0.2.6", + "usb": "^2.7.0" + }, + "dependencies": { + "node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "optional": true + }, + "usb": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/usb/-/usb-2.8.0.tgz", + "integrity": "sha512-umwfJjG3ADI+0xO+7pkrblX2+2BYDgzJTgWrSoxisXncsA2zW30VX2yly5W2U/gqldx6x2sn9b1Uk2gZht6JBQ==", + "optional": true, + "requires": { + "@types/w3c-web-usb": "^1.0.6", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.5.0" + } + } + } + }, "indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -15729,12 +14977,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "devOptional": true - }, "internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -15792,15 +15034,6 @@ "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", "dev": true }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, "is-core-module": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", @@ -15849,16 +15082,6 @@ "is-extglob": "^2.1.1" } }, - "is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "requires": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - } - }, "is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", @@ -15877,12 +15100,6 @@ "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true }, - "is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", - "dev": true - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -15898,18 +15115,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, "is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", @@ -16013,17 +15218,11 @@ "is-docker": "^2.0.0" } }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "devOptional": true + "dev": true }, "isbinaryfile": { "version": "4.0.8", @@ -16212,15 +15411,6 @@ "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", "dev": true }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "requires": { - "package-json": "^6.3.0" - } - }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -16247,28 +15437,29 @@ } }, "lit": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.1.3.tgz", - "integrity": "sha512-46KtKy7iDoY3wZ5VSqBlXll6J/tli5gRMPFRWi5qQ01lvIqcO+dYQwb1l1NYZjbzcHnGnCKrMb8nDv7/ZE4Y4g==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz", + "integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==", "requires": { - "@lit/reactive-element": "^1.1.0", - "lit-element": "^3.1.0", - "lit-html": "^2.1.0" + "@lit/reactive-element": "^1.6.0", + "lit-element": "^3.3.0", + "lit-html": "^2.8.0" } }, "lit-element": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.1.2.tgz", - "integrity": "sha512-5VLn5a7anAFH7oz6d7TRG3KiTZQ5GEFsAgOKB8Yc+HDyuDUGOT2cL1CYTz/U4b/xlJxO+euP14pyji+z3Z3kOg==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz", + "integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==", "requires": { - "@lit/reactive-element": "^1.1.0", - "lit-html": "^2.1.0" + "@lit-labs/ssr-dom-shim": "^1.1.0", + "@lit/reactive-element": "^1.3.0", + "lit-html": "^2.8.0" } }, "lit-html": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.1.3.tgz", - "integrity": "sha512-WgvdwiNeuoT0mYEEJI+AAV2DEtlqzVM4lyDSaeQSg5ZwhS/CkGJBO/4n66alApEuSS9WXw9+ADBp8SPvtDEKSg==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz", + "integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==", "requires": { "@types/trusted-types": "^2.0.2" } @@ -16310,7 +15501,13 @@ "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" }, "lodash.merge": { "version": "4.6.2", @@ -16319,9 +15516,9 @@ "dev": true }, "loglevel": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", - "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", + "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==" }, "lower-case": { "version": "2.0.2", @@ -16518,9 +15715,9 @@ "dev": true }, "minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "devOptional": true, "requires": { "brace-expansion": "^1.1.7" @@ -16530,7 +15727,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "devOptional": true + "dev": true }, "minipass": { "version": "3.1.6", @@ -16609,15 +15806,6 @@ "yallist": "^4.0.0" } }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - }, "mkdirp-infer-owner": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz", @@ -16649,9 +15837,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", + "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==" }, "nanoid": { "version": "3.2.0", @@ -16670,28 +15858,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "needle": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", - "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "optional": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -16715,9 +15881,9 @@ } }, "node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" }, "node-fetch": { "version": "2.6.7", @@ -16767,105 +15933,9 @@ } }, "node-gyp-build": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==" - }, - "node-pre-gyp": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.17.0.tgz", - "integrity": "sha512-abzZt1hmOjkZez29ppg+5gGqdPLUuJeAEwVPtHYEJgx0qzttCbcKFpxrCQn2HYbwCv2c+7JwH4BgEzFkUGpn4A==", - "optional": true, - "requires": { - "detect-libc": "^1.0.3", - "mkdirp": "^0.5.5", - "needle": "^2.5.2", - "nopt": "^4.0.3", - "npm-packlist": "^1.4.8", - "npmlog": "^4.1.2", - "rc": "^1.2.8", - "rimraf": "^2.7.1", - "semver": "^5.7.1", - "tar": "^4.4.13" - }, - "dependencies": { - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "optional": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "optional": true - }, - "tar": { - "version": "4.4.19", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", - "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", - "optional": true, - "requires": { - "chownr": "^1.1.4", - "fs-minipass": "^1.2.7", - "minipass": "^2.9.0", - "minizlib": "^1.3.3", - "mkdirp": "^0.5.5", - "safe-buffer": "^5.2.1", - "yallist": "^3.1.1" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "optional": true - } - } + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==" }, "node-releases": { "version": "2.0.2", @@ -16874,48 +15944,32 @@ "dev": true }, "nodemon": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", - "integrity": "sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.3.tgz", + "integrity": "sha512-7jH/NXbFPxVaMwmBCC2B9F/V6X1VkEdNgx3iu9jji8WxWcvhMWkmhNWhI5077zknOnZnBzba9hZP6bCPJLSReQ==", "dev": true, "requires": { "chokidar": "^3.5.2", - "debug": "^3.2.7", + "debug": "^4", "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "pstree.remy": "^1.1.8", - "semver": "^5.7.1", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", "supports-color": "^5.5.0", "touch": "^3.1.0", - "undefsafe": "^2.0.5", - "update-notifier": "^5.1.0" + "undefsafe": "^2.0.5" }, "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { - "ms": "^2.1.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "lru-cache": "^6.0.0" + } + } } }, "normalize-package-data": { @@ -16959,7 +16013,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", - "devOptional": true, + "dev": true, "requires": { "npm-normalize-package-bin": "^1.0.1" } @@ -16988,7 +16042,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "devOptional": true + "dev": true }, "npm-package-arg": { "version": "8.1.5", @@ -17021,17 +16075,6 @@ } } }, - "npm-packlist": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", - "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, "npm-pick-manifest": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz", @@ -17150,7 +16193,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "devOptional": true, + "dev": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -17221,9 +16264,9 @@ } }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "requires": { "ee-first": "1.1.1" } @@ -17246,15 +16289,6 @@ "mimic-fn": "^2.1.0" } }, - "onoff": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/onoff/-/onoff-6.0.3.tgz", - "integrity": "sha512-xtVlwRDzswYM69bzzIui/qzu7QHsFnjsQiCV1iYVA/HXt5xdc9utc97SYAlXzK8wAhIN7+H7MaVqh2vpfdKk9A==", - "requires": { - "epoll": "^4.0.1", - "lodash.debounce": "^4.0.8" - } - }, "open": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", @@ -17284,23 +16318,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "devOptional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "optional": true - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } + "dev": true }, "p-cancelable": { "version": "2.1.1", @@ -17366,156 +16384,6 @@ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, - "package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "requires": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "dependencies": { - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "requires": { - "defer-to-connect": "^1.0.1" - } - }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - } - } - }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "dependencies": { - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - } - } - }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true - }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true - }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "requires": { - "lowercase-keys": "^1.0.0" - }, - "dependencies": { - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - } - } - } - } - }, "pacote": { "version": "11.3.5", "resolved": "https://registry.npmjs.org/pacote/-/pacote-11.3.5.tgz", @@ -17703,6 +16571,15 @@ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, + "pigpio": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/pigpio/-/pigpio-3.3.1.tgz", + "integrity": "sha512-z7J55K14IwWkA+oW5JHzWcgwThFAuJ7IzV3A2//yRm4jJ2DTU0DHIy91DB0siOi12rvvlrIhRetEuAo0ztF/vQ==", + "requires": { + "bindings": "^1.5.0", + "nan": "^2.14.2" + } + }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -17848,12 +16725,6 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true - }, "printable-characters": { "version": "1.0.42", "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", @@ -17870,7 +16741,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "devOptional": true + "dev": true }, "promise-all-reject-late": { "version": "1.0.1", @@ -17928,21 +16799,20 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, - "requires": { - "escape-goat": "^2.0.0" - } - }, "qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", "dev": true }, + "queue-fifo": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/queue-fifo/-/queue-fifo-0.2.6.tgz", + "integrity": "sha512-rwlnZHAaTmWEGKC7ziasK8u4QnZW/uN6kSiG+tHNf/1GA+R32FArZi18s3SYUpKcA0Y6jJoUDn5GT3Anoc2mWw==", + "requires": { + "dbly-linked-list": "0.3.4" + } + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -17969,26 +16839,6 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "devOptional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "devOptional": true - } - } - }, "read-cmd-shim": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz", @@ -18031,7 +16881,7 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "devOptional": true, + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -18113,24 +16963,6 @@ "unicode-match-property-value-ecmascript": "^2.0.0" } }, - "registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, "regjsgen": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", @@ -18409,33 +17241,24 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "devOptional": true }, - "semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "requires": { - "semver": "^6.3.0" - } - }, "send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { "debug": { @@ -18449,10 +17272,15 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -18470,14 +17298,14 @@ } }, "serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.18.0" } }, "set-blocking": { @@ -18530,11 +17358,31 @@ "devOptional": true }, "simple-git-hooks": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/simple-git-hooks/-/simple-git-hooks-2.7.0.tgz", - "integrity": "sha512-nQe6ASMO9zn5/htIrU37xEIHGr9E6wikXelLbOeTcfsX2O++DHaVug7RSQoq+kO7DvZTH37WA5gW49hN9HTDmQ==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/simple-git-hooks/-/simple-git-hooks-2.9.0.tgz", + "integrity": "sha512-waSQ5paUQtyGC0ZxlHmcMmD9I1rRXauikBwX31bX58l5vTOhCEcBC5Bi+ZDkPXTjDnZAF8TbCqKBY+9+sVPScw==", "dev": true }, + "simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "requires": { + "semver": "^7.5.3" + }, + "dependencies": { + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, "skypack": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/skypack/-/skypack-0.3.2.tgz", @@ -18888,9 +17736,9 @@ } }, "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, "string_decoder": { "version": "1.1.1", @@ -19015,19 +17863,25 @@ "dev": true }, "tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", "devOptional": true, "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", + "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" }, "dependencies": { + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "devOptional": true + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -19073,12 +17927,6 @@ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -19273,15 +18121,6 @@ "imurmurhash": "^0.1.4" } }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "requires": { - "crypto-random-string": "^2.0.0" - } - }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -19296,88 +18135,6 @@ "os-homedir": "^1.0.0" } }, - "update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "dev": true, - "requires": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -19387,29 +18144,14 @@ "punycode": "^2.1.0" } }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "requires": { - "prepend-http": "^2.0.0" - } - }, "usb": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/usb/-/usb-1.9.2.tgz", "integrity": "sha512-dryNz030LWBPAf6gj8vyq0Iev3vPbCLHCT8dBw3gQRXRzVNsIdeuU+VjPp3ksmSPkeMAl1k+kQ14Ij0QHyeiAg==", + "optional": true, "requires": { "node-addon-api": "^4.2.0", "node-gyp-build": "^4.3.0" - }, - "dependencies": { - "node-addon-api": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", - "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" - } } }, "utf-8-validate": { @@ -19451,9 +18193,9 @@ "dev": true }, "uvu": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.3.tgz", - "integrity": "sha512-brFwqA3FXzilmtnIyJ+CxdkInkY/i4ErvP7uV0DnUVxQcQ55reuHphorpF+tZoVHK2MniZ/VJzI7zJQoc9T9Yw==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", "dev": true, "requires": { "dequal": "^2.0.0", @@ -19682,21 +18424,15 @@ } }, "ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "requires": {} }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true - }, "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", "requires": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" diff --git a/package.json b/package.json index cd8c906ac7..ca2c29e175 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "openrowingmonitor", - "version": "0.8.2", + "version": "0.9.0", "description": "A free and open source performance monitor for rowing machines", "main": "app/server.js", - "author": "Lars Berning", + "author": "Jaap van Ekris", "license": "GPL-3.0", "repository": { "type": "git", - "url": "https://github.com/laberning/openrowingmonitor.git" + "url": "https://github.com/JaapvanEkris/openrowingmonitor.git" }, "type": "module", "engines": { @@ -31,22 +31,23 @@ "pre-commit": "npm run lint && npm test" }, "dependencies": { - "@abandonware/bleno": "0.5.1-4", - "@abandonware/noble": "1.9.2-15", - "ant-plus": "0.1.24", - "finalhandler": "1.1.2", + "@abandonware/bleno": "^0.6.1", + "@abandonware/noble": "^1.9.2-23", + "chart.js": "^4.4.1", + "chartjs-plugin-datalabels": "^2.2.0", + "finalhandler": "^1.2.0", "form-data": "4.0.0", - "lit": "2.1.3", - "loglevel": "1.8.0", + "incyclist-ant-plus": "^0.3.1", + "lit": "^2.8.0", + "loglevel": "^1.9.1", "nosleep.js": "0.12.0", - "onoff": "6.0.3", - "serve-static": "1.14.2", - "ws": "8.5.0", - "xml2js": "0.4.23" + "pigpio": "3.3.1", + "serve-static": "^1.15.0", + "ws": "^8.16.0", + "xml2js": "^0.6.2" }, - "//fix1Comment": "version 0.5.3-8 currently does not work with bleno", "optionalDependencies": { - "@abandonware/bluetooth-hci-socket": "0.5.3-7" + "@abandonware/bluetooth-hci-socket": "^0.5.3-10" }, "//fix2Comment": "a hacky fix to not install the optional dependency xpc-connect which has a security issue", "overrides": { @@ -73,14 +74,14 @@ "eslint-plugin-wc": "1.3.2", "http2-proxy": "5.0.53", "markdownlint-cli2": "0.4.0", - "nodemon": "2.0.15", + "nodemon": "^3.0.3", "npm-run-all": "4.1.5", "rollup": "2.67.2", "rollup-plugin-summary": "1.3.0", "rollup-plugin-terser": "7.0.2", - "simple-git-hooks": "2.7.0", + "simple-git-hooks": "^2.9.0", "snowpack": "3.8.8", - "tar": "6.1.11", - "uvu": "0.5.3" + "tar": "^6.2.0", + "uvu": "^0.5.6" } } diff --git a/recordings/Concept2_RowErg_Session_2000meters.csv b/recordings/Concept2_RowErg_Session_2000meters.csv new file mode 100644 index 0000000000..65ee4db3cc --- /dev/null +++ b/recordings/Concept2_RowErg_Session_2000meters.csv @@ -0,0 +1,63000 @@ +0.080654 +0.071426 +0.058965 +0.050558 +0.044207 +0.039636 +0.036263 +0.033684 +0.031432 +0.029379 +0.02732 +0.025791 +0.024678 +0.023824 +0.023014 +0.02225 +0.021168 +0.020272 +0.019798 +0.019262 +0.018968 +0.018483 +0.017651 +0.017126 +0.016796 +0.016591 +0.016371 +0.016203 +0.015614 +0.014994 +0.014842 +0.014784 +0.014693 +0.014631 +0.014228 +0.01376 +0.013578 +0.013772 +0.0137 +0.013413 +0.013058 +0.012863 +0.012946 +0.013001 +0.012801 +0.012791 +0.012484 +0.012308 +0.012315 +0.012352 +0.012402 +0.012389 +0.012078 +0.011917 +0.012071 +0.012101 +0.012058 +0.011934 +0.011807 +0.01167 +0.011628 +0.011647 +0.01175 +0.011807 +0.011684 +0.011561 +0.011508 +0.011647 +0.011778 +0.011875 +0.011648 +0.01155 +0.011624 +0.011752 +0.011875 +0.011957 +0.011872 +0.01168 +0.011617 +0.011781 +0.011981 +0.012034 +0.011695 +0.011626 +0.011754 +0.011869 +0.012014 +0.012084 +0.011848 +0.01175 +0.011846 +0.011932 +0.012037 +0.012141 +0.011916 +0.011829 +0.011917 +0.012069 +0.012128 +0.012154 +0.011962 +0.011885 +0.011943 +0.012052 +0.012183 +0.012265 +0.012061 +0.011952 +0.011994 +0.012113 +0.012247 +0.012336 +0.012088 +0.012018 +0.012072 +0.012225 +0.012362 +0.012418 +0.012139 +0.012031 +0.012128 +0.012258 +0.012389 +0.012465 +0.012274 +0.01213 +0.0122 +0.012302 +0.012446 +0.012521 +0.012293 +0.012216 +0.012254 +0.012386 +0.012533 +0.012597 +0.012361 +0.01229 +0.012382 +0.012416 +0.012591 +0.012653 +0.012416 +0.012314 +0.012397 +0.01253 +0.01267 +0.012723 +0.012495 +0.012419 +0.012446 +0.01259 +0.012718 +0.012779 +0.012575 +0.012465 +0.012551 +0.012696 +0.012821 +0.012827 +0.012635 +0.012526 +0.012576 +0.012719 +0.012855 +0.012935 +0.012715 +0.012598 +0.012661 +0.012791 +0.012928 +0.013012 +0.012739 +0.012667 +0.01275 +0.012905 +0.013004 +0.013133 +0.012793 +0.012692 +0.01278 +0.012973 +0.013062 +0.013163 +0.01289 +0.012781 +0.012865 +0.013 +0.013168 +0.013204 +0.012975 +0.012845 +0.012974 +0.013076 +0.013206 +0.01329 +0.013086 +0.012902 +0.012988 +0.013132 +0.013322 +0.013355 +0.013109 +0.012974 +0.013077 +0.01319 +0.013297 +0.013374 +0.013086 +0.012917 +0.012873 +0.012909 +0.012979 +0.01291 +0.012574 +0.012383 +0.012342 +0.012401 +0.012345 +0.012258 +0.011953 +0.011771 +0.011726 +0.011766 +0.011754 +0.011706 +0.011429 +0.011237 +0.011194 +0.011221 +0.011213 +0.011203 +0.01093 +0.010752 +0.010725 +0.010751 +0.010784 +0.010739 +0.01049 +0.01034 +0.010328 +0.010324 +0.010345 +0.010352 +0.010134 +0.009979 +0.009995 +0.010058 +0.010089 +0.010066 +0.009867 +0.00974 +0.009765 +0.009808 +0.009859 +0.009888 +0.009687 +0.009573 +0.009613 +0.00963 +0.009699 +0.009723 +0.00952 +0.009424 +0.009443 +0.009503 +0.009554 +0.009554 +0.009371 +0.009276 +0.00933 +0.009359 +0.009439 +0.009501 +0.009326 +0.009249 +0.009308 +0.009373 +0.009472 +0.009538 +0.009389 +0.009305 +0.009352 +0.009444 +0.00952 +0.009593 +0.00943 +0.009341 +0.009392 +0.009482 +0.009579 +0.009674 +0.009488 +0.009392 +0.009429 +0.009551 +0.009624 +0.009676 +0.009519 +0.009436 +0.009496 +0.0096 +0.009675 +0.009745 +0.009588 +0.009492 +0.009556 +0.009621 +0.009728 +0.009795 +0.00962 +0.009535 +0.009598 +0.009681 +0.009778 +0.009859 +0.009696 +0.009635 +0.009664 +0.009722 +0.009821 +0.009887 +0.009724 +0.009658 +0.009686 +0.009775 +0.009881 +0.009961 +0.009788 +0.009699 +0.009741 +0.00983 +0.009935 +0.010003 +0.009834 +0.009744 +0.009793 +0.009888 +0.010018 +0.010062 +0.009885 +0.009801 +0.009864 +0.009956 +0.010024 +0.010093 +0.009923 +0.009836 +0.009912 +0.010013 +0.010135 +0.010158 +0.009963 +0.009899 +0.009958 +0.01002 +0.01015 +0.010223 +0.010032 +0.009963 +0.010032 +0.010113 +0.01021 +0.010256 +0.010074 +0.009996 +0.010084 +0.010172 +0.010291 +0.010296 +0.010132 +0.010062 +0.01013 +0.010209 +0.010294 +0.010379 +0.010197 +0.010107 +0.010167 +0.010241 +0.010364 +0.010455 +0.010262 +0.010173 +0.01023 +0.010315 +0.010414 +0.01048 +0.010293 +0.01021 +0.010273 +0.010384 +0.010539 +0.010543 +0.010375 +0.010249 +0.010317 +0.010414 +0.010516 +0.010595 +0.010405 +0.010313 +0.010404 +0.01051 +0.010587 +0.010666 +0.01046 +0.010364 +0.010439 +0.010528 +0.010637 +0.010714 +0.010529 +0.010451 +0.010526 +0.010621 +0.010717 +0.010737 +0.010555 +0.010478 +0.01058 +0.010616 +0.010751 +0.010855 +0.010647 +0.010551 +0.010605 +0.010679 +0.010801 +0.010893 +0.010693 +0.010586 +0.010677 +0.010791 +0.010886 +0.010952 +0.010734 +0.010644 +0.010711 +0.010858 +0.010923 +0.010977 +0.010793 +0.010718 +0.010777 +0.010904 +0.010977 +0.011046 +0.01086 +0.010772 +0.010832 +0.01094 +0.011059 +0.011113 +0.010915 +0.010837 +0.010894 +0.010983 +0.011113 +0.011171 +0.010996 +0.010914 +0.010977 +0.011034 +0.011145 +0.011219 +0.01103 +0.010931 +0.011006 +0.011118 +0.011258 +0.011281 +0.011085 +0.01099 +0.011064 +0.011175 +0.011252 +0.011325 +0.011113 +0.010976 +0.011044 +0.011085 +0.01112 +0.011133 +0.010887 +0.010778 +0.010785 +0.010779 +0.010842 +0.010826 +0.010606 +0.010437 +0.01045 +0.010487 +0.010524 +0.010534 +0.010291 +0.010145 +0.010149 +0.0102 +0.010243 +0.010251 +0.009992 +0.00987 +0.009854 +0.009891 +0.009924 +0.00992 +0.009703 +0.009572 +0.009588 +0.009585 +0.009641 +0.009615 +0.009415 +0.009289 +0.009305 +0.009335 +0.009394 +0.009386 +0.009211 +0.009097 +0.009115 +0.009167 +0.009219 +0.009228 +0.009047 +0.008935 +0.008973 +0.009006 +0.009066 +0.009097 +0.00893 +0.008812 +0.008844 +0.008908 +0.008973 +0.009008 +0.008811 +0.008715 +0.00876 +0.008816 +0.008925 +0.008931 +0.008789 +0.008716 +0.008771 +0.008831 +0.00894 +0.008999 +0.008843 +0.00875 +0.00881 +0.00889 +0.008971 +0.009032 +0.008886 +0.008806 +0.008862 +0.008934 +0.009037 +0.009087 +0.008934 +0.008852 +0.008912 +0.00901 +0.009061 +0.009127 +0.008968 +0.008896 +0.008955 +0.009031 +0.009149 +0.009207 +0.009003 +0.008934 +0.008989 +0.009079 +0.009154 +0.009224 +0.009067 +0.008997 +0.009053 +0.009121 +0.009225 +0.009275 +0.009111 +0.00904 +0.00909 +0.009168 +0.009267 +0.009332 +0.00919 +0.009095 +0.009136 +0.009204 +0.00931 +0.009386 +0.009207 +0.009129 +0.009183 +0.009272 +0.009348 +0.009419 +0.009279 +0.00918 +0.009222 +0.009307 +0.009419 +0.009486 +0.009313 +0.00924 +0.00929 +0.009362 +0.009462 +0.009514 +0.009357 +0.009279 +0.009342 +0.009447 +0.009527 +0.009593 +0.009382 +0.009314 +0.009385 +0.009453 +0.009551 +0.009619 +0.009458 +0.009372 +0.009446 +0.009535 +0.009616 +0.009658 +0.009507 +0.009416 +0.009479 +0.009568 +0.009651 +0.009722 +0.009561 +0.009488 +0.009537 +0.009622 +0.009706 +0.009791 +0.009619 +0.009514 +0.009569 +0.009684 +0.009753 +0.00982 +0.009658 +0.009583 +0.009644 +0.009704 +0.009809 +0.009877 +0.009712 +0.00961 +0.009677 +0.009753 +0.009868 +0.009946 +0.009769 +0.009683 +0.009738 +0.009805 +0.009912 +0.009988 +0.009811 +0.009758 +0.009781 +0.009866 +0.009958 +0.010046 +0.009873 +0.009794 +0.009814 +0.009922 +0.010007 +0.01009 +0.009915 +0.009825 +0.009892 +0.009972 +0.010102 +0.010148 +0.009963 +0.009861 +0.009933 +0.010024 +0.010129 +0.010194 +0.010015 +0.009948 +0.010027 +0.010093 +0.010181 +0.010233 +0.010056 +0.009983 +0.010034 +0.010131 +0.010245 +0.010285 +0.010131 +0.010048 +0.010103 +0.010189 +0.010305 +0.010334 +0.010174 +0.010119 +0.01015 +0.010229 +0.010334 +0.010403 +0.010244 +0.01016 +0.010224 +0.010347 +0.010384 +0.010437 +0.010264 +0.010192 +0.010241 +0.010324 +0.010452 +0.010542 +0.010354 +0.010265 +0.010314 +0.010385 +0.010499 +0.010567 +0.010389 +0.010298 +0.010355 +0.010472 +0.010581 +0.010649 +0.010447 +0.010343 +0.010414 +0.010551 +0.010599 +0.010678 +0.01049 +0.010403 +0.010484 +0.010578 +0.010687 +0.010731 +0.010536 +0.010462 +0.010522 +0.010613 +0.010732 +0.010796 +0.010598 +0.010523 +0.010575 +0.010669 +0.010798 +0.010837 +0.01068 +0.010576 +0.010667 +0.010775 +0.010838 +0.010863 +0.010691 +0.010611 +0.010638 +0.010726 +0.010777 +0.010796 +0.010579 +0.010485 +0.010496 +0.010534 +0.010574 +0.010556 +0.010338 +0.010196 +0.01021 +0.010267 +0.010292 +0.010302 +0.010098 +0.009932 +0.009938 +0.009996 +0.010037 +0.010056 +0.009787 +0.009653 +0.009686 +0.00976 +0.009743 +0.009763 +0.009533 +0.009386 +0.009409 +0.009442 +0.00948 +0.009464 +0.009263 +0.00915 +0.009164 +0.009204 +0.009248 +0.009271 +0.009052 +0.008936 +0.008981 +0.009004 +0.009054 +0.009072 +0.008886 +0.008797 +0.008825 +0.008867 +0.008911 +0.008927 +0.00875 +0.008646 +0.008685 +0.008743 +0.008787 +0.008804 +0.008641 +0.008544 +0.008602 +0.008658 +0.008725 +0.008764 +0.008603 +0.008517 +0.008569 +0.008649 +0.008707 +0.00878 +0.008642 +0.008564 +0.0086 +0.00868 +0.008791 +0.008846 +0.008707 +0.008609 +0.008644 +0.008719 +0.008804 +0.008873 +0.008726 +0.008643 +0.008692 +0.008783 +0.00889 +0.008922 +0.008774 +0.008701 +0.00874 +0.008817 +0.00891 +0.008971 +0.008814 +0.008726 +0.008792 +0.008873 +0.00896 +0.009023 +0.008852 +0.00878 +0.008835 +0.008921 +0.009026 +0.009066 +0.008934 +0.008816 +0.008878 +0.008965 +0.009063 +0.009103 +0.008941 +0.008877 +0.008938 +0.009003 +0.009095 +0.009152 +0.008992 +0.008919 +0.008983 +0.009058 +0.009152 +0.009221 +0.009039 +0.008961 +0.00903 +0.009119 +0.009181 +0.009251 +0.009097 +0.009017 +0.009106 +0.009158 +0.009242 +0.009299 +0.009148 +0.009057 +0.009106 +0.009193 +0.00928 +0.009353 +0.009192 +0.009113 +0.009177 +0.009257 +0.009346 +0.009405 +0.009228 +0.009153 +0.00921 +0.009298 +0.009379 +0.009442 +0.009308 +0.009206 +0.009263 +0.009363 +0.009454 +0.009528 +0.009324 +0.009244 +0.009299 +0.009392 +0.009487 +0.009566 +0.009371 +0.009297 +0.009363 +0.009458 +0.009548 +0.009596 +0.009423 +0.009345 +0.009416 +0.009492 +0.009577 +0.009649 +0.009479 +0.009398 +0.009469 +0.00957 +0.009635 +0.009694 +0.009538 +0.009466 +0.009517 +0.009582 +0.009666 +0.009751 +0.009596 +0.009506 +0.009563 +0.009684 +0.009719 +0.009787 +0.00962 +0.009547 +0.009583 +0.009677 +0.009789 +0.009864 +0.009705 +0.009616 +0.009678 +0.009733 +0.009838 +0.009901 +0.009724 +0.009641 +0.009709 +0.009815 +0.009911 +0.009944 +0.009769 +0.009694 +0.009762 +0.009844 +0.00994 +0.010018 +0.009833 +0.009767 +0.009798 +0.009894 +0.009992 +0.010064 +0.00989 +0.009791 +0.009869 +0.009958 +0.010042 +0.010123 +0.009969 +0.009845 +0.009902 +0.010005 +0.010119 +0.010188 +0.009975 +0.009897 +0.009958 +0.010055 +0.010161 +0.010225 +0.010034 +0.009945 +0.010016 +0.010106 +0.01023 +0.010255 +0.010101 +0.010012 +0.010071 +0.010163 +0.010275 +0.010319 +0.010146 +0.010063 +0.010117 +0.010201 +0.010313 +0.010428 +0.010213 +0.010099 +0.010188 +0.010262 +0.010386 +0.010431 +0.010241 +0.010163 +0.010212 +0.010309 +0.010435 +0.01049 +0.010315 +0.010244 +0.010267 +0.010361 +0.010478 +0.010542 +0.010363 +0.010281 +0.010327 +0.010432 +0.010543 +0.010648 +0.01042 +0.010315 +0.010379 +0.010467 +0.010588 +0.010655 +0.010469 +0.010377 +0.010429 +0.010536 +0.010677 +0.010683 +0.010498 +0.010403 +0.010456 +0.010526 +0.010603 +0.010609 +0.010377 +0.010251 +0.010299 +0.01033 +0.010379 +0.010389 +0.010181 +0.010017 +0.010012 +0.010067 +0.01011 +0.010127 +0.009895 +0.00976 +0.009793 +0.009795 +0.009837 +0.009848 +0.009614 +0.009486 +0.009514 +0.00955 +0.009595 +0.009597 +0.009379 +0.009234 +0.009257 +0.009298 +0.009324 +0.009327 +0.009129 +0.009007 +0.009055 +0.009082 +0.009128 +0.009128 +0.00894 +0.008826 +0.008846 +0.008899 +0.008948 +0.008968 +0.008784 +0.008692 +0.008709 +0.008755 +0.008807 +0.008844 +0.008666 +0.008558 +0.00858 +0.008643 +0.008696 +0.00872 +0.00855 +0.008468 +0.008495 +0.008566 +0.00863 +0.008684 +0.008535 +0.008465 +0.00849 +0.008557 +0.008659 +0.008693 +0.008555 +0.008466 +0.008521 +0.008592 +0.008702 +0.008757 +0.008604 +0.008532 +0.008587 +0.008642 +0.00872 +0.00879 +0.008637 +0.008563 +0.008618 +0.008694 +0.00877 +0.008863 +0.008708 +0.008618 +0.008669 +0.008742 +0.008814 +0.008902 +0.008747 +0.008654 +0.008696 +0.008775 +0.008853 +0.008966 +0.008774 +0.008703 +0.008761 +0.008831 +0.008904 +0.008963 +0.008824 +0.008739 +0.008799 +0.00888 +0.008953 +0.00902 +0.008875 +0.008793 +0.008843 +0.008926 +0.00901 +0.009076 +0.008917 +0.008829 +0.008898 +0.00898 +0.009089 +0.009118 +0.008963 +0.00887 +0.008932 +0.009022 +0.00909 +0.009185 +0.009002 +0.008919 +0.008979 +0.009063 +0.009161 +0.009221 +0.009062 +0.008976 +0.009024 +0.009112 +0.009191 +0.009265 +0.009107 +0.009022 +0.009084 +0.009154 +0.009252 +0.00932 +0.00917 +0.009092 +0.009123 +0.009193 +0.009287 +0.009349 +0.009195 +0.009106 +0.009171 +0.009252 +0.009369 +0.009436 +0.009246 +0.00916 +0.00921 +0.009291 +0.009388 +0.009458 +0.009281 +0.0092 +0.009273 +0.009356 +0.009473 +0.009528 +0.009351 +0.009242 +0.00931 +0.009403 +0.009521 +0.009552 +0.009399 +0.009289 +0.009359 +0.009462 +0.009546 +0.009612 +0.009447 +0.009351 +0.0094 +0.009496 +0.009588 +0.009643 +0.009484 +0.009404 +0.00946 +0.009563 +0.009666 +0.009713 +0.009542 +0.009447 +0.009507 +0.009586 +0.009688 +0.009763 +0.009608 +0.009524 +0.009557 +0.00965 +0.009779 +0.009806 +0.009613 +0.009534 +0.009605 +0.009691 +0.009789 +0.009852 +0.009689 +0.00961 +0.00968 +0.009771 +0.009849 +0.009899 +0.009727 +0.009631 +0.009718 +0.009806 +0.009893 +0.009957 +0.009795 +0.009725 +0.009819 +0.009851 +0.009948 +0.009991 +0.009822 +0.009751 +0.009831 +0.009893 +0.010013 +0.010054 +0.009884 +0.009811 +0.009855 +0.009945 +0.010054 +0.010121 +0.009939 +0.009859 +0.009922 +0.01002 +0.010103 +0.010173 +0.01 +0.009913 +0.009973 +0.010085 +0.01016 +0.010206 +0.010041 +0.009956 +0.010039 +0.010109 +0.010205 +0.010308 +0.010089 +0.010004 +0.010068 +0.010156 +0.010263 +0.010347 +0.010137 +0.010071 +0.010124 +0.010207 +0.010327 +0.010402 +0.010202 +0.010116 +0.010185 +0.010285 +0.01043 +0.010431 +0.010241 +0.010156 +0.010229 +0.010317 +0.010424 +0.010499 +0.010316 +0.010221 +0.010284 +0.010374 +0.010491 +0.010547 +0.010364 +0.010272 +0.010344 +0.010435 +0.010536 +0.010599 +0.010391 +0.010304 +0.010358 +0.010449 +0.010522 +0.010491 +0.010287 +0.010154 +0.010182 +0.010242 +0.010303 +0.010312 +0.010068 +0.009961 +0.009961 +0.010013 +0.010041 +0.010038 +0.009814 +0.009691 +0.00971 +0.009749 +0.009772 +0.009798 +0.009577 +0.00945 +0.009477 +0.009501 +0.009531 +0.00955 +0.009342 +0.009233 +0.009223 +0.009261 +0.009322 +0.009296 +0.009116 +0.008987 +0.009019 +0.009067 +0.009099 +0.009115 +0.008926 +0.008824 +0.008857 +0.008898 +0.008945 +0.008961 +0.008785 +0.008678 +0.008723 +0.008766 +0.008815 +0.008839 +0.00867 +0.00856 +0.008606 +0.008665 +0.008716 +0.008762 +0.008564 +0.008491 +0.008519 +0.00859 +0.008655 +0.008705 +0.008534 +0.008462 +0.008524 +0.008593 +0.008671 +0.008746 +0.008592 +0.008503 +0.008561 +0.008647 +0.008721 +0.008781 +0.008632 +0.008553 +0.008599 +0.008679 +0.00877 +0.008835 +0.00868 +0.008616 +0.008668 +0.008743 +0.008828 +0.008857 +0.00871 +0.008639 +0.008694 +0.008774 +0.008856 +0.008921 +0.008779 +0.008694 +0.008747 +0.008821 +0.008902 +0.008951 +0.008804 +0.008731 +0.008793 +0.008859 +0.008937 +0.009016 +0.00887 +0.008787 +0.008846 +0.008928 +0.009002 +0.009041 +0.008902 +0.008825 +0.008931 +0.008941 +0.009032 +0.009084 +0.008944 +0.00887 +0.008934 +0.009009 +0.009096 +0.009146 +0.009002 +0.00891 +0.008978 +0.009046 +0.009132 +0.009195 +0.009047 +0.008965 +0.009032 +0.009114 +0.00919 +0.009239 +0.009079 +0.009006 +0.009065 +0.009143 +0.009238 +0.009311 +0.009157 +0.009049 +0.00914 +0.009199 +0.009283 +0.009349 +0.009163 +0.009092 +0.009157 +0.00923 +0.009326 +0.009398 +0.009231 +0.009153 +0.009223 +0.009311 +0.009381 +0.009438 +0.009278 +0.009195 +0.009246 +0.009336 +0.009426 +0.009486 +0.00933 +0.009262 +0.009347 +0.009428 +0.009473 +0.009513 +0.009356 +0.009308 +0.009342 +0.009423 +0.009518 +0.009589 +0.009418 +0.009357 +0.009432 +0.009494 +0.009585 +0.009624 +0.009464 +0.009383 +0.009443 +0.009546 +0.009616 +0.009688 +0.009536 +0.00945 +0.009507 +0.009585 +0.009683 +0.009767 +0.009564 +0.009483 +0.009548 +0.009624 +0.009749 +0.009814 +0.009614 +0.009547 +0.009593 +0.009677 +0.009774 +0.009839 +0.009677 +0.00959 +0.009651 +0.009743 +0.009833 +0.009917 +0.009722 +0.009643 +0.009703 +0.009779 +0.009874 +0.009952 +0.009798 +0.009716 +0.009743 +0.009838 +0.009927 +0.009998 +0.009832 +0.009739 +0.00979 +0.009908 +0.009977 +0.010046 +0.009881 +0.009792 +0.009854 +0.009951 +0.010036 +0.010104 +0.009946 +0.009841 +0.009889 +0.009988 +0.010077 +0.010165 +0.009992 +0.009913 +0.009992 +0.010067 +0.010125 +0.0102 +0.010022 +0.009936 +0.010003 +0.010097 +0.010205 +0.01027 +0.010083 +0.009998 +0.010053 +0.010151 +0.010256 +0.010312 +0.010148 +0.010059 +0.010121 +0.010201 +0.01032 +0.010361 +0.010187 +0.010104 +0.010193 +0.01027 +0.010342 +0.010431 +0.010246 +0.010162 +0.010228 +0.010306 +0.010403 +0.010474 +0.010295 +0.010212 +0.010279 +0.010354 +0.010476 +0.010533 +0.010368 +0.010283 +0.010326 +0.010403 +0.010516 +0.010576 +0.010405 +0.010316 +0.010396 +0.010485 +0.010546 +0.010615 +0.010406 +0.010308 +0.010316 +0.01037 +0.010431 +0.010454 +0.010248 +0.010113 +0.010143 +0.010186 +0.010242 +0.010225 +0.00999 +0.009849 +0.009869 +0.009902 +0.009943 +0.009943 +0.009747 +0.009622 +0.009616 +0.009659 +0.009696 +0.009718 +0.009471 +0.009345 +0.009354 +0.00939 +0.009444 +0.009463 +0.009255 +0.009142 +0.009135 +0.009173 +0.009213 +0.00922 +0.009027 +0.008915 +0.008941 +0.00898 +0.009016 +0.009043 +0.008881 +0.008766 +0.008787 +0.008835 +0.008874 +0.008902 +0.008739 +0.008641 +0.008679 +0.008723 +0.00876 +0.008789 +0.008631 +0.008545 +0.008576 +0.008618 +0.008683 +0.008728 +0.00855 +0.008468 +0.008509 +0.00858 +0.008646 +0.008713 +0.008569 +0.008509 +0.008562 +0.008635 +0.008721 +0.00876 +0.008607 +0.008525 +0.008587 +0.008653 +0.00874 +0.008803 +0.008657 +0.008597 +0.008667 +0.008712 +0.008787 +0.008845 +0.0087 +0.008621 +0.008669 +0.008745 +0.008838 +0.008898 +0.008775 +0.008657 +0.008728 +0.008801 +0.008894 +0.008946 +0.008798 +0.00871 +0.008766 +0.008845 +0.00892 +0.008981 +0.008832 +0.008755 +0.00881 +0.008885 +0.008984 +0.009051 +0.008911 +0.008801 +0.008861 +0.008938 +0.009011 +0.009086 +0.008923 +0.008847 +0.008905 +0.008998 +0.009059 +0.009132 +0.008985 +0.00889 +0.008955 +0.009027 +0.009115 +0.009183 +0.00903 +0.008941 +0.008995 +0.009085 +0.009162 +0.009231 +0.009071 +0.008986 +0.009052 +0.009146 +0.00923 +0.009266 +0.009107 +0.00903 +0.009108 +0.009191 +0.009245 +0.009318 +0.009168 +0.009071 +0.009142 +0.009224 +0.009298 +0.009383 +0.00922 +0.009134 +0.009193 +0.009267 +0.009361 +0.009416 +0.009256 +0.009178 +0.009236 +0.009306 +0.009405 +0.009485 +0.009335 +0.009257 +0.009288 +0.009377 +0.009443 +0.009507 +0.00935 +0.009273 +0.009341 +0.00942 +0.009491 +0.009568 +0.009417 +0.00934 +0.009387 +0.009472 +0.009548 +0.009619 +0.009445 +0.009377 +0.009427 +0.009509 +0.009613 +0.009667 +0.009517 +0.009435 +0.009495 +0.00959 +0.009642 +0.009708 +0.00954 +0.009467 +0.00954 +0.009612 +0.009706 +0.009784 +0.009633 +0.009521 +0.00958 +0.009655 +0.009748 +0.009818 +0.009655 +0.009568 +0.009631 +0.00973 +0.009802 +0.009891 +0.009716 +0.009622 +0.00966 +0.009764 +0.009867 +0.009962 +0.009764 +0.009661 +0.009722 +0.009827 +0.009926 +0.009979 +0.009803 +0.009709 +0.009775 +0.009859 +0.009971 +0.010024 +0.009851 +0.009783 +0.009848 +0.009939 +0.010033 +0.01008 +0.009891 +0.009816 +0.009876 +0.009964 +0.010067 +0.010136 +0.009999 +0.009895 +0.009943 +0.010029 +0.01014 +0.010163 +0.009993 +0.009914 +0.009987 +0.01006 +0.010159 +0.010267 +0.010098 +0.009987 +0.010054 +0.010126 +0.010212 +0.010292 +0.010103 +0.010019 +0.010093 +0.010176 +0.010291 +0.010368 +0.010178 +0.010128 +0.010143 +0.010207 +0.010322 +0.01039 +0.010245 +0.010131 +0.010187 +0.010285 +0.010387 +0.010464 +0.010276 +0.010176 +0.010249 +0.010355 +0.010448 +0.010512 +0.010341 +0.010225 +0.0103 +0.010393 +0.010497 +0.01057 +0.010376 +0.010338 +0.010351 +0.010424 +0.010529 +0.010578 +0.010384 +0.010259 +0.010294 +0.010335 +0.010404 +0.010456 +0.010212 +0.010092 +0.010127 +0.010145 +0.01019 +0.010221 +0.009957 +0.009831 +0.009841 +0.009883 +0.009923 +0.00995 +0.009726 +0.009609 +0.009632 +0.009666 +0.009685 +0.009687 +0.009473 +0.009351 +0.00937 +0.009389 +0.009438 +0.009459 +0.009272 +0.009131 +0.009139 +0.00916 +0.009203 +0.009221 +0.009031 +0.008919 +0.008924 +0.008966 +0.009036 +0.009044 +0.008863 +0.008762 +0.008776 +0.008809 +0.008861 +0.008903 +0.008732 +0.008641 +0.008654 +0.00868 +0.008727 +0.008772 +0.008613 +0.008509 +0.008541 +0.008593 +0.00866 +0.008674 +0.008528 +0.008439 +0.008477 +0.008544 +0.008615 +0.008677 +0.008539 +0.008467 +0.008524 +0.008602 +0.00867 +0.008726 +0.008569 +0.008502 +0.008552 +0.008624 +0.008707 +0.008773 +0.008645 +0.008544 +0.008594 +0.008685 +0.008747 +0.008819 +0.008658 +0.008591 +0.008648 +0.008716 +0.008805 +0.008862 +0.008714 +0.008655 +0.008718 +0.00875 +0.008845 +0.008898 +0.008753 +0.008681 +0.008737 +0.008803 +0.008894 +0.00897 +0.00882 +0.008731 +0.008791 +0.008871 +0.008957 +0.009009 +0.008837 +0.008762 +0.00882 +0.008899 +0.008984 +0.009045 +0.008912 +0.008833 +0.008886 +0.008953 +0.009018 +0.009096 +0.008935 +0.008861 +0.008949 +0.008986 +0.009077 +0.009157 +0.008999 +0.008918 +0.008974 +0.009055 +0.009113 +0.009183 +0.009035 +0.008949 +0.009043 +0.009112 +0.00917 +0.009232 +0.009073 +0.009005 +0.009054 +0.009137 +0.009217 +0.009296 +0.009131 +0.009059 +0.009118 +0.009196 +0.009265 +0.009336 +0.009186 +0.009104 +0.009166 +0.009234 +0.009306 +0.009385 +0.009229 +0.009155 +0.009214 +0.009294 +0.009364 +0.009468 +0.009279 +0.009188 +0.009242 +0.009321 +0.00941 +0.009487 +0.009335 +0.009255 +0.009314 +0.009402 +0.009455 +0.00953 +0.009359 +0.00929 +0.009348 +0.009431 +0.009516 +0.0096 +0.009446 +0.009342 +0.009401 +0.009483 +0.009558 +0.009627 +0.009472 +0.009389 +0.009465 +0.009507 +0.009635 +0.009676 +0.009532 +0.009449 +0.009507 +0.009576 +0.009668 +0.009739 +0.009558 +0.009482 +0.009534 +0.009613 +0.009721 +0.00982 +0.009626 +0.009542 +0.009611 +0.00966 +0.009766 +0.009843 +0.00965 +0.009579 +0.009642 +0.009737 +0.009868 +0.009898 +0.009719 +0.009638 +0.009682 +0.009779 +0.009868 +0.009933 +0.009771 +0.00968 +0.009753 +0.009853 +0.009941 +0.010028 +0.009818 +0.009717 +0.009775 +0.009879 +0.009972 +0.01005 +0.009884 +0.009774 +0.009845 +0.009937 +0.010039 +0.010126 +0.009914 +0.009831 +0.009887 +0.009985 +0.010103 +0.010186 +0.009954 +0.009885 +0.009947 +0.010029 +0.010146 +0.010194 +0.010027 +0.009944 +0.010038 +0.0101 +0.010198 +0.010241 +0.010072 +0.009985 +0.010051 +0.010138 +0.010233 +0.010332 +0.010174 +0.010055 +0.010111 +0.010181 +0.010285 +0.010363 +0.010176 +0.010093 +0.01019 +0.010239 +0.010347 +0.010432 +0.01025 +0.010157 +0.010212 +0.010295 +0.0104 +0.010478 +0.010289 +0.010185 +0.010274 +0.01035 +0.010489 +0.010543 +0.010349 +0.010256 +0.010273 +0.01034 +0.010433 +0.010481 +0.010246 +0.01012 +0.010174 +0.010251 +0.010296 +0.010286 +0.010057 +0.00992 +0.009958 +0.009997 +0.01003 +0.010037 +0.009806 +0.009691 +0.009721 +0.00976 +0.009779 +0.00977 +0.009562 +0.009454 +0.009468 +0.009487 +0.009508 +0.009523 +0.009322 +0.009207 +0.009217 +0.009253 +0.009272 +0.009272 +0.009072 +0.008968 +0.009003 +0.009022 +0.009051 +0.009086 +0.008885 +0.008796 +0.00883 +0.008866 +0.008894 +0.008921 +0.008744 +0.008631 +0.008666 +0.008724 +0.008762 +0.008812 +0.008611 +0.008509 +0.00855 +0.008599 +0.008658 +0.008694 +0.008492 +0.008408 +0.008457 +0.008515 +0.008584 +0.008626 +0.008475 +0.008395 +0.008465 +0.008552 +0.008628 +0.008678 +0.008513 +0.008448 +0.008488 +0.008562 +0.008649 +0.008716 +0.008564 +0.008489 +0.00855 +0.008633 +0.008738 +0.008765 +0.008611 +0.00853 +0.008574 +0.008655 +0.008741 +0.008805 +0.008672 +0.00857 +0.008629 +0.008722 +0.008799 +0.008854 +0.0087 +0.008621 +0.008668 +0.008752 +0.008839 +0.008893 +0.008746 +0.008662 +0.008724 +0.008811 +0.008892 +0.008948 +0.008787 +0.008728 +0.008774 +0.008864 +0.008913 +0.008977 +0.008835 +0.008765 +0.008821 +0.008899 +0.008964 +0.009025 +0.00888 +0.008819 +0.008848 +0.008936 +0.009011 +0.009087 +0.008923 +0.008857 +0.008908 +0.008979 +0.009067 +0.009135 +0.008969 +0.008897 +0.008952 +0.009027 +0.009113 +0.009193 +0.009035 +0.008964 +0.00899 +0.009072 +0.009165 +0.009221 +0.009065 +0.00899 +0.00904 +0.009123 +0.009206 +0.009281 +0.009112 +0.009055 +0.009084 +0.009173 +0.009266 +0.00933 +0.009154 +0.009078 +0.00914 +0.009216 +0.009308 +0.009389 +0.009202 +0.009125 +0.009195 +0.009278 +0.009389 +0.009418 +0.009251 +0.009168 +0.009251 +0.009318 +0.009416 +0.009455 +0.009301 +0.009222 +0.009283 +0.009364 +0.009458 +0.009512 +0.00935 +0.009282 +0.009346 +0.009421 +0.009519 +0.009566 +0.009389 +0.009318 +0.009388 +0.009464 +0.009548 +0.009625 +0.009461 +0.009397 +0.009429 +0.009517 +0.009607 +0.009649 +0.009496 +0.009437 +0.009468 +0.009563 +0.009638 +0.009713 +0.00956 +0.009471 +0.009536 +0.009629 +0.009718 +0.009752 +0.009596 +0.009516 +0.009572 +0.009657 +0.009746 +0.00982 +0.009665 +0.009585 +0.009655 +0.009746 +0.00978 +0.009856 +0.009697 +0.009612 +0.009674 +0.009752 +0.009872 +0.00995 +0.009744 +0.009669 +0.009743 +0.0098 +0.009903 +0.009978 +0.009797 +0.00973 +0.009792 +0.009859 +0.00995 +0.010023 +0.009849 +0.009767 +0.009824 +0.009925 +0.010057 +0.010086 +0.009907 +0.009812 +0.009867 +0.009967 +0.010058 +0.010127 +0.009961 +0.009857 +0.009958 +0.010028 +0.010137 +0.010195 +0.010011 +0.009917 +0.009971 +0.010071 +0.010168 +0.01024 +0.010053 +0.009979 +0.010037 +0.010139 +0.010244 +0.01032 +0.010106 +0.010007 +0.010101 +0.010153 +0.010278 +0.010336 +0.010179 +0.010078 +0.010148 +0.010238 +0.010324 +0.010386 +0.010211 +0.010136 +0.01019 +0.010268 +0.010408 +0.010464 +0.010285 +0.010203 +0.010243 +0.010321 +0.010439 +0.010509 +0.010321 +0.01019 +0.010244 +0.010343 +0.010442 +0.010407 +0.010192 +0.010069 +0.010109 +0.010132 +0.010188 +0.0102 +0.00998 +0.009837 +0.009879 +0.009883 +0.009924 +0.009925 +0.009704 +0.009578 +0.009595 +0.009629 +0.009669 +0.009657 +0.00945 +0.00935 +0.009354 +0.009355 +0.009385 +0.009389 +0.009191 +0.009062 +0.00911 +0.009102 +0.009149 +0.009152 +0.008959 +0.008858 +0.008863 +0.008904 +0.008944 +0.008965 +0.008768 +0.008659 +0.008693 +0.00873 +0.008772 +0.008792 +0.008619 +0.008523 +0.008544 +0.008597 +0.008645 +0.008686 +0.008501 +0.008399 +0.008435 +0.00848 +0.008531 +0.008564 +0.008414 +0.008321 +0.008377 +0.008453 +0.008475 +0.008519 +0.008375 +0.008311 +0.008368 +0.008442 +0.00852 +0.008583 +0.008426 +0.008359 +0.008411 +0.008495 +0.008564 +0.00861 +0.008477 +0.008397 +0.00845 +0.008528 +0.008611 +0.008669 +0.008547 +0.008434 +0.008492 +0.008574 +0.008656 +0.008708 +0.008553 +0.008485 +0.008528 +0.008616 +0.008702 +0.008763 +0.008593 +0.008527 +0.008583 +0.008676 +0.008752 +0.0088 +0.008636 +0.008567 +0.008629 +0.008705 +0.008772 +0.008845 +0.008692 +0.00862 +0.008675 +0.008763 +0.00886 +0.008922 +0.00874 +0.008676 +0.008708 +0.008781 +0.008874 +0.008921 +0.008779 +0.008702 +0.008757 +0.008841 +0.008927 +0.008994 +0.008831 +0.008757 +0.008803 +0.008879 +0.00896 +0.009032 +0.008883 +0.008793 +0.008847 +0.008932 +0.009031 +0.009088 +0.008932 +0.008855 +0.008901 +0.008992 +0.009047 +0.00911 +0.008958 +0.008885 +0.008961 +0.009016 +0.009112 +0.009186 +0.009026 +0.008935 +0.008982 +0.009069 +0.009157 +0.009214 +0.009064 +0.008978 +0.009026 +0.009115 +0.009218 +0.009283 +0.009124 +0.009044 +0.009083 +0.009161 +0.009244 +0.009316 +0.009179 +0.00908 +0.009124 +0.009202 +0.0093 +0.009363 +0.009202 +0.009145 +0.009178 +0.009254 +0.00934 +0.00941 +0.009255 +0.009161 +0.009238 +0.009315 +0.0094 +0.009473 +0.009298 +0.009225 +0.009274 +0.009347 +0.009452 +0.00952 +0.009344 +0.009259 +0.009347 +0.00943 +0.009506 +0.009552 +0.009384 +0.009312 +0.009375 +0.009444 +0.009544 +0.009611 +0.009473 +0.009349 +0.009421 +0.009508 +0.009597 +0.009668 +0.0095 +0.009411 +0.009466 +0.009547 +0.009654 +0.009715 +0.00955 +0.009484 +0.009514 +0.009596 +0.009705 +0.009781 +0.009611 +0.009523 +0.009537 +0.00964 +0.00974 +0.009826 +0.009653 +0.009566 +0.009613 +0.0097 +0.009794 +0.009866 +0.009686 +0.009608 +0.009663 +0.009747 +0.009865 +0.009936 +0.009747 +0.009652 +0.009724 +0.009813 +0.009899 +0.009959 +0.009791 +0.009731 +0.009773 +0.00986 +0.009962 +0.010039 +0.009829 +0.009745 +0.009818 +0.009911 +0.009991 +0.010068 +0.009887 +0.009804 +0.00988 +0.009987 +0.010073 +0.010128 +0.009951 +0.009852 +0.009916 +0.010011 +0.010116 +0.010179 +0.009984 +0.009926 +0.010017 +0.010098 +0.010156 +0.01021 +0.010022 +0.009961 +0.010037 +0.010103 +0.010221 +0.010272 +0.010096 +0.010021 +0.01007 +0.01017 +0.010276 +0.010329 +0.010155 +0.010069 +0.010129 +0.010225 +0.010327 +0.010397 +0.010202 +0.010119 +0.010183 +0.010308 +0.010377 +0.010437 +0.010261 +0.010186 +0.010241 +0.010305 +0.010443 +0.010461 +0.01027 +0.01016 +0.010217 +0.010279 +0.010352 +0.010372 +0.010154 +0.010032 +0.010027 +0.010089 +0.010136 +0.01014 +0.009915 +0.009778 +0.0098 +0.009851 +0.009891 +0.009919 +0.009664 +0.009515 +0.00953 +0.00956 +0.009607 +0.009609 +0.009408 +0.009299 +0.009283 +0.009316 +0.009347 +0.009385 +0.009162 +0.009044 +0.00907 +0.00909 +0.009134 +0.009164 +0.008971 +0.00884 +0.008864 +0.008911 +0.008943 +0.008977 +0.008793 +0.008686 +0.008739 +0.008788 +0.008797 +0.008819 +0.008659 +0.008549 +0.008586 +0.008635 +0.008679 +0.00872 +0.008545 +0.008441 +0.008484 +0.008547 +0.008602 +0.008648 +0.008494 +0.008411 +0.008446 +0.0085 +0.008588 +0.008636 +0.008503 +0.008432 +0.00848 +0.008534 +0.008628 +0.008687 +0.008556 +0.008493 +0.008548 +0.008596 +0.008661 +0.008736 +0.008573 +0.008514 +0.008554 +0.008632 +0.008713 +0.008778 +0.008636 +0.008568 +0.008624 +0.008696 +0.008754 +0.008816 +0.008676 +0.008619 +0.008647 +0.008729 +0.008805 +0.008856 +0.008721 +0.008667 +0.008702 +0.008781 +0.008859 +0.008907 +0.008788 +0.00869 +0.008735 +0.008805 +0.008894 +0.008961 +0.008825 +0.008731 +0.0088 +0.008883 +0.008942 +0.009003 +0.008852 +0.008786 +0.008833 +0.008902 +0.009003 +0.009051 +0.008909 +0.008819 +0.00888 +0.008952 +0.009038 +0.009112 +0.008948 +0.008863 +0.008933 +0.009009 +0.00912 +0.00916 +0.008995 +0.008909 +0.008965 +0.009048 +0.009137 +0.009201 +0.00906 +0.008953 +0.00901 +0.009091 +0.00919 +0.009261 +0.009091 +0.009005 +0.009073 +0.009151 +0.009236 +0.009302 +0.009134 +0.009049 +0.009112 +0.00919 +0.009279 +0.009364 +0.009203 +0.009122 +0.009184 +0.009232 +0.009318 +0.009383 +0.009232 +0.009151 +0.009203 +0.009283 +0.009389 +0.009459 +0.009284 +0.009193 +0.009267 +0.009338 +0.009427 +0.009504 +0.009331 +0.009239 +0.009309 +0.009415 +0.00948 +0.009559 +0.009384 +0.009275 +0.009356 +0.009443 +0.009551 +0.009604 +0.00943 +0.009332 +0.009401 +0.009496 +0.009592 +0.009644 +0.00946 +0.009397 +0.009452 +0.009533 +0.009635 +0.009689 +0.00953 +0.009459 +0.009511 +0.009601 +0.009679 +0.00975 +0.009563 +0.009492 +0.009554 +0.009637 +0.00974 +0.00979 +0.009638 +0.009559 +0.009594 +0.009678 +0.009786 +0.009846 +0.009672 +0.009589 +0.009646 +0.009755 +0.009838 +0.009888 +0.00973 +0.009643 +0.009722 +0.009797 +0.009876 +0.009945 +0.009787 +0.009689 +0.009759 +0.009858 +0.009944 +0.009996 +0.00983 +0.00975 +0.009842 +0.009898 +0.009972 +0.010043 +0.009902 +0.009796 +0.009881 +0.009953 +0.010029 +0.010097 +0.009932 +0.009841 +0.009916 +0.009988 +0.010086 +0.010186 +0.009997 +0.009905 +0.009964 +0.010032 +0.010144 +0.010206 +0.010039 +0.009957 +0.010011 +0.010092 +0.01023 +0.010315 +0.010118 +0.009992 +0.010038 +0.010144 +0.010243 +0.010343 +0.010129 +0.010043 +0.010107 +0.010211 +0.01031 +0.010388 +0.010195 +0.010119 +0.010165 +0.010261 +0.010368 +0.010428 +0.010252 +0.010182 +0.010215 +0.010316 +0.010428 +0.010483 +0.010301 +0.010213 +0.010319 +0.010381 +0.010464 +0.010532 +0.01034 +0.010235 +0.010298 +0.010398 +0.010438 +0.010461 +0.010242 +0.010133 +0.010156 +0.010219 +0.010302 +0.010256 +0.010021 +0.009878 +0.00991 +0.009965 +0.009992 +0.010001 +0.00977 +0.009655 +0.009665 +0.009718 +0.009767 +0.009776 +0.00953 +0.009386 +0.009409 +0.009451 +0.009502 +0.009487 +0.009289 +0.009162 +0.009187 +0.00924 +0.009267 +0.009262 +0.009048 +0.008939 +0.008994 +0.009002 +0.009048 +0.009052 +0.008876 +0.008754 +0.00879 +0.008852 +0.008894 +0.008893 +0.008715 +0.008609 +0.008652 +0.00872 +0.00874 +0.00876 +0.008585 +0.008489 +0.008553 +0.008591 +0.008652 +0.008678 +0.008501 +0.0084 +0.008442 +0.008512 +0.008575 +0.008614 +0.008462 +0.008389 +0.008442 +0.008538 +0.008608 +0.008676 +0.008509 +0.00844 +0.008477 +0.008558 +0.008638 +0.008697 +0.008559 +0.008487 +0.008555 +0.008608 +0.008696 +0.008744 +0.008596 +0.008539 +0.008583 +0.008634 +0.008716 +0.008783 +0.008654 +0.008565 +0.008618 +0.008693 +0.008807 +0.008841 +0.008699 +0.008615 +0.008672 +0.008726 +0.008834 +0.008875 +0.008726 +0.008658 +0.008708 +0.008776 +0.008873 +0.008951 +0.008801 +0.008731 +0.008756 +0.008833 +0.0089 +0.008964 +0.008838 +0.008743 +0.008795 +0.008878 +0.008958 +0.009028 +0.008865 +0.008814 +0.008834 +0.008914 +0.009003 +0.009074 +0.008913 +0.008842 +0.008916 +0.008977 +0.009046 +0.009122 +0.008963 +0.008884 +0.008939 +0.009023 +0.009111 +0.00919 +0.008996 +0.008918 +0.008996 +0.00907 +0.009155 +0.009205 +0.00905 +0.008981 +0.009032 +0.009114 +0.009189 +0.00927 +0.009116 +0.009042 +0.009091 +0.009164 +0.009233 +0.009306 +0.009157 +0.009068 +0.009126 +0.009222 +0.009283 +0.009362 +0.009208 +0.009125 +0.009218 +0.009268 +0.009334 +0.009391 +0.009241 +0.009178 +0.009251 +0.009305 +0.009384 +0.009451 +0.009291 +0.009217 +0.009289 +0.009355 +0.009431 +0.009511 +0.009355 +0.009252 +0.00933 +0.00941 +0.009489 +0.009561 +0.009401 +0.009313 +0.009381 +0.00946 +0.009551 +0.009635 +0.009429 +0.009346 +0.009418 +0.009501 +0.009609 +0.009662 +0.009488 +0.009438 +0.009457 +0.009533 +0.009642 +0.009707 +0.009543 +0.009459 +0.009522 +0.009599 +0.009705 +0.009771 +0.009609 +0.009516 +0.009564 +0.009641 +0.009741 +0.009817 +0.009646 +0.009589 +0.00961 +0.009701 +0.009813 +0.009883 +0.009702 +0.009604 +0.009651 +0.009749 +0.009839 +0.009939 +0.009737 +0.009641 +0.009719 +0.00983 +0.009915 +0.009974 +0.0098 +0.009698 +0.009761 +0.009848 +0.009955 +0.010021 +0.009836 +0.009766 +0.009861 +0.009943 +0.010008 +0.010066 +0.009882 +0.009816 +0.009861 +0.009945 +0.010055 +0.01011 +0.009949 +0.009871 +0.009953 +0.010006 +0.010109 +0.010178 +0.01 +0.009914 +0.00998 +0.010052 +0.010174 +0.010245 +0.010051 +0.00996 +0.010044 +0.010149 +0.010232 +0.010271 +0.010107 +0.010009 +0.010116 +0.010174 +0.010255 +0.010347 +0.010146 +0.010063 +0.010139 +0.010215 +0.010322 +0.010412 +0.010212 +0.010121 +0.010203 +0.010271 +0.010383 +0.010462 +0.010263 +0.010175 +0.010245 +0.010344 +0.010479 +0.010506 +0.010335 +0.010209 +0.010293 +0.010381 +0.010507 +0.010544 +0.010349 +0.010263 +0.010372 +0.010462 +0.010553 +0.0106 +0.010392 +0.010298 +0.010353 +0.01041 +0.010477 +0.01049 +0.010294 +0.010173 +0.010201 +0.010251 +0.010297 +0.010328 +0.010067 +0.009925 +0.009954 +0.009998 +0.010047 +0.010049 +0.009832 +0.009705 +0.009703 +0.009735 +0.009785 +0.009783 +0.00957 +0.009464 +0.009468 +0.009511 +0.009558 +0.009539 +0.009318 +0.009208 +0.009227 +0.009253 +0.009284 +0.009311 +0.009119 +0.009022 +0.009019 +0.009065 +0.009101 +0.009119 +0.00892 +0.008814 +0.008846 +0.008887 +0.00893 +0.008959 +0.00879 +0.008687 +0.008707 +0.008764 +0.008815 +0.008838 +0.008654 +0.008566 +0.008604 +0.008649 +0.008707 +0.008744 +0.008587 +0.008498 +0.008548 +0.00862 +0.008686 +0.008766 +0.008592 +0.008509 +0.008569 +0.008639 +0.008717 +0.008769 +0.008622 +0.008543 +0.008611 +0.008692 +0.008774 +0.00883 +0.008684 +0.008602 +0.008638 +0.008727 +0.008802 +0.008866 +0.008716 +0.008655 +0.008686 +0.008767 +0.008861 +0.008923 +0.008783 +0.008683 +0.008739 +0.008824 +0.008935 +0.008951 +0.0088 +0.008726 +0.008795 +0.008887 +0.008948 +0.008995 +0.008853 +0.008776 +0.008824 +0.008898 +0.008989 +0.00905 +0.008914 +0.008847 +0.0089 +0.00897 +0.00905 +0.009115 +0.008935 +0.008867 +0.00892 +0.008999 +0.009087 +0.00916 +0.008997 +0.008926 +0.008994 +0.009038 +0.009133 +0.009188 +0.009043 +0.00897 +0.009011 +0.009102 +0.009189 +0.00926 +0.009086 +0.009006 +0.00907 +0.009136 +0.009228 +0.009306 +0.009124 +0.009077 +0.009111 +0.009197 +0.009283 +0.009359 +0.009185 +0.009094 +0.009157 +0.009245 +0.009336 +0.009428 +0.009217 +0.009143 +0.009202 +0.009291 +0.009388 +0.009452 +0.00927 +0.009198 +0.009255 +0.009353 +0.009419 +0.009481 +0.009316 +0.009246 +0.009308 +0.009377 +0.009489 +0.009559 +0.009379 +0.009293 +0.00935 +0.00944 +0.009529 +0.009579 +0.009435 +0.009352 +0.009424 +0.009484 +0.00961 +0.009619 +0.009463 +0.009391 +0.009438 +0.00953 +0.00963 +0.009676 +0.00952 +0.009449 +0.009514 +0.009598 +0.009688 +0.009731 +0.009567 +0.009484 +0.009546 +0.009626 +0.00972 +0.009803 +0.00962 +0.009551 +0.009625 +0.009717 +0.009802 +0.009824 +0.009657 +0.009572 +0.009657 +0.009726 +0.009812 +0.009909 +0.00974 +0.009644 +0.009713 +0.009787 +0.009863 +0.009929 +0.009771 +0.009683 +0.009744 +0.009831 +0.009952 +0.010014 +0.009837 +0.009755 +0.009799 +0.009877 +0.009981 +0.010077 +0.009882 +0.009774 +0.009834 +0.009963 +0.010062 +0.010119 +0.009927 +0.009839 +0.009885 +0.009982 +0.010088 +0.010149 +0.009973 +0.009883 +0.009983 +0.010059 +0.010157 +0.010222 +0.010018 +0.009937 +0.010006 +0.010087 +0.0102 +0.010279 +0.010103 +0.010016 +0.010058 +0.010154 +0.010266 +0.010304 +0.01013 +0.010036 +0.010142 +0.010186 +0.010288 +0.010381 +0.0102 +0.010093 +0.010182 +0.010261 +0.010342 +0.010434 +0.010233 +0.010149 +0.010222 +0.010309 +0.010416 +0.010493 +0.010315 +0.010242 +0.010278 +0.010336 +0.010457 +0.010539 +0.010363 +0.010269 +0.010328 +0.010405 +0.01052 +0.010587 +0.010394 +0.010308 +0.010381 +0.010472 +0.010591 +0.010649 +0.010434 +0.010316 +0.010378 +0.010431 +0.010499 +0.010496 +0.010285 +0.01019 +0.010226 +0.010244 +0.010305 +0.010267 +0.01004 +0.009905 +0.009933 +0.009985 +0.010015 +0.010015 +0.009798 +0.009678 +0.009698 +0.009732 +0.009774 +0.009755 +0.009538 +0.009425 +0.00943 +0.009466 +0.009501 +0.009494 +0.009283 +0.009183 +0.0092 +0.009261 +0.009264 +0.009247 +0.009039 +0.008937 +0.008983 +0.00899 +0.00903 +0.009043 +0.008858 +0.008759 +0.00879 +0.008835 +0.008869 +0.008885 +0.008688 +0.008598 +0.008617 +0.008665 +0.008708 +0.008735 +0.008555 +0.008464 +0.008519 +0.008555 +0.008602 +0.008623 +0.008465 +0.008377 +0.008429 +0.008463 +0.008517 +0.008571 +0.008418 +0.008368 +0.008407 +0.008495 +0.008545 +0.008624 +0.008464 +0.008389 +0.008441 +0.008514 +0.008597 +0.008659 +0.008528 +0.008447 +0.008506 +0.008557 +0.008651 +0.008698 +0.008556 +0.008488 +0.008539 +0.008601 +0.008689 +0.008761 +0.008621 +0.008564 +0.008589 +0.008655 +0.008747 +0.008779 +0.008637 +0.008561 +0.008619 +0.008693 +0.0088 +0.008847 +0.008683 +0.008624 +0.008679 +0.008747 +0.008819 +0.008887 +0.008725 +0.008658 +0.008711 +0.008794 +0.008872 +0.00893 +0.008796 +0.008705 +0.008771 +0.008844 +0.008932 +0.008985 +0.008833 +0.008764 +0.008789 +0.00887 +0.008959 +0.009022 +0.008865 +0.008794 +0.008863 +0.008932 +0.009022 +0.009069 +0.008919 +0.008836 +0.008886 +0.008974 +0.009058 +0.009115 +0.008965 +0.008898 +0.008947 +0.009027 +0.009122 +0.009175 +0.009002 +0.008929 +0.008992 +0.009086 +0.009169 +0.009203 +0.009047 +0.008988 +0.009037 +0.00912 +0.009203 +0.009256 +0.009111 +0.009016 +0.009075 +0.009164 +0.009249 +0.009325 +0.00916 +0.009088 +0.009129 +0.009212 +0.009312 +0.009349 +0.009201 +0.009117 +0.009178 +0.009263 +0.009344 +0.009407 +0.009274 +0.009206 +0.009223 +0.009301 +0.009402 +0.009438 +0.009318 +0.009197 +0.009267 +0.009362 +0.009437 +0.0095 +0.009347 +0.009256 +0.009311 +0.009403 +0.009502 +0.009554 +0.009398 +0.009318 +0.009372 +0.009461 +0.009554 +0.009612 +0.00944 +0.009358 +0.009419 +0.009529 +0.009596 +0.009654 +0.009493 +0.009402 +0.009476 +0.00955 +0.009637 +0.009734 +0.009532 +0.00945 +0.009507 +0.009604 +0.009689 +0.009761 +0.009599 +0.009505 +0.009569 +0.009661 +0.009742 +0.00981 +0.009644 +0.009558 +0.009612 +0.009691 +0.009802 +0.009905 +0.009703 +0.009617 +0.009662 +0.009734 +0.009847 +0.009913 +0.009733 +0.009655 +0.009707 +0.009824 +0.009897 +0.009966 +0.009801 +0.009718 +0.009766 +0.009837 +0.009943 +0.010013 +0.009845 +0.009756 +0.009826 +0.009903 +0.010002 +0.010095 +0.009914 +0.009839 +0.009864 +0.009957 +0.010031 +0.01011 +0.009926 +0.009846 +0.009918 +0.010022 +0.010128 +0.010186 +0.010007 +0.009891 +0.009962 +0.010061 +0.010154 +0.010222 +0.010054 +0.009948 +0.010023 +0.010146 +0.010232 +0.010281 +0.010098 +0.01002 +0.010097 +0.010153 +0.01025 +0.010325 +0.010189 +0.010067 +0.010126 +0.01023 +0.010324 +0.010361 +0.0102 +0.010118 +0.010178 +0.010285 +0.010368 +0.010429 +0.010261 +0.01017 +0.010227 +0.010321 +0.010437 +0.010492 +0.010292 +0.010203 +0.010317 +0.010305 +0.010385 +0.010421 +0.010191 +0.010061 +0.010124 +0.010145 +0.010185 +0.0102 +0.009973 +0.009836 +0.009849 +0.009876 +0.009908 +0.009911 +0.009683 +0.009557 +0.009562 +0.009616 +0.00964 +0.009642 +0.009444 +0.009296 +0.009304 +0.009344 +0.009372 +0.009403 +0.009156 +0.009035 +0.009039 +0.009087 +0.009144 +0.009134 +0.008953 +0.008812 +0.008821 +0.008864 +0.008903 +0.008929 +0.008727 +0.008618 +0.008655 +0.008706 +0.008767 +0.008773 +0.008598 +0.008497 +0.008533 +0.008571 +0.008638 +0.00865 +0.008486 +0.008389 +0.008432 +0.008504 +0.008548 +0.008563 +0.00839 +0.008308 +0.008353 +0.008423 +0.00847 +0.008521 +0.008369 +0.0083 +0.00836 +0.008417 +0.008498 +0.008553 +0.008417 +0.008334 +0.008394 +0.008457 +0.008537 +0.008592 +0.008467 +0.008384 +0.008426 +0.008515 +0.008596 +0.00864 +0.008491 +0.008425 +0.008476 +0.008573 +0.008628 +0.008687 +0.008523 +0.008462 +0.008528 +0.008607 +0.008703 +0.008731 +0.008581 +0.0085 +0.008556 +0.00865 +0.00873 +0.008763 +0.008614 +0.008561 +0.008599 +0.00868 +0.008761 +0.008833 +0.008679 +0.008598 +0.00865 +0.008729 +0.008812 +0.008865 +0.00874 +0.008661 +0.008717 +0.008768 +0.008851 +0.008903 +0.008783 +0.008688 +0.008728 +0.008812 +0.008898 +0.008966 +0.008819 +0.008743 +0.008793 +0.008852 +0.008942 +0.009005 +0.008862 +0.008782 +0.008824 +0.008904 +0.008991 +0.009075 +0.008913 +0.008842 +0.008888 +0.008941 +0.009039 +0.009105 +0.008972 +0.008872 +0.008918 +0.008983 +0.009081 +0.009164 +0.009023 +0.008919 +0.008978 +0.009045 +0.009124 +0.009188 +0.009042 +0.008951 +0.009014 +0.00909 +0.009174 +0.009256 +0.0091 +0.009029 +0.009079 +0.009135 +0.009227 +0.009292 +0.009129 +0.009051 +0.009114 +0.009193 +0.009301 +0.009359 +0.009188 +0.009108 +0.009169 +0.009233 +0.009313 +0.009381 +0.009251 +0.009136 +0.009194 +0.009278 +0.009377 +0.009458 +0.009293 +0.009213 +0.009265 +0.009326 +0.009427 +0.009476 +0.009319 +0.009243 +0.009295 +0.009378 +0.009478 +0.009563 +0.009414 +0.009313 +0.009354 +0.009416 +0.009514 +0.009583 +0.009422 +0.009339 +0.009391 +0.0095 +0.009587 +0.009656 +0.009479 +0.009387 +0.009443 +0.009527 +0.009613 +0.009685 +0.009529 +0.009449 +0.009491 +0.009572 +0.009672 +0.009734 +0.009569 +0.00949 +0.009563 +0.009668 +0.00973 +0.009797 +0.009632 +0.009523 +0.009587 +0.009669 +0.009768 +0.009843 +0.009674 +0.009584 +0.009658 +0.009744 +0.009847 +0.009907 +0.009709 +0.009624 +0.009691 +0.009779 +0.009871 +0.009942 +0.009781 +0.009685 +0.009758 +0.009864 +0.009973 +0.009996 +0.009813 +0.009718 +0.009785 +0.009911 +0.009966 +0.010031 +0.009891 +0.009801 +0.009861 +0.009949 +0.010044 +0.010084 +0.009919 +0.009835 +0.009887 +0.009971 +0.010087 +0.010172 +0.00999 +0.009911 +0.009961 +0.010038 +0.010142 +0.010223 +0.010036 +0.009942 +0.009992 +0.010078 +0.010189 +0.010253 +0.010107 +0.009992 +0.010038 +0.010144 +0.010252 +0.010321 +0.010128 +0.010055 +0.010109 +0.010205 +0.010298 +0.010367 +0.010182 +0.010093 +0.010164 +0.010252 +0.010368 +0.010445 +0.010271 +0.010144 +0.010206 +0.010292 +0.010403 +0.010472 +0.010282 +0.010201 +0.010283 +0.010376 +0.010479 +0.010501 +0.010311 +0.01022 +0.010282 +0.010337 +0.010404 +0.010458 +0.010216 +0.01009 +0.010148 +0.010172 +0.010212 +0.010229 +0.010013 +0.009884 +0.009873 +0.009884 +0.009927 +0.009953 +0.009704 +0.009577 +0.009596 +0.009613 +0.00963 +0.009631 +0.009419 +0.009286 +0.0093 +0.009332 +0.009374 +0.009364 +0.009158 +0.00903 +0.009049 +0.0091 +0.009097 +0.0091 +0.008898 +0.008798 +0.008817 +0.008882 +0.008907 +0.008919 +0.008719 +0.008644 +0.008652 +0.00869 +0.008721 +0.008746 +0.008578 +0.008477 +0.008514 +0.008565 +0.008608 +0.008651 +0.008467 +0.00838 +0.008408 +0.008462 +0.008496 +0.008524 +0.008356 +0.008282 +0.008327 +0.008391 +0.008446 +0.00848 +0.008352 +0.008276 +0.008353 +0.008414 +0.008468 +0.008506 +0.008371 +0.008297 +0.008353 +0.008443 +0.008501 +0.00856 +0.00842 +0.008356 +0.008414 +0.00849 +0.008561 +0.008616 +0.008454 +0.008394 +0.00844 +0.008511 +0.008596 +0.008659 +0.008505 +0.008435 +0.008508 +0.008579 +0.008666 +0.008702 +0.008563 +0.008484 +0.008545 +0.008601 +0.008683 +0.008738 +0.008592 +0.00853 +0.008569 +0.008649 +0.008738 +0.008815 +0.008634 +0.008564 +0.008619 +0.008699 +0.008775 +0.008842 +0.008709 +0.00862 +0.008658 +0.008743 +0.008815 +0.008875 +0.008731 +0.008661 +0.008713 +0.008794 +0.008872 +0.008942 +0.008817 +0.008729 +0.008767 +0.008815 +0.00891 +0.00897 +0.008826 +0.008753 +0.008799 +0.00888 +0.008964 +0.009049 +0.008873 +0.008812 +0.00885 +0.008915 +0.00901 +0.009062 +0.008915 +0.008844 +0.008902 +0.00896 +0.009061 +0.009129 +0.008968 +0.008907 +0.008953 +0.009016 +0.009124 +0.009177 +0.009007 +0.00894 +0.008982 +0.009048 +0.009151 +0.009208 +0.009057 +0.009001 +0.009058 +0.009097 +0.009197 +0.00927 +0.009101 +0.009022 +0.009083 +0.009143 +0.009249 +0.009318 +0.009153 +0.009089 +0.009146 +0.00921 +0.00929 +0.009355 +0.009205 +0.009129 +0.009196 +0.009248 +0.009344 +0.009403 +0.009244 +0.009209 +0.009229 +0.009291 +0.009395 +0.009456 +0.009288 +0.009215 +0.009274 +0.009353 +0.009446 +0.00952 +0.00935 +0.009279 +0.009329 +0.009394 +0.009494 +0.009554 +0.009395 +0.009309 +0.009364 +0.009455 +0.009569 +0.009628 +0.009461 +0.009361 +0.009405 +0.0095 +0.009593 +0.009666 +0.009493 +0.00941 +0.009451 +0.009547 +0.009653 +0.009714 +0.009554 +0.009469 +0.009516 +0.009607 +0.009691 +0.009758 +0.009574 +0.009494 +0.009568 +0.009666 +0.009766 +0.009832 +0.00967 +0.009587 +0.009607 +0.009689 +0.009787 +0.009854 +0.00969 +0.009602 +0.00966 +0.009774 +0.009857 +0.009902 +0.009747 +0.009652 +0.009717 +0.009814 +0.009895 +0.009968 +0.009807 +0.009711 +0.009761 +0.009855 +0.009953 +0.010016 +0.009841 +0.00977 +0.009856 +0.009915 +0.010027 +0.01006 +0.00988 +0.009805 +0.009856 +0.009943 +0.010078 +0.010114 +0.009946 +0.009893 +0.009915 +0.010027 +0.010117 +0.010163 +0.009993 +0.009909 +0.009967 +0.010049 +0.010167 +0.010241 +0.010053 +0.009973 +0.010043 +0.010149 +0.010216 +0.010259 +0.010093 +0.010034 +0.010074 +0.010151 +0.01027 +0.010339 +0.010169 +0.010076 +0.010128 +0.010213 +0.010319 +0.010393 +0.010199 +0.010111 +0.01019 +0.010284 +0.010392 +0.010463 +0.010257 +0.010155 +0.010237 +0.010345 +0.010468 +0.01047 +0.010264 +0.010188 +0.010264 +0.010355 +0.010383 +0.010418 +0.010193 +0.010081 +0.010122 +0.010188 +0.010248 +0.010229 +0.010017 +0.009867 +0.009906 +0.009938 +0.009966 +0.009988 +0.009741 +0.009624 +0.00967 +0.009699 +0.009714 +0.009731 +0.009521 +0.009415 +0.009402 +0.009422 +0.009459 +0.009467 +0.009273 +0.009155 +0.009145 +0.009181 +0.009187 +0.0092 +0.009002 +0.008897 +0.008915 +0.00895 +0.009005 +0.009016 +0.008823 +0.008718 +0.00875 +0.008782 +0.008813 +0.008844 +0.008655 +0.008565 +0.008598 +0.008663 +0.008713 +0.00874 +0.008537 +0.008453 +0.008492 +0.008553 +0.0086 +0.008634 +0.008469 +0.008406 +0.008439 +0.008489 +0.008567 +0.0086 +0.008456 +0.008384 +0.008445 +0.008523 +0.008587 +0.008653 +0.00851 +0.008439 +0.008467 +0.008561 +0.008652 +0.008705 +0.008539 +0.008473 +0.008536 +0.008614 +0.008724 +0.008731 +0.008577 +0.008511 +0.008569 +0.008646 +0.008744 +0.008797 +0.00863 +0.008564 +0.008616 +0.008706 +0.008765 +0.00883 +0.008673 +0.008604 +0.008665 +0.008743 +0.008826 +0.008881 +0.00874 +0.008648 +0.0087 +0.008779 +0.00886 +0.008922 +0.008772 +0.008701 +0.008771 +0.008856 +0.008911 +0.008992 +0.008812 +0.008728 +0.008797 +0.008863 +0.008952 +0.009017 +0.008868 +0.008788 +0.008851 +0.008931 +0.009005 +0.009067 +0.008916 +0.008836 +0.008876 +0.008958 +0.009054 +0.009107 +0.008965 +0.008888 +0.008935 +0.009007 +0.009088 +0.009168 +0.009023 +0.008943 +0.008971 +0.009052 +0.009138 +0.009237 +0.009058 +0.008973 +0.009029 +0.009113 +0.009187 +0.00924 +0.009091 +0.009021 +0.009072 +0.009162 +0.00924 +0.009292 +0.009138 +0.00906 +0.009117 +0.009198 +0.009286 +0.009365 +0.009192 +0.009119 +0.009172 +0.009273 +0.009364 +0.009385 +0.009232 +0.009143 +0.009208 +0.009306 +0.009389 +0.00946 +0.009288 +0.009202 +0.009263 +0.009356 +0.00943 +0.00949 +0.009331 +0.009256 +0.009327 +0.00939 +0.009487 +0.009551 +0.009383 +0.009296 +0.009376 +0.009437 +0.009529 +0.009609 +0.009455 +0.009374 +0.009399 +0.009483 +0.009579 +0.009645 +0.009488 +0.009405 +0.009472 +0.009565 +0.009633 +0.009685 +0.009533 +0.009443 +0.009497 +0.009585 +0.009688 +0.009749 +0.009586 +0.009524 +0.009565 +0.009637 +0.00974 +0.009793 +0.00963 +0.009554 +0.009617 +0.009719 +0.00981 +0.009849 +0.009681 +0.009605 +0.009645 +0.009731 +0.009831 +0.009905 +0.009721 +0.009644 +0.009717 +0.009788 +0.0099 +0.009984 +0.009788 +0.009688 +0.009759 +0.009836 +0.009942 +0.010008 +0.009835 +0.009741 +0.009807 +0.009905 +0.010031 +0.010068 +0.009876 +0.009796 +0.009867 +0.009946 +0.010035 +0.010121 +0.009955 +0.009838 +0.009902 +0.009998 +0.010089 +0.010164 +0.009994 +0.009898 +0.009964 +0.010053 +0.010172 +0.010232 +0.010027 +0.00995 +0.010005 +0.010094 +0.010223 +0.0103 +0.010108 +0.010007 +0.010072 +0.010162 +0.010289 +0.010303 +0.010123 +0.010055 +0.010109 +0.010203 +0.010313 +0.010374 +0.010211 +0.010137 +0.010173 +0.010261 +0.010355 +0.010424 +0.010245 +0.010172 +0.010216 +0.010306 +0.010432 +0.010518 +0.010332 +0.010203 +0.010274 +0.010357 +0.010476 +0.010534 +0.010369 +0.010297 +0.010315 +0.010415 +0.010535 +0.010577 +0.010412 +0.010327 +0.010372 +0.010504 +0.01058 +0.010602 +0.010384 +0.010263 +0.010279 +0.010334 +0.010407 +0.010449 +0.01022 +0.01009 +0.010134 +0.01014 +0.01017 +0.010155 +0.00993 +0.009808 +0.009812 +0.00989 +0.009912 +0.00989 +0.009679 +0.009557 +0.009555 +0.009596 +0.009647 +0.009658 +0.009452 +0.009315 +0.009329 +0.009373 +0.009409 +0.009425 +0.009208 +0.009096 +0.009103 +0.009147 +0.009231 +0.009212 +0.009015 +0.008902 +0.008933 +0.009 +0.009048 +0.009074 +0.008887 +0.008769 +0.008809 +0.00884 +0.008906 +0.008929 +0.008758 +0.008648 +0.008687 +0.008739 +0.008801 +0.008841 +0.008662 +0.008549 +0.008584 +0.00865 +0.008704 +0.008758 +0.008598 +0.008511 +0.008569 +0.008633 +0.00871 +0.008773 +0.008618 +0.008547 +0.008586 +0.008651 +0.008732 +0.008797 +0.008656 +0.008586 +0.008625 +0.008734 +0.008793 +0.008851 +0.008708 +0.008636 +0.008677 +0.008743 +0.008837 +0.008889 +0.008742 +0.008671 +0.008732 +0.008796 +0.008881 +0.008948 +0.008799 +0.008744 +0.00879 +0.008831 +0.008905 +0.008998 +0.008865 +0.008764 +0.008818 +0.008885 +0.008971 +0.009027 +0.008878 +0.008808 +0.008862 +0.008933 +0.009019 +0.009103 +0.008932 +0.008861 +0.008918 +0.008988 +0.009063 +0.009121 +0.008979 +0.0089 +0.008949 +0.009044 +0.009127 +0.009198 +0.009015 +0.00893 +0.008987 +0.009072 +0.009164 +0.009233 +0.009078 +0.008987 +0.009046 +0.009131 +0.009212 +0.009278 +0.00911 +0.009037 +0.009094 +0.009183 +0.009254 +0.009323 +0.009167 +0.009089 +0.009151 +0.009237 +0.009314 +0.009366 +0.009191 +0.009142 +0.009223 +0.009277 +0.009341 +0.009412 +0.009245 +0.009178 +0.009228 +0.009311 +0.009405 +0.009495 +0.009298 +0.009227 +0.009293 +0.009363 +0.009453 +0.009528 +0.009349 +0.00927 +0.00933 +0.009417 +0.009513 +0.009566 +0.009421 +0.00932 +0.009385 +0.009477 +0.009574 +0.009643 +0.009465 +0.009348 +0.009417 +0.009505 +0.009611 +0.009653 +0.009501 +0.009427 +0.009474 +0.009577 +0.009663 +0.009702 +0.009543 +0.009465 +0.009538 +0.00961 +0.009702 +0.009781 +0.009607 +0.00953 +0.009596 +0.009677 +0.009746 +0.009805 +0.009641 +0.009597 +0.009628 +0.009706 +0.009819 +0.009859 +0.009715 +0.009615 +0.009675 +0.009758 +0.009843 +0.009912 +0.009752 +0.009668 +0.009721 +0.009811 +0.009912 +0.009992 +0.009813 +0.00973 +0.009772 +0.009859 +0.009965 +0.010023 +0.009843 +0.009768 +0.009835 +0.009943 +0.010002 +0.010066 +0.009899 +0.009825 +0.009889 +0.009956 +0.010064 +0.010131 +0.009966 +0.009861 +0.009927 +0.010018 +0.010122 +0.01019 +0.010003 +0.009918 +0.009989 +0.010082 +0.010191 +0.010249 +0.010052 +0.009969 +0.010026 +0.010128 +0.010249 +0.010276 +0.010108 +0.010021 +0.010099 +0.010184 +0.01031 +0.010335 +0.010143 +0.010062 +0.010138 +0.010223 +0.010323 +0.010401 +0.010211 +0.010144 +0.010212 +0.010284 +0.010385 +0.010445 +0.010267 +0.010184 +0.010235 +0.010334 +0.01049 +0.010504 +0.010321 +0.010237 +0.010278 +0.010388 +0.010485 +0.010551 +0.010379 +0.010277 +0.010362 +0.010462 +0.010559 +0.010622 +0.010425 +0.010321 +0.010397 +0.010494 +0.010607 +0.010663 +0.010484 +0.010419 +0.010472 +0.010564 +0.010696 +0.010694 +0.010537 +0.010429 +0.010505 +0.010601 +0.010688 +0.010763 +0.01057 +0.010457 +0.010516 +0.010557 +0.01064 +0.010682 +0.010441 +0.010312 +0.010357 +0.010397 +0.010443 +0.010476 +0.010234 +0.010089 +0.010099 +0.010136 +0.010183 +0.010202 +0.010017 +0.009842 +0.009872 +0.009874 +0.009919 +0.009945 +0.009718 +0.009594 +0.009621 +0.009646 +0.009673 +0.009702 +0.009483 +0.00935 +0.009365 +0.009401 +0.009434 +0.00945 +0.009255 +0.009139 +0.00915 +0.009191 +0.009222 +0.009254 +0.009062 +0.008952 +0.008987 +0.009023 +0.009044 +0.009066 +0.008906 +0.008798 +0.008813 +0.008857 +0.008907 +0.008949 +0.008779 +0.008683 +0.00869 +0.008754 +0.008807 +0.008838 +0.008683 +0.008595 +0.008627 +0.008691 +0.008771 +0.008832 +0.008673 +0.008611 +0.00866 +0.008706 +0.008794 +0.008864 +0.00871 +0.008661 +0.008684 +0.008759 +0.008841 +0.008903 +0.008756 +0.008699 +0.008732 +0.008804 +0.008893 +0.008948 +0.008803 +0.008731 +0.008792 +0.008863 +0.008932 +0.009006 +0.008852 +0.008773 +0.008827 +0.008901 +0.009001 +0.009048 +0.008899 +0.008823 +0.008876 +0.008947 +0.009041 +0.009115 +0.008929 +0.008857 +0.008905 +0.008985 +0.009083 +0.009136 +0.008987 +0.008929 +0.008991 +0.009039 +0.009132 +0.009192 +0.009021 +0.008949 +0.009006 +0.009097 +0.009172 +0.009232 +0.009089 +0.009023 +0.009062 +0.009146 +0.009228 +0.009276 +0.009116 +0.009051 +0.009115 +0.009225 +0.009267 +0.00933 +0.009159 +0.009088 +0.009152 +0.009224 +0.009319 +0.009383 +0.009219 +0.009147 +0.009194 +0.009288 +0.009369 +0.009437 +0.009287 +0.009189 +0.009245 +0.009335 +0.009413 +0.00948 +0.009318 +0.009246 +0.009301 +0.009393 +0.009495 +0.009549 +0.009384 +0.009273 +0.009348 +0.009419 +0.009507 +0.009574 +0.009405 +0.009334 +0.009408 +0.00949 +0.009578 +0.009652 +0.009456 +0.009378 +0.009441 +0.009526 +0.009612 +0.009677 +0.009518 +0.009433 +0.009519 +0.009604 +0.009675 +0.009729 +0.009556 +0.009483 +0.009569 +0.009621 +0.009703 +0.00977 +0.009627 +0.00957 +0.009597 +0.00968 +0.009784 +0.009811 +0.009653 +0.009584 +0.009632 +0.009734 +0.009841 +0.009881 +0.009713 +0.009629 +0.009696 +0.009772 +0.009878 +0.009945 +0.009763 +0.009687 +0.009746 +0.009861 +0.009944 +0.009976 +0.009814 +0.009727 +0.009794 +0.009873 +0.01 +0.010043 +0.009866 +0.00978 +0.009859 +0.009926 +0.010028 +0.010094 +0.009925 +0.009847 +0.00988 +0.009993 +0.010071 +0.010159 +0.009985 +0.009898 +0.009935 +0.010041 +0.010153 +0.010229 +0.010012 +0.009922 +0.009992 +0.010093 +0.010213 +0.010267 +0.01008 +0.01 +0.010036 +0.010131 +0.010241 +0.010302 +0.010119 +0.010051 +0.010103 +0.010215 +0.01033 +0.010351 +0.010168 +0.01009 +0.010148 +0.010246 +0.010356 +0.010441 +0.010252 +0.010171 +0.010205 +0.010309 +0.010401 +0.01045 +0.010272 +0.010204 +0.010266 +0.010346 +0.010475 +0.010547 +0.010349 +0.010271 +0.010319 +0.01039 +0.010511 +0.010567 +0.010401 +0.010319 +0.010358 +0.010457 +0.010577 +0.010659 +0.010462 +0.010341 +0.010406 +0.010523 +0.010632 +0.010683 +0.010504 +0.010418 +0.010471 +0.010571 +0.010697 +0.010759 +0.010532 +0.010446 +0.010506 +0.010599 +0.010675 +0.010654 +0.010436 +0.010291 +0.010358 +0.010374 +0.010423 +0.010441 +0.010224 +0.010073 +0.010076 +0.01011 +0.010166 +0.010184 +0.009922 +0.009805 +0.009816 +0.009865 +0.009923 +0.009917 +0.009701 +0.009578 +0.009597 +0.009614 +0.00965 +0.009664 +0.009443 +0.009326 +0.009339 +0.009382 +0.009426 +0.009446 +0.009239 +0.009115 +0.009123 +0.009182 +0.009253 +0.009246 +0.009037 +0.008923 +0.008953 +0.009038 +0.009064 +0.009078 +0.008906 +0.008802 +0.008812 +0.00887 +0.008926 +0.008957 +0.008769 +0.00867 +0.008702 +0.008764 +0.008814 +0.008849 +0.008682 +0.008579 +0.00862 +0.008678 +0.008742 +0.008783 +0.008623 +0.008543 +0.008604 +0.008684 +0.008738 +0.00882 +0.008667 +0.008592 +0.008645 +0.008708 +0.008777 +0.008857 +0.008704 +0.00863 +0.008677 +0.008756 +0.008841 +0.008894 +0.008749 +0.00868 +0.008737 +0.0088 +0.008883 +0.008947 +0.008795 +0.008716 +0.008776 +0.00886 +0.008935 +0.008988 +0.008845 +0.008791 +0.008818 +0.008887 +0.008962 +0.009029 +0.008885 +0.008805 +0.008861 +0.008958 +0.009045 +0.009099 +0.008919 +0.00885 +0.008901 +0.008978 +0.009067 +0.009134 +0.008973 +0.008901 +0.008963 +0.009048 +0.009132 +0.009173 +0.009029 +0.008948 +0.008995 +0.00908 +0.009166 +0.00925 +0.009069 +0.009011 +0.009049 +0.00913 +0.00923 +0.009273 +0.009106 +0.009037 +0.009091 +0.00917 +0.009263 +0.009326 +0.009159 +0.009087 +0.009148 +0.009219 +0.00931 +0.009383 +0.009208 +0.00913 +0.009188 +0.009278 +0.009376 +0.009434 +0.009263 +0.009184 +0.009263 +0.009322 +0.009395 +0.009458 +0.009304 +0.009231 +0.009286 +0.009383 +0.009476 +0.009536 +0.00935 +0.009272 +0.00933 +0.009422 +0.009511 +0.00957 +0.009414 +0.009331 +0.009399 +0.009469 +0.009565 +0.009605 +0.009455 +0.009375 +0.009424 +0.009521 +0.009639 +0.009674 +0.009489 +0.009414 +0.009461 +0.009562 +0.009662 +0.009721 +0.009556 +0.009482 +0.009534 +0.009625 +0.009716 +0.009761 +0.009599 +0.009524 +0.009594 +0.009659 +0.009754 +0.009828 +0.009656 +0.009565 +0.009647 +0.009719 +0.009801 +0.009885 +0.009718 +0.00964 +0.009668 +0.00975 +0.009856 +0.00993 +0.009782 +0.009681 +0.009762 +0.0098 +0.009902 +0.009962 +0.0098 +0.009714 +0.009765 +0.009867 +0.009966 +0.010052 +0.009883 +0.009775 +0.009815 +0.009919 +0.010009 +0.010077 +0.009907 +0.009826 +0.009933 +0.009988 +0.01007 +0.010115 +0.009963 +0.009868 +0.009923 +0.010017 +0.010122 +0.010193 +0.009994 +0.009933 +0.009995 +0.010083 +0.0102 +0.010236 +0.010051 +0.00998 +0.010033 +0.010122 +0.010231 +0.010286 +0.010109 +0.01003 +0.0101 +0.010216 +0.010273 +0.010327 +0.010157 +0.010087 +0.010145 +0.010241 +0.010333 +0.010409 +0.010219 +0.010131 +0.010231 +0.010269 +0.010386 +0.010452 +0.010277 +0.01021 +0.010262 +0.010326 +0.010441 +0.010514 +0.01032 +0.010229 +0.010301 +0.010444 +0.010522 +0.010565 +0.010365 +0.010279 +0.010358 +0.010445 +0.010546 +0.010621 +0.010425 +0.010367 +0.010427 +0.010516 +0.010652 +0.010669 +0.010463 +0.010393 +0.01045 +0.01056 +0.010684 +0.010718 +0.010543 +0.010455 +0.010524 +0.010657 +0.010685 +0.010752 +0.010562 +0.010477 +0.010528 +0.010583 +0.010599 +0.010619 +0.010414 +0.01028 +0.010287 +0.010343 +0.010391 +0.010401 +0.010176 +0.010034 +0.010017 +0.010065 +0.01009 +0.010107 +0.009898 +0.009745 +0.009783 +0.009822 +0.009842 +0.009874 +0.009614 +0.009484 +0.009478 +0.009516 +0.009551 +0.009568 +0.009398 +0.009251 +0.009237 +0.009277 +0.009319 +0.00933 +0.009141 +0.009029 +0.009051 +0.009093 +0.009133 +0.009165 +0.008992 +0.008883 +0.008886 +0.008942 +0.008979 +0.009014 +0.008847 +0.008743 +0.00879 +0.008817 +0.008879 +0.008899 +0.00873 +0.008645 +0.008664 +0.008704 +0.008764 +0.008814 +0.008667 +0.008559 +0.008606 +0.008663 +0.008735 +0.008804 +0.008663 +0.008587 +0.008636 +0.008687 +0.008786 +0.008831 +0.008685 +0.008619 +0.008669 +0.008743 +0.008842 +0.008903 +0.008753 +0.008687 +0.00872 +0.008786 +0.008869 +0.008912 +0.008779 +0.008698 +0.008762 +0.008844 +0.00893 +0.00897 +0.008821 +0.008777 +0.008805 +0.008883 +0.008955 +0.009024 +0.008868 +0.008807 +0.008865 +0.008928 +0.009014 +0.009089 +0.008902 +0.008836 +0.008891 +0.008975 +0.009068 +0.009148 +0.008961 +0.00889 +0.008943 +0.009049 +0.009113 +0.009156 +0.008995 +0.008927 +0.008981 +0.009062 +0.009148 +0.009231 +0.009048 +0.008978 +0.009036 +0.009117 +0.009197 +0.009266 +0.009108 +0.009027 +0.009083 +0.009172 +0.009265 +0.009327 +0.009146 +0.009073 +0.009155 +0.009221 +0.009288 +0.009349 +0.009183 +0.009128 +0.009192 +0.009284 +0.009355 +0.009418 +0.009242 +0.009156 +0.009222 +0.009295 +0.009391 +0.009456 +0.009317 +0.009215 +0.009265 +0.009362 +0.009438 +0.009508 +0.009349 +0.00926 +0.009315 +0.009403 +0.009502 +0.009595 +0.009394 +0.009307 +0.009364 +0.009447 +0.009551 +0.009612 +0.009432 +0.009375 +0.009421 +0.009497 +0.009606 +0.009667 +0.009476 +0.009401 +0.009475 +0.009555 +0.009638 +0.00971 +0.009538 +0.009453 +0.009513 +0.009617 +0.00971 +0.009751 +0.00959 +0.009539 +0.009598 +0.00964 +0.009727 +0.009792 +0.009636 +0.009555 +0.009626 +0.009715 +0.009811 +0.009848 +0.009683 +0.009601 +0.009661 +0.009749 +0.009848 +0.009918 +0.009755 +0.009677 +0.009719 +0.009807 +0.009889 +0.009953 +0.009783 +0.009706 +0.009771 +0.009892 +0.00997 +0.010017 +0.009868 +0.00975 +0.009818 +0.009876 +0.009989 +0.010061 +0.009881 +0.009817 +0.009887 +0.009955 +0.010043 +0.010117 +0.009943 +0.009849 +0.009914 +0.010004 +0.010106 +0.010178 +0.010016 +0.00992 +0.009958 +0.010066 +0.010191 +0.010218 +0.010036 +0.009947 +0.010016 +0.010143 +0.010217 +0.010283 +0.010111 +0.009987 +0.010057 +0.010173 +0.010264 +0.01033 +0.010155 +0.010072 +0.010137 +0.010237 +0.010325 +0.010377 +0.010191 +0.010105 +0.010179 +0.010282 +0.010391 +0.01047 +0.010259 +0.010163 +0.010234 +0.010336 +0.010413 +0.010491 +0.010305 +0.010208 +0.010288 +0.010383 +0.010467 +0.01054 +0.01036 +0.010283 +0.010355 +0.010428 +0.010543 +0.010596 +0.01042 +0.01034 +0.010381 +0.010496 +0.010608 +0.01069 +0.010455 +0.010368 +0.010432 +0.010536 +0.010651 +0.010725 +0.010547 +0.010441 +0.010487 +0.010596 +0.010675 +0.010739 +0.010555 +0.010455 +0.010511 +0.010585 +0.010635 +0.010641 +0.010409 +0.010287 +0.010329 +0.010375 +0.010405 +0.010422 +0.010252 +0.010089 +0.010062 +0.010103 +0.010122 +0.010145 +0.009921 +0.009795 +0.009818 +0.009854 +0.009889 +0.009908 +0.009675 +0.009559 +0.009573 +0.009575 +0.009606 +0.009624 +0.009417 +0.009299 +0.009318 +0.009336 +0.00936 +0.009385 +0.009201 +0.009087 +0.009126 +0.009129 +0.009175 +0.009188 +0.009009 +0.008907 +0.008938 +0.008962 +0.009012 +0.009033 +0.008858 +0.008762 +0.008778 +0.008817 +0.008873 +0.008916 +0.008739 +0.008644 +0.008672 +0.00871 +0.008757 +0.008797 +0.008647 +0.008555 +0.008591 +0.00865 +0.008715 +0.008769 +0.008624 +0.008572 +0.008625 +0.008712 +0.008745 +0.00883 +0.008648 +0.008578 +0.008639 +0.008714 +0.00879 +0.008863 +0.008727 +0.008638 +0.008696 +0.008782 +0.008852 +0.008889 +0.008748 +0.008683 +0.00873 +0.008808 +0.008891 +0.008955 +0.008818 +0.008738 +0.008799 +0.008874 +0.008948 +0.008978 +0.008838 +0.008786 +0.008833 +0.008884 +0.008981 +0.009033 +0.008923 +0.008814 +0.00886 +0.00894 +0.009036 +0.009083 +0.008941 +0.008867 +0.008925 +0.008989 +0.009084 +0.00915 +0.008982 +0.008909 +0.008964 +0.009047 +0.009127 +0.009182 +0.009044 +0.00896 +0.009008 +0.009093 +0.009197 +0.009262 +0.009076 +0.008994 +0.009044 +0.009128 +0.009228 +0.009287 +0.009142 +0.009042 +0.009104 +0.009189 +0.009281 +0.009344 +0.009164 +0.00909 +0.009149 +0.009232 +0.009317 +0.009375 +0.009227 +0.00915 +0.009209 +0.009296 +0.009377 +0.009421 +0.009269 +0.009196 +0.009273 +0.009331 +0.009405 +0.009461 +0.009315 +0.009255 +0.009301 +0.009382 +0.009478 +0.009522 +0.009366 +0.009283 +0.009357 +0.009411 +0.00951 +0.009598 +0.009425 +0.009341 +0.009418 +0.009486 +0.009555 +0.009615 +0.009466 +0.009375 +0.009435 +0.009545 +0.009626 +0.0097 +0.009518 +0.009415 +0.009478 +0.00957 +0.009665 +0.009721 +0.009569 +0.00949 +0.009544 +0.009624 +0.009724 +0.009783 +0.009606 +0.009528 +0.009589 +0.009668 +0.009782 +0.009844 +0.009684 +0.009591 +0.009649 +0.009706 +0.009809 +0.009887 +0.009727 +0.009649 +0.00968 +0.009765 +0.009883 +0.009934 +0.009777 +0.009685 +0.009725 +0.009824 +0.009914 +0.009985 +0.009826 +0.009728 +0.009789 +0.009907 +0.009995 +0.010049 +0.009874 +0.009775 +0.009826 +0.009921 +0.010022 +0.010086 +0.009916 +0.009834 +0.009946 +0.009991 +0.010083 +0.010141 +0.009951 +0.009908 +0.009929 +0.010023 +0.010135 +0.010194 +0.010011 +0.009948 +0.009987 +0.010076 +0.010203 +0.010232 +0.010078 +0.010001 +0.010057 +0.010139 +0.010256 +0.010297 +0.010122 +0.010046 +0.010111 +0.010233 +0.010276 +0.010346 +0.010163 +0.010111 +0.01017 +0.010239 +0.010369 +0.0104 +0.010217 +0.010149 +0.010212 +0.01029 +0.010404 +0.010471 +0.010309 +0.01021 +0.010248 +0.010349 +0.010455 +0.010533 +0.01034 +0.010241 +0.010319 +0.010475 +0.010524 +0.01057 +0.01037 +0.010288 +0.010366 +0.010456 +0.010567 +0.010632 +0.010444 +0.01037 +0.01045 +0.010529 +0.01063 +0.010675 +0.010492 +0.010407 +0.010492 +0.010562 +0.010682 +0.010758 +0.010565 +0.010473 +0.010545 +0.010663 +0.010705 +0.010791 +0.010547 +0.010493 +0.010544 +0.010599 +0.010667 +0.01069 +0.010483 +0.010376 +0.010386 +0.010423 +0.010465 +0.010496 +0.010276 +0.010146 +0.010147 +0.010166 +0.010217 +0.010215 +0.009999 +0.009875 +0.009883 +0.009939 +0.009981 +0.009999 +0.009775 +0.009643 +0.009612 +0.009649 +0.009698 +0.009694 +0.009504 +0.009393 +0.009407 +0.009415 +0.009452 +0.009474 +0.009271 +0.009157 +0.009179 +0.00921 +0.009255 +0.009274 +0.009101 +0.00899 +0.009002 +0.009045 +0.009079 +0.009106 +0.008932 +0.008829 +0.008879 +0.008907 +0.008951 +0.008981 +0.008823 +0.00871 +0.008739 +0.008772 +0.00883 +0.008873 +0.008705 +0.008619 +0.00866 +0.00872 +0.008785 +0.008844 +0.008697 +0.008616 +0.008674 +0.00872 +0.008815 +0.008871 +0.008725 +0.008653 +0.008695 +0.008774 +0.008874 +0.008939 +0.008789 +0.008721 +0.008745 +0.008824 +0.008892 +0.008956 +0.008817 +0.008741 +0.008791 +0.008877 +0.008951 +0.00901 +0.008859 +0.008797 +0.008843 +0.008922 +0.008993 +0.009093 +0.008903 +0.008823 +0.008886 +0.008965 +0.009031 +0.009106 +0.008953 +0.008875 +0.00893 +0.00902 +0.009103 +0.009182 +0.008993 +0.008928 +0.008967 +0.009049 +0.009148 +0.009195 +0.009046 +0.00897 +0.009034 +0.009104 +0.009187 +0.009271 +0.009091 +0.009007 +0.00907 +0.009144 +0.009236 +0.009329 +0.009134 +0.009058 +0.009112 +0.009203 +0.009281 +0.009353 +0.009187 +0.009111 +0.009196 +0.009267 +0.009324 +0.009379 +0.009228 +0.00916 +0.009206 +0.009295 +0.009395 +0.009459 +0.009273 +0.009201 +0.009264 +0.009342 +0.009429 +0.009497 +0.009328 +0.009249 +0.009306 +0.009405 +0.009499 +0.009533 +0.009385 +0.009294 +0.009352 +0.009449 +0.009543 +0.00962 +0.009423 +0.009354 +0.009418 +0.00949 +0.009591 +0.009623 +0.00947 +0.0094 +0.009463 +0.009533 +0.009632 +0.009699 +0.009529 +0.009457 +0.009513 +0.009601 +0.009673 +0.009743 +0.009569 +0.009493 +0.009561 +0.009632 +0.009734 +0.00981 +0.009643 +0.009586 +0.009605 +0.009691 +0.009762 +0.009833 +0.009697 +0.00958 +0.009656 +0.00974 +0.009831 +0.009892 +0.009726 +0.009645 +0.009699 +0.009791 +0.009886 +0.009949 +0.009778 +0.009705 +0.009757 +0.009835 +0.00994 +0.009996 +0.00983 +0.009753 +0.009819 +0.009917 +0.009986 +0.010058 +0.009885 +0.009794 +0.009837 +0.009942 +0.01004 +0.010114 +0.009933 +0.009841 +0.009918 +0.010009 +0.010104 +0.010171 +0.010011 +0.009879 +0.009952 +0.010037 +0.010139 +0.010222 +0.010046 +0.009932 +0.010014 +0.010118 +0.010224 +0.010252 +0.010068 +0.009998 +0.010067 +0.010167 +0.01026 +0.010325 +0.010139 +0.010044 +0.01011 +0.010212 +0.010305 +0.010374 +0.010192 +0.010111 +0.010187 +0.010303 +0.010348 +0.010415 +0.010238 +0.010149 +0.010214 +0.010319 +0.010462 +0.010499 +0.010313 +0.010208 +0.010267 +0.010354 +0.010462 +0.010529 +0.010361 +0.010261 +0.010323 +0.01044 +0.01054 +0.010602 +0.010415 +0.010305 +0.01037 +0.010475 +0.010575 +0.010652 +0.010468 +0.010362 +0.010434 +0.01054 +0.010684 +0.010693 +0.010501 +0.010436 +0.010494 +0.010579 +0.010692 +0.010753 +0.010581 +0.010472 +0.010547 +0.010648 +0.010745 +0.010806 +0.010595 +0.010522 +0.010604 +0.010643 +0.010717 +0.010754 +0.010515 +0.010387 +0.010422 +0.010488 +0.010536 +0.010548 +0.010361 +0.01018 +0.010182 +0.010197 +0.01025 +0.010275 +0.01005 +0.009905 +0.009942 +0.009985 +0.010027 +0.010047 +0.009845 +0.00966 +0.009697 +0.009726 +0.009766 +0.009783 +0.009551 +0.009428 +0.009463 +0.009502 +0.009548 +0.00955 +0.009343 +0.009231 +0.009249 +0.009274 +0.009321 +0.009334 +0.009143 +0.009038 +0.009092 +0.009127 +0.009176 +0.00919 +0.008987 +0.008883 +0.008912 +0.008967 +0.009013 +0.009068 +0.008846 +0.008754 +0.008815 +0.008859 +0.008912 +0.008938 +0.008772 +0.008656 +0.008713 +0.008789 +0.008844 +0.008915 +0.008731 +0.008646 +0.008715 +0.008808 +0.008875 +0.008947 +0.008795 +0.008705 +0.008754 +0.008826 +0.008913 +0.008972 +0.008825 +0.008738 +0.0088 +0.0089 +0.008974 +0.009035 +0.008871 +0.008804 +0.008834 +0.008921 +0.009017 +0.009064 +0.008918 +0.008841 +0.008897 +0.009015 +0.009102 +0.009114 +0.008953 +0.008884 +0.00893 +0.009011 +0.009105 +0.009155 +0.009007 +0.008944 +0.008994 +0.009056 +0.009148 +0.009209 +0.009056 +0.008988 +0.009022 +0.00911 +0.009205 +0.009268 +0.009106 +0.009034 +0.009082 +0.009156 +0.009252 +0.009318 +0.009162 +0.009101 +0.009112 +0.0092 +0.009286 +0.009361 +0.00922 +0.009123 +0.009176 +0.009246 +0.009344 +0.009404 +0.009248 +0.009198 +0.009213 +0.009294 +0.009397 +0.009455 +0.009306 +0.009229 +0.009276 +0.009342 +0.009437 +0.009512 +0.009338 +0.009262 +0.009321 +0.009418 +0.009518 +0.009557 +0.009392 +0.009312 +0.009383 +0.009442 +0.009532 +0.009594 +0.009441 +0.009354 +0.009415 +0.009523 +0.009604 +0.009663 +0.009498 +0.009415 +0.009451 +0.009535 +0.009643 +0.009697 +0.009538 +0.009461 +0.009523 +0.009615 +0.009709 +0.009772 +0.009624 +0.009482 +0.009545 +0.009645 +0.009741 +0.009814 +0.00966 +0.009555 +0.009616 +0.009684 +0.00979 +0.009858 +0.009683 +0.009593 +0.009668 +0.009759 +0.009875 +0.00992 +0.009758 +0.009651 +0.009706 +0.009802 +0.009887 +0.009961 +0.009791 +0.009727 +0.009796 +0.009864 +0.009956 +0.01002 +0.009825 +0.009756 +0.009818 +0.009896 +0.010008 +0.010052 +0.009898 +0.009823 +0.009883 +0.00997 +0.010075 +0.0101 +0.009934 +0.009872 +0.00991 +0.010003 +0.010108 +0.010186 +0.010009 +0.009928 +0.009988 +0.010107 +0.010139 +0.010208 +0.010036 +0.009958 +0.010023 +0.010119 +0.010211 +0.010268 +0.010093 +0.010067 +0.01007 +0.010154 +0.010269 +0.010342 +0.010152 +0.010069 +0.01013 +0.01022 +0.010332 +0.010393 +0.010206 +0.010113 +0.010199 +0.010303 +0.010404 +0.010447 +0.010268 +0.010173 +0.010233 +0.010321 +0.01043 +0.010497 +0.010298 +0.010227 +0.010299 +0.010398 +0.010504 +0.010554 +0.010359 +0.010275 +0.010352 +0.010427 +0.010535 +0.010615 +0.010439 +0.010348 +0.010422 +0.010523 +0.010603 +0.01065 +0.010465 +0.010374 +0.010463 +0.010553 +0.010644 +0.010741 +0.010522 +0.01045 +0.010501 +0.010591 +0.010704 +0.01078 +0.010581 +0.010492 +0.010593 +0.010676 +0.010769 +0.010823 +0.010602 +0.010512 +0.010574 +0.010681 +0.010716 +0.010737 +0.010472 +0.010355 +0.010403 +0.01045 +0.010499 +0.010461 +0.010235 +0.010105 +0.010115 +0.010153 +0.010195 +0.01021 +0.00997 +0.009828 +0.009845 +0.009868 +0.009903 +0.009916 +0.00968 +0.009555 +0.009599 +0.009625 +0.009674 +0.009632 +0.009413 +0.009275 +0.009301 +0.00934 +0.009368 +0.009379 +0.009196 +0.009069 +0.009097 +0.009104 +0.009151 +0.00917 +0.008981 +0.008882 +0.008903 +0.008947 +0.008995 +0.009017 +0.008848 +0.008733 +0.008772 +0.008806 +0.008858 +0.008885 +0.008716 +0.008641 +0.008673 +0.008703 +0.008775 +0.008777 +0.008623 +0.008554 +0.008576 +0.008646 +0.008707 +0.00875 +0.008602 +0.008531 +0.008598 +0.008665 +0.008735 +0.008804 +0.008653 +0.008581 +0.008628 +0.0087 +0.008783 +0.008846 +0.008696 +0.008628 +0.008671 +0.008743 +0.008841 +0.008899 +0.008769 +0.008672 +0.008727 +0.008795 +0.008867 +0.008958 +0.008776 +0.008724 +0.008756 +0.00884 +0.008908 +0.00898 +0.008827 +0.008759 +0.008818 +0.008907 +0.008969 +0.009029 +0.00887 +0.008799 +0.008857 +0.008927 +0.009009 +0.009088 +0.008928 +0.008858 +0.008912 +0.00899 +0.009095 +0.009118 +0.00896 +0.008888 +0.008941 +0.00902 +0.009108 +0.009183 +0.009016 +0.008955 +0.008995 +0.009076 +0.009163 +0.009205 +0.009056 +0.00898 +0.009041 +0.009119 +0.009196 +0.009288 +0.009126 +0.009039 +0.009092 +0.009175 +0.009251 +0.009302 +0.009159 +0.009091 +0.009148 +0.009208 +0.00931 +0.009348 +0.009198 +0.009127 +0.009174 +0.009265 +0.009357 +0.009421 +0.009249 +0.009167 +0.009223 +0.009305 +0.009407 +0.009467 +0.009295 +0.009221 +0.009287 +0.009355 +0.009448 +0.009509 +0.009354 +0.009287 +0.009331 +0.009425 +0.009527 +0.009563 +0.009405 +0.009301 +0.009362 +0.009451 +0.009544 +0.009607 +0.009448 +0.009383 +0.009423 +0.009515 +0.0096 +0.009645 +0.009487 +0.009413 +0.009465 +0.009553 +0.009653 +0.009734 +0.009553 +0.009472 +0.009531 +0.009601 +0.009681 +0.009765 +0.009603 +0.009539 +0.009575 +0.00966 +0.009757 +0.009793 +0.009636 +0.00955 +0.009614 +0.009698 +0.009792 +0.00986 +0.009703 +0.00961 +0.009683 +0.009757 +0.009849 +0.009917 +0.009747 +0.009662 +0.009711 +0.009803 +0.009913 +0.009981 +0.009806 +0.009734 +0.009789 +0.009873 +0.009937 +0.01001 +0.009834 +0.009756 +0.009839 +0.0099 +0.01002 +0.010082 +0.009902 +0.009799 +0.009868 +0.009957 +0.010056 +0.010133 +0.009941 +0.009868 +0.00995 +0.010022 +0.010125 +0.010192 +0.009978 +0.009908 +0.009971 +0.01009 +0.010184 +0.010211 +0.010041 +0.009983 +0.010048 +0.010128 +0.010236 +0.010297 +0.010073 +0.010003 +0.010076 +0.010162 +0.010276 +0.010334 +0.010155 +0.010076 +0.010139 +0.010214 +0.010324 +0.010395 +0.010216 +0.01014 +0.010215 +0.010283 +0.010375 +0.010444 +0.010287 +0.010186 +0.010221 +0.010317 +0.010453 +0.010522 +0.010329 +0.01026 +0.010294 +0.010357 +0.010464 +0.010553 +0.01036 +0.010294 +0.010347 +0.010429 +0.010545 +0.01062 +0.010418 +0.010327 +0.010414 +0.010498 +0.010596 +0.010682 +0.010523 +0.010392 +0.010442 +0.010532 +0.010657 +0.010718 +0.01052 +0.010443 +0.01053 +0.010655 +0.010696 +0.01074 +0.010551 +0.010458 +0.010519 +0.010571 +0.010624 +0.010649 +0.010442 +0.010297 +0.010325 +0.010381 +0.010413 +0.01042 +0.010199 +0.010113 +0.010123 +0.010114 +0.010149 +0.010148 +0.009906 +0.009786 +0.009806 +0.00985 +0.009872 +0.009896 +0.009681 +0.009543 +0.009563 +0.0096 +0.009618 +0.009604 +0.009407 +0.009293 +0.00932 +0.009345 +0.009407 +0.009392 +0.009191 +0.009094 +0.009113 +0.009184 +0.009192 +0.009198 +0.009021 +0.008925 +0.008959 +0.008992 +0.009052 +0.009055 +0.008879 +0.008775 +0.008803 +0.008858 +0.008906 +0.008923 +0.008752 +0.008658 +0.008691 +0.008735 +0.00881 +0.008824 +0.008648 +0.008565 +0.008619 +0.008689 +0.008725 +0.008773 +0.008621 +0.008578 +0.008622 +0.008684 +0.008751 +0.008838 +0.008662 +0.008601 +0.008642 +0.008718 +0.008809 +0.008859 +0.00871 +0.008642 +0.008702 +0.008776 +0.008867 +0.008935 +0.008763 +0.00868 +0.008733 +0.008822 +0.008893 +0.008955 +0.008809 +0.008735 +0.008788 +0.008868 +0.00897 +0.009024 +0.008878 +0.008781 +0.008811 +0.008892 +0.008987 +0.009051 +0.008889 +0.00882 +0.008914 +0.00896 +0.009041 +0.009091 +0.008948 +0.008863 +0.008911 +0.008994 +0.009082 +0.009147 +0.008993 +0.008941 +0.008966 +0.009039 +0.009136 +0.009199 +0.009036 +0.008954 +0.009013 +0.009117 +0.00921 +0.009234 +0.009075 +0.009023 +0.009047 +0.009138 +0.009222 +0.009285 +0.009139 +0.009054 +0.009109 +0.00919 +0.00928 +0.009356 +0.009191 +0.009118 +0.009141 +0.00923 +0.009329 +0.009394 +0.009222 +0.009144 +0.009202 +0.009282 +0.009372 +0.009465 +0.009303 +0.009226 +0.009231 +0.009309 +0.009423 +0.009479 +0.009326 +0.009231 +0.009291 +0.009401 +0.009477 +0.009549 +0.009392 +0.009299 +0.009333 +0.009417 +0.009521 +0.009578 +0.009421 +0.00936 +0.009388 +0.00947 +0.009576 +0.009628 +0.009469 +0.009388 +0.009446 +0.009564 +0.009637 +0.009667 +0.009521 +0.009444 +0.009503 +0.00956 +0.00966 +0.009733 +0.009564 +0.009478 +0.009549 +0.00964 +0.009738 +0.0098 +0.009631 +0.009519 +0.009591 +0.009679 +0.009773 +0.00983 +0.009667 +0.009582 +0.009654 +0.009744 +0.009844 +0.009931 +0.009698 +0.009616 +0.00969 +0.009774 +0.009876 +0.009948 +0.009767 +0.009706 +0.009748 +0.009837 +0.009935 +0.009974 +0.009806 +0.009728 +0.009791 +0.009887 +0.009976 +0.010045 +0.009889 +0.009797 +0.009862 +0.009935 +0.010024 +0.010098 +0.009944 +0.009845 +0.009883 +0.009975 +0.010078 +0.010145 +0.009974 +0.009883 +0.009957 +0.010054 +0.010131 +0.010201 +0.010022 +0.009935 +0.010006 +0.010084 +0.010189 +0.010257 +0.01009 +0.009995 +0.010042 +0.010149 +0.010244 +0.010316 +0.010152 +0.010076 +0.010104 +0.010188 +0.010286 +0.010356 +0.010186 +0.010078 +0.010159 +0.010258 +0.010367 +0.010453 +0.010227 +0.010133 +0.01019 +0.010308 +0.010411 +0.010459 +0.010297 +0.010205 +0.010286 +0.01038 +0.010465 +0.010504 +0.010346 +0.010269 +0.010337 +0.010397 +0.01049 +0.010584 +0.010413 +0.010317 +0.01038 +0.010467 +0.010556 +0.010628 +0.010457 +0.010392 +0.010407 +0.010542 +0.010632 +0.010695 +0.010519 +0.010409 +0.01046 +0.010578 +0.010673 +0.010744 +0.010584 +0.010485 +0.010541 +0.01062 +0.010725 +0.010799 +0.010636 +0.01051 +0.010579 +0.010682 +0.010781 +0.010817 +0.010619 +0.010487 +0.010516 +0.010584 +0.010647 +0.010661 +0.010425 +0.010304 +0.0103 +0.010332 +0.010348 +0.010346 +0.010119 +0.009982 +0.010027 +0.010009 +0.010039 +0.010055 +0.009844 +0.009711 +0.00973 +0.009721 +0.009758 +0.009752 +0.009542 +0.009423 +0.009416 +0.009454 +0.009496 +0.009498 +0.009288 +0.009159 +0.009175 +0.009187 +0.009219 +0.009244 +0.009042 +0.00894 +0.008964 +0.008988 +0.009056 +0.009067 +0.00887 +0.008769 +0.008791 +0.008828 +0.008891 +0.008912 +0.008739 +0.008644 +0.008692 +0.008704 +0.008757 +0.008798 +0.008613 +0.008526 +0.008562 +0.008607 +0.008658 +0.008705 +0.008562 +0.008459 +0.008497 +0.008552 +0.008608 +0.008666 +0.008513 +0.008444 +0.008488 +0.008583 +0.00865 +0.008701 +0.008563 +0.008488 +0.008534 +0.008616 +0.008684 +0.008732 +0.008597 +0.008523 +0.008576 +0.008649 +0.008741 +0.008784 +0.008657 +0.008583 +0.008632 +0.008699 +0.008785 +0.008835 +0.008679 +0.00862 +0.00868 +0.008741 +0.008836 +0.008875 +0.008732 +0.008661 +0.008736 +0.008784 +0.008859 +0.008924 +0.008804 +0.008696 +0.00875 +0.008826 +0.008936 +0.008978 +0.008814 +0.008747 +0.008802 +0.008869 +0.008958 +0.009033 +0.008869 +0.008794 +0.008853 +0.00892 +0.009025 +0.009085 +0.008919 +0.008836 +0.008891 +0.008965 +0.009057 +0.009115 +0.008959 +0.008895 +0.008964 +0.009068 +0.009111 +0.00916 +0.009019 +0.008915 +0.008979 +0.00905 +0.009145 +0.009211 +0.009049 +0.00898 +0.009031 +0.009122 +0.009213 +0.009257 +0.009111 +0.009014 +0.009073 +0.009158 +0.009253 +0.009312 +0.009158 +0.009082 +0.009127 +0.009208 +0.009301 +0.009368 +0.009235 +0.009112 +0.009164 +0.009251 +0.009337 +0.009434 +0.009266 +0.009151 +0.009217 +0.009307 +0.009391 +0.009455 +0.00929 +0.009208 +0.009264 +0.009375 +0.009453 +0.009513 +0.00935 +0.00927 +0.009307 +0.009398 +0.009498 +0.009549 +0.009387 +0.009307 +0.009374 +0.009506 +0.009545 +0.009601 +0.009437 +0.009345 +0.00942 +0.009499 +0.009603 +0.00965 +0.009487 +0.009395 +0.009461 +0.009546 +0.009637 +0.009706 +0.009541 +0.009451 +0.009533 +0.009599 +0.009694 +0.009754 +0.009601 +0.0095 +0.009559 +0.009651 +0.009748 +0.009851 +0.009624 +0.009548 +0.009609 +0.009697 +0.009822 +0.009861 +0.009676 +0.009611 +0.009688 +0.009741 +0.009838 +0.009897 +0.009729 +0.009668 +0.009731 +0.009814 +0.009909 +0.009976 +0.009771 +0.009696 +0.009764 +0.009841 +0.009942 +0.010016 +0.009865 +0.0098 +0.009823 +0.009927 +0.009996 +0.010041 +0.009874 +0.009789 +0.009861 +0.009958 +0.010053 +0.010113 +0.009944 +0.00987 +0.009919 +0.010015 +0.010093 +0.010172 +0.010004 +0.009904 +0.009965 +0.010072 +0.010153 +0.010225 +0.010057 +0.009986 +0.010053 +0.010093 +0.010199 +0.010278 +0.010137 +0.010016 +0.010055 +0.010168 +0.010264 +0.010324 +0.010149 +0.010057 +0.010116 +0.010212 +0.010333 +0.010415 +0.010216 +0.010107 +0.010174 +0.010269 +0.010373 +0.01044 +0.010253 +0.010178 +0.010299 +0.010337 +0.010431 +0.010469 +0.010295 +0.01022 +0.010307 +0.010364 +0.010481 +0.010533 +0.010372 +0.010298 +0.010354 +0.010441 +0.010532 +0.010587 +0.010408 +0.01032 +0.010377 +0.010457 +0.010571 +0.010588 +0.010366 +0.010248 +0.010311 +0.010312 +0.010363 +0.010353 +0.01014 +0.010024 +0.010048 +0.010073 +0.010154 +0.010076 +0.009858 +0.009739 +0.009744 +0.009785 +0.009833 +0.009833 +0.009627 +0.009495 +0.009498 +0.009536 +0.009574 +0.009561 +0.009357 +0.009218 +0.009248 +0.009297 +0.009374 +0.009336 +0.009111 +0.008999 +0.009017 +0.009076 +0.009111 +0.009128 +0.008939 +0.008821 +0.008842 +0.008891 +0.008955 +0.008947 +0.008765 +0.008664 +0.00869 +0.008741 +0.008792 +0.008823 +0.008638 +0.00854 +0.00857 +0.008622 +0.008683 +0.008702 +0.008526 +0.008429 +0.008492 +0.00852 +0.008564 +0.008604 +0.008442 +0.008346 +0.008383 +0.008458 +0.008531 +0.008569 +0.008405 +0.008323 +0.00838 +0.008452 +0.008535 +0.008591 +0.008457 +0.008363 +0.008412 +0.008492 +0.008588 +0.008644 +0.008501 +0.008433 +0.008471 +0.008537 +0.008618 +0.008685 +0.00853 +0.008461 +0.008518 +0.008593 +0.008689 +0.008731 +0.008569 +0.008519 +0.008551 +0.008624 +0.008708 +0.008787 +0.008615 +0.008551 +0.008592 +0.008673 +0.008748 +0.008826 +0.008672 +0.008592 +0.008646 +0.008719 +0.008796 +0.008865 +0.008722 +0.008638 +0.008696 +0.008778 +0.008862 +0.008899 +0.008758 +0.008688 +0.008741 +0.008835 +0.008891 +0.008944 +0.008809 +0.008726 +0.0088 +0.008868 +0.008948 +0.008987 +0.008848 +0.008769 +0.008826 +0.008908 +0.008987 +0.009038 +0.008894 +0.008824 +0.008873 +0.008943 +0.009042 +0.009095 +0.008945 +0.008868 +0.008932 +0.008991 +0.009094 +0.009159 +0.008993 +0.008946 +0.008973 +0.009032 +0.009122 +0.009184 +0.009038 +0.008961 +0.009028 +0.009086 +0.00918 +0.009239 +0.009081 +0.009016 +0.009066 +0.009126 +0.009226 +0.009286 +0.009133 +0.009045 +0.009113 +0.009178 +0.009278 +0.009353 +0.009196 +0.009114 +0.009171 +0.009223 +0.009348 +0.009383 +0.009213 +0.009134 +0.009192 +0.009283 +0.009377 +0.00944 +0.009292 +0.009215 +0.009268 +0.009313 +0.009417 +0.00947 +0.009309 +0.009238 +0.009295 +0.009382 +0.009493 +0.009563 +0.009376 +0.009299 +0.009346 +0.009415 +0.009514 +0.009589 +0.009416 +0.009373 +0.009388 +0.009495 +0.00956 +0.009625 +0.009472 +0.009378 +0.009437 +0.009527 +0.009617 +0.009693 +0.00954 +0.009448 +0.009486 +0.009573 +0.009674 +0.00973 +0.009566 +0.009485 +0.009542 +0.009638 +0.009747 +0.009793 +0.009624 +0.009529 +0.00959 +0.009705 +0.00977 +0.009825 +0.009648 +0.009618 +0.009659 +0.009731 +0.009828 +0.009895 +0.009698 +0.009621 +0.009684 +0.009771 +0.00988 +0.009948 +0.009774 +0.009679 +0.009741 +0.009833 +0.009919 +0.00999 +0.009824 +0.009721 +0.009803 +0.009909 +0.010006 +0.010073 +0.009865 +0.009764 +0.00983 +0.009926 +0.010041 +0.01009 +0.009901 +0.009847 +0.009905 +0.009992 +0.010095 +0.010131 +0.009959 +0.009892 +0.00994 +0.01003 +0.01014 +0.010203 +0.010018 +0.009944 +0.00999 +0.010089 +0.010198 +0.010265 +0.010103 +0.009981 +0.010039 +0.010141 +0.010241 +0.010302 +0.010125 +0.010069 +0.010095 +0.010179 +0.010297 +0.010354 +0.010185 +0.010116 +0.01017 +0.01023 +0.010352 +0.010414 +0.010226 +0.010145 +0.010211 +0.010303 +0.010436 +0.010484 +0.010334 +0.010226 +0.010232 +0.010333 +0.010439 +0.010511 +0.010324 +0.01024 +0.010335 +0.010428 +0.010525 +0.010589 +0.010381 +0.010292 +0.010367 +0.010452 +0.010564 +0.010651 +0.010433 +0.01034 +0.010401 +0.010486 +0.010577 +0.010599 +0.010422 +0.010244 +0.01029 +0.010354 +0.01038 +0.01038 +0.010168 +0.010035 +0.010044 +0.010093 +0.010131 +0.010149 +0.009915 +0.009787 +0.009794 +0.009835 +0.009855 +0.009845 +0.009637 +0.009509 +0.009507 +0.009543 +0.009602 +0.009607 +0.009385 +0.009265 +0.009274 +0.009324 +0.009338 +0.009326 +0.009141 +0.009006 +0.009024 +0.009051 +0.009109 +0.009133 +0.008933 +0.008806 +0.00883 +0.008869 +0.008909 +0.008929 +0.008739 +0.008639 +0.00866 +0.008709 +0.00877 +0.008804 +0.008619 +0.008515 +0.008544 +0.008598 +0.008631 +0.008663 +0.008493 +0.008414 +0.008439 +0.008481 +0.008548 +0.008572 +0.008414 +0.008347 +0.008368 +0.008436 +0.008512 +0.008557 +0.00841 +0.008335 +0.008404 +0.00847 +0.008539 +0.008605 +0.008473 +0.008382 +0.008431 +0.008509 +0.008592 +0.008643 +0.008507 +0.008436 +0.008489 +0.008559 +0.008653 +0.008709 +0.008554 +0.008494 +0.008513 +0.008583 +0.008673 +0.008733 +0.008591 +0.008521 +0.008583 +0.00867 +0.008725 +0.008782 +0.008634 +0.008554 +0.008608 +0.008681 +0.008765 +0.008834 +0.008677 +0.008605 +0.008661 +0.008744 +0.008827 +0.008883 +0.00874 +0.008653 +0.008695 +0.008771 +0.00886 +0.008944 +0.008788 +0.008686 +0.008739 +0.008813 +0.008905 +0.008973 +0.008813 +0.008743 +0.008793 +0.008866 +0.008965 +0.009013 +0.00887 +0.008784 +0.008847 +0.008916 +0.008998 +0.009052 +0.008903 +0.008843 +0.008885 +0.008955 +0.009053 +0.009124 +0.008959 +0.008895 +0.008952 +0.009024 +0.009118 +0.009142 +0.009017 +0.008914 +0.008967 +0.009051 +0.009133 +0.009201 +0.009056 +0.008982 +0.009037 +0.009098 +0.009194 +0.009255 +0.009094 +0.009021 +0.00906 +0.009141 +0.009241 +0.009316 +0.009157 +0.009074 +0.009139 +0.009193 +0.00928 +0.009351 +0.009193 +0.009137 +0.009155 +0.009244 +0.009323 +0.009409 +0.009253 +0.00914 +0.009214 +0.009291 +0.009381 +0.009448 +0.009285 +0.009218 +0.009271 +0.00935 +0.009434 +0.0095 +0.009331 +0.00925 +0.009311 +0.00938 +0.009486 +0.009569 +0.009399 +0.00931 +0.009376 +0.009455 +0.009553 +0.009578 +0.009416 +0.009343 +0.009401 +0.009496 +0.009622 +0.009648 +0.009488 +0.009398 +0.009456 +0.009532 +0.009624 +0.009687 +0.009528 +0.009461 +0.009506 +0.009583 +0.009687 +0.009751 +0.00957 +0.00951 +0.009558 +0.009635 +0.009738 +0.009824 +0.009672 +0.009548 +0.00959 +0.009677 +0.009777 +0.009849 +0.00967 +0.009588 +0.00967 +0.009734 +0.009848 +0.00992 +0.009737 +0.009636 +0.009701 +0.009788 +0.009885 +0.009953 +0.009781 +0.00968 +0.009762 +0.009854 +0.009957 +0.010001 +0.009841 +0.009763 +0.00983 +0.009934 +0.009963 +0.010051 +0.009892 +0.009786 +0.009842 +0.009942 +0.01004 +0.010109 +0.00994 +0.009845 +0.009908 +0.01002 +0.010118 +0.010169 +0.00997 +0.009895 +0.009951 +0.010054 +0.010157 +0.010219 +0.010044 +0.009971 +0.010041 +0.010145 +0.010198 +0.010237 +0.010086 +0.009996 +0.01005 +0.010157 +0.010264 +0.010305 +0.010136 +0.010056 +0.010119 +0.010207 +0.010308 +0.010379 +0.010203 +0.010129 +0.010182 +0.010265 +0.010356 +0.010438 +0.010243 +0.010152 +0.010228 +0.01035 +0.010441 +0.010485 +0.010305 +0.010214 +0.010309 +0.010336 +0.010458 +0.01053 +0.010351 +0.010279 +0.01032 +0.010416 +0.010527 +0.0106 +0.010404 +0.010312 +0.010376 +0.010472 +0.010575 +0.010633 +0.010386 +0.010272 +0.010303 +0.010343 +0.010414 +0.010484 +0.010241 +0.010105 +0.010132 +0.010147 +0.010237 +0.01017 +0.009962 +0.009841 +0.009837 +0.009873 +0.009925 +0.009936 +0.009732 +0.009594 +0.009594 +0.009629 +0.009644 +0.009666 +0.00945 +0.009315 +0.009337 +0.009361 +0.009397 +0.009424 +0.00921 +0.009114 +0.009091 +0.009124 +0.009155 +0.009187 +0.009009 +0.008889 +0.008913 +0.008962 +0.008987 +0.009016 +0.008836 +0.008737 +0.008747 +0.008797 +0.00884 +0.008888 +0.008706 +0.0086 +0.008642 +0.008684 +0.008731 +0.008753 +0.008593 +0.008483 +0.008515 +0.008568 +0.008625 +0.008687 +0.008515 +0.008408 +0.00844 +0.008516 +0.008582 +0.008627 +0.008473 +0.008397 +0.008443 +0.008524 +0.008624 +0.008646 +0.008521 +0.00844 +0.008502 +0.008569 +0.008657 +0.008709 +0.008542 +0.008486 +0.00854 +0.008602 +0.008676 +0.008754 +0.0086 +0.008531 +0.008587 +0.008671 +0.008769 +0.008817 +0.00865 +0.008563 +0.008615 +0.008692 +0.008782 +0.00885 +0.008688 +0.008619 +0.008676 +0.008748 +0.008831 +0.008921 +0.008737 +0.008646 +0.008704 +0.008785 +0.008868 +0.008929 +0.008797 +0.00871 +0.008759 +0.008829 +0.00892 +0.00897 +0.008825 +0.008752 +0.008805 +0.008911 +0.008968 +0.009022 +0.008874 +0.008819 +0.008842 +0.008917 +0.00901 +0.009068 +0.008925 +0.008833 +0.008894 +0.008982 +0.00906 +0.00914 +0.008977 +0.008906 +0.008935 +0.009011 +0.009106 +0.009167 +0.009006 +0.008925 +0.008992 +0.009081 +0.009156 +0.009238 +0.00908 +0.009016 +0.009017 +0.009105 +0.009184 +0.009259 +0.009112 +0.009045 +0.009071 +0.00917 +0.009259 +0.009315 +0.009159 +0.009089 +0.009119 +0.009206 +0.009305 +0.009358 +0.009195 +0.009117 +0.009171 +0.00928 +0.009356 +0.00942 +0.00925 +0.009175 +0.009223 +0.009311 +0.009426 +0.009445 +0.009283 +0.009208 +0.009256 +0.009363 +0.009439 +0.009508 +0.009377 +0.009258 +0.009325 +0.0094 +0.009497 +0.009547 +0.00939 +0.009328 +0.009369 +0.009455 +0.009551 +0.009606 +0.009442 +0.009362 +0.009428 +0.0095 +0.009587 +0.009679 +0.009524 +0.009438 +0.009459 +0.009543 +0.009639 +0.009706 +0.009535 +0.009451 +0.009511 +0.009594 +0.009704 +0.009775 +0.009592 +0.009496 +0.009578 +0.009671 +0.009747 +0.009805 +0.009631 +0.009557 +0.009627 +0.009711 +0.009813 +0.009847 +0.009688 +0.009614 +0.009696 +0.009777 +0.009833 +0.00992 +0.009745 +0.009663 +0.009717 +0.0098 +0.009904 +0.009945 +0.009781 +0.009707 +0.009766 +0.009869 +0.009949 +0.010019 +0.009843 +0.009752 +0.009824 +0.009895 +0.009994 +0.010073 +0.009903 +0.009829 +0.009887 +0.009986 +0.010104 +0.010085 +0.009935 +0.009848 +0.009916 +0.01002 +0.0101 +0.01016 +0.009998 +0.009909 +0.009974 +0.010077 +0.01016 +0.010234 +0.010058 +0.009965 +0.010017 +0.01012 +0.010214 +0.010279 +0.010104 +0.010014 +0.010078 +0.010178 +0.01029 +0.010363 +0.01016 +0.01007 +0.010113 +0.010221 +0.010326 +0.010407 +0.010201 +0.010105 +0.010176 +0.010278 +0.010379 +0.010465 +0.010269 +0.010163 +0.010235 +0.010329 +0.010427 +0.010495 +0.010312 +0.010229 +0.01031 +0.010401 +0.010511 +0.010582 +0.010345 +0.010262 +0.010333 +0.010432 +0.010535 +0.0106 +0.010437 +0.010373 +0.010401 +0.010488 +0.010557 +0.010617 +0.010423 +0.010317 +0.010328 +0.010394 +0.01048 +0.010468 +0.010256 +0.010127 +0.010148 +0.010175 +0.010224 +0.010266 +0.010017 +0.009906 +0.009878 +0.009907 +0.009932 +0.009937 +0.009724 +0.009602 +0.009597 +0.009622 +0.009686 +0.009694 +0.009461 +0.00934 +0.009347 +0.009358 +0.009392 +0.009412 +0.009206 +0.009091 +0.009113 +0.009125 +0.009156 +0.009182 +0.00899 +0.008901 +0.008905 +0.008924 +0.008956 +0.009001 +0.008824 +0.008707 +0.008738 +0.008779 +0.008822 +0.008839 +0.008671 +0.008571 +0.008609 +0.008637 +0.00869 +0.008726 +0.008557 +0.008464 +0.008487 +0.00855 +0.008588 +0.008626 +0.008461 +0.008378 +0.008413 +0.008463 +0.008533 +0.008584 +0.008458 +0.008368 +0.008407 +0.008463 +0.008568 +0.008622 +0.008466 +0.008414 +0.008437 +0.00852 +0.008593 +0.008652 +0.008507 +0.008449 +0.008502 +0.008572 +0.008657 +0.008719 +0.008557 +0.008474 +0.008537 +0.008608 +0.008695 +0.008743 +0.008598 +0.008524 +0.008583 +0.008653 +0.008745 +0.008819 +0.008671 +0.008574 +0.008614 +0.008685 +0.008775 +0.008844 +0.008689 +0.008615 +0.008683 +0.008748 +0.008821 +0.008893 +0.008745 +0.008665 +0.008712 +0.008787 +0.008868 +0.008932 +0.008793 +0.008738 +0.008758 +0.008843 +0.00891 +0.008982 +0.008831 +0.008751 +0.00881 +0.008885 +0.00899 +0.009025 +0.008862 +0.008803 +0.00886 +0.008942 +0.009005 +0.009066 +0.008913 +0.008847 +0.00889 +0.00898 +0.00906 +0.009127 +0.008967 +0.008899 +0.008957 +0.009028 +0.009103 +0.009171 +0.009004 +0.008954 +0.008985 +0.009064 +0.009145 +0.009206 +0.009072 +0.008999 +0.009066 +0.00911 +0.009194 +0.00928 +0.009092 +0.009032 +0.009073 +0.009157 +0.009271 +0.00932 +0.009151 +0.009073 +0.009137 +0.009215 +0.009298 +0.009368 +0.009187 +0.009116 +0.009176 +0.009255 +0.009375 +0.009418 +0.009252 +0.00916 +0.009225 +0.009317 +0.009406 +0.009485 +0.009281 +0.009213 +0.009277 +0.009363 +0.009482 +0.009495 +0.009328 +0.009266 +0.009317 +0.009402 +0.009489 +0.00956 +0.00939 +0.009315 +0.009389 +0.009457 +0.009556 +0.009606 +0.009434 +0.009364 +0.009418 +0.0095 +0.009582 +0.009665 +0.00951 +0.009449 +0.009484 +0.009554 +0.00964 +0.009683 +0.009532 +0.009459 +0.00951 +0.00961 +0.009708 +0.009747 +0.009583 +0.009511 +0.009565 +0.009649 +0.009748 +0.009806 +0.009664 +0.009561 +0.009612 +0.009721 +0.009788 +0.009858 +0.009686 +0.009603 +0.00967 +0.009783 +0.009855 +0.009904 +0.009745 +0.009669 +0.009734 +0.009786 +0.009891 +0.009967 +0.009796 +0.0097 +0.009762 +0.009856 +0.009953 +0.010027 +0.00986 +0.009768 +0.009805 +0.009924 +0.009987 +0.010057 +0.009899 +0.009801 +0.009867 +0.009975 +0.010087 +0.010152 +0.009968 +0.009841 +0.009897 +0.01 +0.010095 +0.010165 +0.009994 +0.009915 +0.009991 +0.010084 +0.010183 +0.010233 +0.01002 +0.009956 +0.010017 +0.010109 +0.010218 +0.010288 +0.0101 +0.010016 +0.010071 +0.010165 +0.010279 +0.010355 +0.010166 +0.010061 +0.010117 +0.010248 +0.010321 +0.010372 +0.010202 +0.010123 +0.010185 +0.010262 +0.010385 +0.010436 +0.010264 +0.010197 +0.010254 +0.010311 +0.01043 +0.010486 +0.010313 +0.010229 +0.010276 +0.010379 +0.010492 +0.010567 +0.010409 +0.010267 +0.010294 +0.010408 +0.010509 +0.010543 +0.010332 +0.010225 +0.010245 +0.010311 +0.010386 +0.010394 +0.01014 +0.010013 +0.010076 +0.010061 +0.010122 +0.010126 +0.009923 +0.009778 +0.009779 +0.009817 +0.009878 +0.009854 +0.009634 +0.009515 +0.009528 +0.009603 +0.009614 +0.009597 +0.009394 +0.009271 +0.009305 +0.009337 +0.009356 +0.009386 +0.009169 +0.009051 +0.009068 +0.009121 +0.009154 +0.009173 +0.00898 +0.008862 +0.008888 +0.008942 +0.008988 +0.009015 +0.008828 +0.008733 +0.008757 +0.008808 +0.008872 +0.008886 +0.008728 +0.008605 +0.00863 +0.008677 +0.008741 +0.008781 +0.008605 +0.008503 +0.008547 +0.008632 +0.008657 +0.008699 +0.008535 +0.00844 +0.008474 +0.008547 +0.008624 +0.008684 +0.008531 +0.008456 +0.008517 +0.008578 +0.008653 +0.008724 +0.008582 +0.008507 +0.008541 +0.008627 +0.008706 +0.008797 +0.008652 +0.008547 +0.008592 +0.008654 +0.008751 +0.008812 +0.008663 +0.008586 +0.008639 +0.008709 +0.008802 +0.008858 +0.00872 +0.008662 +0.008692 +0.008749 +0.008838 +0.008897 +0.008758 +0.008683 +0.008734 +0.008796 +0.008885 +0.008964 +0.008807 +0.008725 +0.008791 +0.008862 +0.008952 +0.008987 +0.00883 +0.00876 +0.008821 +0.008897 +0.008976 +0.009045 +0.008901 +0.008828 +0.008871 +0.008948 +0.009035 +0.009096 +0.008926 +0.008853 +0.008913 +0.008986 +0.009083 +0.009136 +0.00898 +0.008903 +0.008957 +0.009038 +0.009114 +0.009185 +0.009037 +0.008962 +0.009044 +0.009091 +0.009183 +0.009226 +0.00907 +0.009001 +0.009038 +0.009123 +0.00921 +0.009278 +0.009119 +0.009041 +0.009115 +0.009184 +0.009276 +0.009333 +0.009166 +0.009094 +0.009149 +0.009221 +0.009302 +0.00938 +0.009234 +0.009152 +0.00923 +0.009287 +0.009375 +0.009449 +0.009245 +0.009175 +0.009229 +0.009304 +0.009414 +0.0095 +0.009319 +0.00923 +0.009283 +0.009371 +0.009457 +0.00953 +0.009354 +0.009278 +0.009343 +0.009424 +0.009527 +0.009583 +0.009401 +0.009326 +0.009394 +0.009475 +0.009557 +0.009616 +0.009471 +0.009414 +0.009452 +0.009522 +0.009605 +0.009653 +0.009503 +0.009425 +0.009484 +0.009563 +0.009698 +0.009721 +0.009544 +0.009474 +0.009524 +0.009611 +0.009712 +0.009773 +0.00961 +0.009535 +0.009601 +0.00968 +0.009753 +0.009823 +0.00965 +0.009571 +0.009639 +0.009728 +0.009833 +0.009871 +0.009718 +0.009632 +0.009693 +0.00977 +0.009852 +0.009922 +0.009756 +0.009666 +0.009756 +0.009819 +0.009933 +0.009981 +0.009817 +0.00972 +0.009791 +0.00987 +0.009956 +0.010029 +0.009861 +0.009784 +0.009821 +0.009919 +0.010023 +0.010119 +0.009902 +0.00982 +0.00989 +0.009981 +0.010084 +0.01015 +0.009965 +0.009867 +0.00992 +0.010016 +0.010133 +0.01019 +0.010003 +0.009935 +0.009996 +0.0101 +0.010206 +0.010242 +0.010048 +0.009979 +0.01004 +0.010126 +0.010232 +0.010298 +0.010161 +0.010038 +0.01009 +0.01019 +0.010286 +0.010365 +0.010149 +0.010076 +0.010142 +0.010231 +0.010331 +0.010425 +0.010238 +0.010149 +0.010214 +0.010281 +0.010393 +0.010457 +0.010273 +0.010182 +0.010268 +0.010332 +0.010448 +0.010519 +0.010367 +0.010241 +0.010289 +0.010391 +0.010501 +0.010573 +0.010383 +0.010315 +0.010369 +0.010443 +0.010551 +0.01063 +0.010426 +0.01035 +0.010421 +0.010492 +0.01062 +0.010664 +0.01042 +0.010328 +0.010367 +0.010421 +0.010463 +0.010492 +0.010287 +0.010186 +0.010162 +0.010211 +0.010265 +0.010253 +0.010027 +0.009872 +0.009887 +0.009936 +0.009974 +0.00996 +0.009745 +0.009613 +0.009628 +0.009661 +0.009682 +0.009692 +0.009463 +0.009337 +0.009343 +0.009372 +0.009417 +0.009434 +0.009221 +0.009107 +0.009124 +0.009166 +0.009182 +0.009201 +0.008995 +0.008885 +0.00891 +0.008942 +0.008996 +0.009032 +0.008838 +0.008723 +0.008752 +0.008803 +0.008839 +0.008882 +0.008709 +0.008606 +0.00864 +0.008676 +0.00875 +0.00877 +0.008602 +0.008491 +0.008527 +0.008566 +0.008624 +0.008675 +0.008517 +0.008438 +0.008465 +0.00852 +0.008585 +0.008649 +0.008505 +0.008426 +0.00845 +0.008531 +0.008618 +0.00867 +0.008533 +0.008458 +0.008512 +0.008583 +0.008676 +0.008732 +0.008586 +0.008514 +0.008556 +0.008625 +0.008701 +0.008761 +0.008618 +0.008552 +0.008606 +0.008664 +0.008752 +0.008819 +0.008695 +0.008601 +0.008645 +0.008716 +0.008807 +0.008852 +0.008711 +0.008669 +0.008691 +0.008771 +0.008836 +0.008893 +0.008747 +0.008686 +0.008729 +0.008803 +0.008894 +0.008951 +0.008814 +0.008731 +0.008793 +0.008861 +0.008944 +0.008995 +0.008844 +0.008772 +0.008823 +0.00891 +0.009001 +0.009062 +0.008894 +0.008822 +0.00887 +0.00895 +0.009047 +0.009085 +0.008929 +0.008882 +0.008905 +0.008987 +0.009073 +0.009138 +0.008985 +0.008921 +0.008974 +0.009049 +0.009143 +0.009177 +0.009028 +0.008948 +0.009001 +0.009091 +0.009173 +0.009231 +0.009084 +0.009005 +0.009082 +0.009171 +0.009207 +0.009265 +0.00912 +0.009048 +0.0091 +0.009186 +0.009283 +0.009331 +0.009173 +0.009091 +0.009149 +0.009226 +0.00932 +0.009377 +0.00923 +0.009156 +0.009208 +0.009294 +0.009372 +0.009414 +0.009266 +0.00918 +0.009242 +0.009327 +0.009423 +0.0095 +0.009326 +0.009242 +0.009306 +0.009363 +0.009478 +0.009506 +0.009355 +0.009285 +0.009337 +0.009419 +0.009517 +0.009603 +0.009423 +0.009339 +0.009394 +0.009474 +0.009561 +0.009618 +0.009449 +0.009372 +0.009447 +0.009532 +0.009607 +0.009676 +0.00951 +0.009426 +0.009485 +0.009574 +0.009689 +0.009736 +0.009558 +0.009488 +0.009545 +0.009624 +0.009701 +0.009771 +0.009607 +0.009526 +0.00958 +0.009663 +0.009782 +0.009845 +0.009676 +0.009588 +0.009633 +0.009703 +0.00982 +0.009869 +0.00971 +0.009651 +0.009692 +0.009762 +0.009861 +0.00993 +0.009784 +0.009668 +0.009721 +0.009812 +0.009929 +0.010003 +0.009802 +0.009723 +0.00979 +0.009868 +0.009971 +0.010044 +0.009857 +0.009771 +0.009844 +0.009931 +0.010026 +0.010106 +0.009922 +0.009824 +0.009885 +0.00998 +0.010078 +0.010136 +0.009964 +0.009904 +0.009965 +0.010027 +0.010128 +0.010182 +0.010015 +0.009922 +0.010007 +0.010079 +0.010171 +0.010248 +0.010089 +0.009988 +0.01005 +0.01015 +0.010226 +0.01028 +0.010114 +0.010038 +0.010103 +0.0102 +0.01028 +0.010343 +0.01017 +0.01009 +0.010175 +0.010223 +0.010334 +0.010403 +0.010223 +0.010158 +0.010213 +0.01028 +0.010431 +0.010443 +0.01027 +0.010192 +0.010243 +0.010357 +0.010468 +0.010521 +0.010346 +0.010261 +0.010287 +0.01039 +0.010509 +0.010576 +0.010379 +0.010306 +0.010411 +0.01049 +0.010572 +0.010591 +0.010382 +0.010304 +0.010348 +0.010417 +0.010492 +0.010483 +0.010258 +0.010113 +0.010153 +0.010215 +0.010248 +0.010235 +0.010016 +0.009889 +0.009924 +0.009991 +0.010016 +0.010003 +0.009761 +0.00964 +0.009665 +0.009687 +0.009733 +0.009747 +0.009542 +0.009383 +0.009392 +0.009428 +0.009469 +0.009443 +0.009233 +0.009117 +0.009141 +0.009173 +0.009207 +0.009223 +0.009015 +0.008913 +0.00894 +0.008982 +0.009023 +0.009015 +0.008839 +0.008735 +0.008766 +0.008812 +0.008856 +0.008874 +0.008691 +0.008599 +0.008639 +0.008709 +0.008728 +0.008738 +0.008584 +0.008472 +0.008517 +0.00856 +0.008613 +0.008641 +0.008478 +0.008381 +0.008428 +0.008489 +0.008553 +0.008583 +0.008421 +0.008356 +0.008405 +0.008483 +0.008551 +0.008629 +0.008471 +0.008386 +0.008435 +0.008525 +0.008594 +0.008655 +0.008518 +0.008436 +0.008501 +0.008559 +0.008654 +0.008712 +0.008556 +0.008483 +0.008557 +0.008598 +0.008673 +0.008743 +0.008591 +0.008522 +0.008576 +0.008674 +0.00874 +0.008781 +0.008651 +0.008566 +0.008612 +0.00869 +0.008772 +0.008832 +0.008686 +0.008609 +0.008668 +0.008744 +0.008829 +0.008887 +0.008735 +0.008685 +0.008708 +0.008781 +0.008855 +0.008921 +0.008781 +0.008705 +0.008769 +0.008853 +0.008925 +0.008958 +0.008814 +0.00875 +0.008797 +0.008869 +0.008959 +0.009025 +0.008872 +0.008799 +0.008855 +0.008934 +0.009017 +0.009057 +0.008919 +0.00883 +0.008886 +0.008967 +0.009052 +0.009143 +0.00896 +0.008888 +0.008944 +0.009023 +0.009115 +0.00916 +0.008996 +0.008927 +0.008986 +0.009061 +0.009144 +0.009213 +0.009049 +0.008988 +0.009044 +0.009126 +0.009212 +0.009251 +0.009098 +0.009034 +0.009066 +0.009148 +0.009237 +0.009298 +0.009151 +0.009073 +0.009143 +0.009232 +0.009289 +0.009371 +0.009185 +0.009113 +0.009171 +0.009245 +0.009333 +0.009412 +0.009239 +0.009164 +0.009226 +0.009316 +0.009401 +0.009451 +0.0093 +0.009212 +0.009256 +0.009361 +0.009447 +0.009495 +0.009341 +0.009261 +0.009318 +0.009405 +0.009497 +0.00957 +0.009404 +0.009296 +0.009345 +0.009445 +0.009572 +0.009584 +0.009431 +0.009353 +0.009422 +0.009496 +0.009579 +0.009666 +0.009481 +0.009406 +0.009473 +0.009564 +0.009629 +0.009698 +0.009536 +0.009451 +0.00951 +0.009605 +0.009681 +0.009754 +0.009595 +0.009517 +0.009606 +0.00965 +0.009718 +0.009791 +0.009627 +0.009549 +0.009633 +0.00968 +0.009797 +0.00986 +0.0097 +0.009627 +0.009665 +0.009736 +0.009839 +0.009909 +0.009723 +0.009648 +0.009708 +0.009795 +0.009904 +0.009978 +0.009798 +0.00971 +0.009763 +0.009861 +0.009957 +0.009997 +0.009812 +0.009737 +0.009834 +0.009909 +0.010014 +0.010074 +0.009892 +0.009791 +0.009858 +0.009951 +0.010053 +0.01012 +0.009956 +0.009843 +0.009918 +0.010016 +0.010088 +0.010161 +0.009992 +0.009907 +0.009965 +0.010066 +0.010192 +0.01026 +0.01003 +0.009934 +0.010005 +0.010109 +0.010199 +0.010263 +0.010104 +0.010012 +0.010088 +0.010189 +0.010266 +0.010309 +0.010139 +0.010063 +0.01012 +0.010212 +0.01031 +0.010388 +0.010209 +0.010132 +0.010185 +0.010255 +0.010372 +0.010474 +0.010249 +0.010146 +0.010232 +0.010312 +0.010444 +0.010494 +0.01031 +0.010225 +0.01027 +0.010364 +0.010494 +0.010533 +0.010358 +0.010291 +0.010327 +0.010422 +0.010532 +0.010575 +0.010388 +0.010297 +0.010344 +0.010414 +0.010482 +0.010557 +0.010282 +0.010149 +0.010192 +0.010216 +0.010299 +0.010272 +0.010055 +0.009917 +0.009953 +0.009964 +0.010011 +0.010009 +0.009775 +0.009656 +0.009664 +0.009693 +0.009725 +0.009737 +0.009522 +0.009396 +0.009393 +0.009433 +0.009466 +0.009458 +0.009264 +0.009153 +0.009169 +0.009185 +0.009225 +0.009228 +0.009039 +0.008931 +0.008961 +0.008976 +0.009024 +0.009029 +0.008858 +0.008753 +0.008794 +0.008847 +0.008883 +0.00891 +0.008724 +0.008628 +0.008641 +0.008704 +0.008743 +0.008769 +0.008603 +0.008519 +0.008542 +0.008591 +0.008654 +0.008687 +0.008534 +0.008425 +0.008469 +0.008521 +0.008594 +0.008644 +0.008498 +0.008427 +0.008484 +0.00855 +0.00862 +0.008685 +0.008538 +0.008458 +0.008502 +0.00858 +0.008673 +0.008738 +0.008589 +0.00851 +0.008588 +0.008628 +0.008701 +0.008767 +0.008614 +0.008547 +0.008598 +0.008681 +0.008774 +0.008839 +0.008682 +0.008587 +0.008652 +0.008715 +0.008798 +0.008843 +0.0087 +0.008636 +0.008695 +0.00877 +0.008868 +0.008924 +0.008764 +0.00868 +0.008728 +0.008813 +0.008888 +0.008953 +0.008805 +0.008728 +0.008781 +0.008871 +0.008959 +0.009003 +0.008854 +0.008765 +0.008823 +0.008929 +0.008989 +0.009042 +0.008886 +0.008838 +0.008876 +0.008955 +0.009036 +0.009095 +0.008951 +0.008858 +0.00891 +0.008998 +0.009083 +0.009143 +0.008998 +0.008908 +0.00896 +0.009036 +0.009125 +0.009189 +0.009033 +0.008951 +0.009017 +0.009093 +0.009181 +0.009247 +0.009103 +0.009028 +0.009052 +0.009129 +0.009214 +0.009283 +0.009137 +0.009056 +0.0091 +0.009186 +0.009295 +0.009342 +0.009191 +0.009105 +0.009144 +0.009221 +0.009324 +0.009374 +0.009219 +0.009145 +0.009211 +0.009304 +0.00938 +0.009447 +0.009271 +0.00919 +0.009244 +0.009324 +0.009453 +0.009474 +0.009319 +0.009238 +0.009287 +0.009381 +0.009467 +0.009528 +0.00937 +0.009288 +0.009334 +0.009438 +0.009536 +0.0096 +0.009425 +0.009337 +0.009385 +0.0095 +0.009567 +0.009624 +0.009461 +0.009377 +0.009457 +0.009545 +0.009628 +0.009697 +0.009537 +0.009452 +0.009477 +0.009564 +0.009658 +0.009725 +0.009571 +0.009473 +0.009532 +0.009627 +0.009737 +0.009783 +0.009616 +0.009534 +0.009592 +0.009689 +0.009769 +0.009845 +0.009667 +0.009592 +0.009642 +0.009727 +0.009829 +0.00988 +0.00972 +0.009638 +0.009724 +0.009822 +0.009885 +0.009932 +0.009752 +0.009681 +0.009734 +0.009825 +0.009932 +0.009993 +0.009824 +0.00976 +0.00981 +0.00989 +0.009968 +0.010041 +0.009862 +0.009782 +0.009851 +0.009916 +0.010049 +0.010125 +0.009928 +0.009849 +0.009913 +0.009997 +0.010106 +0.010128 +0.009966 +0.009882 +0.009953 +0.010024 +0.010133 +0.010203 +0.010021 +0.009944 +0.010004 +0.010095 +0.010185 +0.010265 +0.010094 +0.00999 +0.010046 +0.010149 +0.010244 +0.010307 +0.010142 +0.010038 +0.010133 +0.010209 +0.010344 +0.010385 +0.010171 +0.010072 +0.010148 +0.010255 +0.010352 +0.010409 +0.010234 +0.010154 +0.010227 +0.010332 +0.010413 +0.010452 +0.010292 +0.010218 +0.010254 +0.010361 +0.010444 +0.010542 +0.010368 +0.010263 +0.010324 +0.010415 +0.010533 +0.010599 +0.010374 +0.010288 +0.010371 +0.010464 +0.010565 +0.010639 +0.010475 +0.010377 +0.010387 +0.010497 +0.010595 +0.010663 +0.010461 +0.010319 +0.010346 +0.010395 +0.010455 +0.010457 +0.010224 +0.010093 +0.010117 +0.010141 +0.010202 +0.010216 +0.010007 +0.009812 +0.009819 +0.009861 +0.009915 +0.009922 +0.009693 +0.009566 +0.00958 +0.009613 +0.009625 +0.00964 +0.009418 +0.009289 +0.009306 +0.009315 +0.009375 +0.009404 +0.009189 +0.009061 +0.009066 +0.009108 +0.009125 +0.00915 +0.008964 +0.008845 +0.008888 +0.008903 +0.008945 +0.008983 +0.008808 +0.008709 +0.008721 +0.008774 +0.00881 +0.008851 +0.008669 +0.008565 +0.008601 +0.008678 +0.008705 +0.008742 +0.008591 +0.008482 +0.008516 +0.008574 +0.008645 +0.008662 +0.008515 +0.008437 +0.008474 +0.008541 +0.008625 +0.008687 +0.008553 +0.008504 +0.008522 +0.00859 +0.008668 +0.008721 +0.008576 +0.00851 +0.008556 +0.008629 +0.008717 +0.008776 +0.008626 +0.008551 +0.008606 +0.008672 +0.008776 +0.00883 +0.008667 +0.008596 +0.008647 +0.008724 +0.008806 +0.008865 +0.008727 +0.008648 +0.00871 +0.00877 +0.008846 +0.008923 +0.00878 +0.008677 +0.008729 +0.008807 +0.008903 +0.008977 +0.008818 +0.008743 +0.008774 +0.008855 +0.008943 +0.009002 +0.00885 +0.008765 +0.008819 +0.008918 +0.00901 +0.009068 +0.008906 +0.008839 +0.008873 +0.008943 +0.009034 +0.009099 +0.008941 +0.008868 +0.008943 +0.009014 +0.009107 +0.009133 +0.008981 +0.008908 +0.008968 +0.009039 +0.009135 +0.009191 +0.009038 +0.008986 +0.009023 +0.009083 +0.009189 +0.009229 +0.009082 +0.009011 +0.009056 +0.00913 +0.009225 +0.009304 +0.00914 +0.009067 +0.00913 +0.00919 +0.009271 +0.009337 +0.009179 +0.009127 +0.009155 +0.00922 +0.009317 +0.009382 +0.009246 +0.00916 +0.009214 +0.009271 +0.009375 +0.009441 +0.00929 +0.009201 +0.009232 +0.009321 +0.009439 +0.009497 +0.009331 +0.00925 +0.009315 +0.009368 +0.009474 +0.009539 +0.009365 +0.009287 +0.009346 +0.009437 +0.009577 +0.009595 +0.00941 +0.009352 +0.009399 +0.009477 +0.009566 +0.009631 +0.009465 +0.009387 +0.009448 +0.009527 +0.009619 +0.009711 +0.009516 +0.009431 +0.009492 +0.009593 +0.009674 +0.009746 +0.00959 +0.009495 +0.009538 +0.009617 +0.00973 +0.009783 +0.009617 +0.009543 +0.009624 +0.009731 +0.009783 +0.009842 +0.009686 +0.009567 +0.009632 +0.009721 +0.009828 +0.009891 +0.009707 +0.009638 +0.009702 +0.009786 +0.009879 +0.009949 +0.009771 +0.009687 +0.009755 +0.009835 +0.009942 +0.010019 +0.009839 +0.009727 +0.009792 +0.009889 +0.010026 +0.010042 +0.009869 +0.009781 +0.009864 +0.009989 +0.010034 +0.010098 +0.00993 +0.009817 +0.009888 +0.009992 +0.010081 +0.010161 +0.009995 +0.009885 +0.009953 +0.010052 +0.01014 +0.010204 +0.01003 +0.009947 +0.010009 +0.010115 +0.01022 +0.010291 +0.01007 +0.009991 +0.010051 +0.010135 +0.010255 +0.010313 +0.01018 +0.010058 +0.010109 +0.010194 +0.010298 +0.010355 +0.010183 +0.010109 +0.010157 +0.010259 +0.010365 +0.010418 +0.010242 +0.010163 +0.010212 +0.010307 +0.010421 +0.010515 +0.010333 +0.010211 +0.010261 +0.010344 +0.010465 +0.01052 +0.010351 +0.010269 +0.010333 +0.010425 +0.010542 +0.010589 +0.010407 +0.010312 +0.010362 +0.010464 +0.010584 +0.010634 +0.010459 +0.010376 +0.010426 +0.010527 +0.010654 +0.010721 +0.010524 +0.010436 +0.010483 +0.010588 +0.0107 +0.010714 +0.010544 +0.010448 +0.010484 +0.010542 +0.010638 +0.010635 +0.010429 +0.010306 +0.010316 +0.010346 +0.010377 +0.010367 +0.010146 +0.009996 +0.010007 +0.010062 +0.010095 +0.010084 +0.009861 +0.009737 +0.009758 +0.009757 +0.009803 +0.009787 +0.009581 +0.009484 +0.009457 +0.009499 +0.009544 +0.009538 +0.009334 +0.009219 +0.009231 +0.009266 +0.009302 +0.009319 +0.009126 +0.009011 +0.009034 +0.009067 +0.009116 +0.009136 +0.008961 +0.008841 +0.008869 +0.008926 +0.008971 +0.009031 +0.008807 +0.008707 +0.008724 +0.008799 +0.008826 +0.008849 +0.008681 +0.008585 +0.00863 +0.008676 +0.008735 +0.00879 +0.008622 +0.008542 +0.008572 +0.008642 +0.008711 +0.008756 +0.008619 +0.008552 +0.008597 +0.008665 +0.008774 +0.008826 +0.008676 +0.008591 +0.008656 +0.008719 +0.008821 +0.008847 +0.008694 +0.008631 +0.008683 +0.008789 +0.00886 +0.008886 +0.008749 +0.008679 +0.008727 +0.008806 +0.008893 +0.008947 +0.008803 +0.008727 +0.008787 +0.008881 +0.008935 +0.008997 +0.008845 +0.008769 +0.008826 +0.008898 +0.008986 +0.009039 +0.008909 +0.008825 +0.008909 +0.008951 +0.009035 +0.009073 +0.00893 +0.008873 +0.008905 +0.008987 +0.009089 +0.009148 +0.008971 +0.008898 +0.008972 +0.009046 +0.009143 +0.009184 +0.009034 +0.008956 +0.009009 +0.009097 +0.00917 +0.009242 +0.009094 +0.00902 +0.009072 +0.009137 +0.009237 +0.00929 +0.00916 +0.009038 +0.009086 +0.009177 +0.009268 +0.009335 +0.009178 +0.009105 +0.009169 +0.009236 +0.009332 +0.009369 +0.009217 +0.009141 +0.009197 +0.009285 +0.009377 +0.009429 +0.009282 +0.009191 +0.009255 +0.009328 +0.009425 +0.009485 +0.00932 +0.009248 +0.009317 +0.009404 +0.009481 +0.009538 +0.009366 +0.009283 +0.009341 +0.009423 +0.009518 +0.00958 +0.009418 +0.009335 +0.009412 +0.009498 +0.009581 +0.009629 +0.009466 +0.009384 +0.009443 +0.009531 +0.009622 +0.00968 +0.009543 +0.009452 +0.009501 +0.009593 +0.009681 +0.009756 +0.009555 +0.009476 +0.009535 +0.009619 +0.009726 +0.009789 +0.009624 +0.009553 +0.009599 +0.00968 +0.009771 +0.009827 +0.009666 +0.009611 +0.00963 +0.009721 +0.00982 +0.009892 +0.009719 +0.009638 +0.009701 +0.009784 +0.009881 +0.00995 +0.009783 +0.009707 +0.009749 +0.009811 +0.009921 +0.009998 +0.009824 +0.009753 +0.009793 +0.009881 +0.009969 +0.010049 +0.009895 +0.009784 +0.009834 +0.009941 +0.010026 +0.010098 +0.009935 +0.009839 +0.009892 +0.010013 +0.01011 +0.010156 +0.009986 +0.009896 +0.009976 +0.010024 +0.010122 +0.01019 +0.010024 +0.009939 +0.010018 +0.010106 +0.010227 +0.010272 +0.010055 +0.009984 +0.010043 +0.010138 +0.010252 +0.010305 +0.010129 +0.010061 +0.010104 +0.010199 +0.010311 +0.010361 +0.010186 +0.010097 +0.010186 +0.010277 +0.010389 +0.010391 +0.010225 +0.010165 +0.010202 +0.010298 +0.010417 +0.010471 +0.010294 +0.010237 +0.010288 +0.010347 +0.010464 +0.010522 +0.010343 +0.010272 +0.010315 +0.010409 +0.01055 +0.010596 +0.010411 +0.010329 +0.010384 +0.01049 +0.010556 +0.010618 +0.010446 +0.010371 +0.010429 +0.010543 +0.010653 +0.010698 +0.010502 +0.010418 +0.010474 +0.010584 +0.01066 +0.010721 +0.0105 +0.010399 +0.010418 +0.010463 +0.010505 +0.010534 +0.010309 +0.010159 +0.010178 +0.010217 +0.010312 +0.010274 +0.010006 +0.009877 +0.009913 +0.00991 +0.009939 +0.00996 +0.009744 +0.009616 +0.009628 +0.009664 +0.009671 +0.009682 +0.009469 +0.009353 +0.009345 +0.009372 +0.009413 +0.009425 +0.009218 +0.009104 +0.009103 +0.009142 +0.009178 +0.009202 +0.009027 +0.008896 +0.008906 +0.008938 +0.008992 +0.009024 +0.00883 +0.008747 +0.008741 +0.008793 +0.008829 +0.008864 +0.008687 +0.008587 +0.008607 +0.008651 +0.008724 +0.008748 +0.008559 +0.008465 +0.008502 +0.008554 +0.008598 +0.008647 +0.008493 +0.008404 +0.00845 +0.008519 +0.008592 +0.008689 +0.008509 +0.008429 +0.008473 +0.008551 +0.008633 +0.008687 +0.008542 +0.008462 +0.008544 +0.008604 +0.008688 +0.008746 +0.008598 +0.00851 +0.008558 +0.008646 +0.00872 +0.008777 +0.008636 +0.008572 +0.008602 +0.00869 +0.008784 +0.008844 +0.008713 +0.00861 +0.008657 +0.008725 +0.008837 +0.008864 +0.008716 +0.008646 +0.008696 +0.008791 +0.008868 +0.008927 +0.008776 +0.008708 +0.008743 +0.008828 +0.008921 +0.008953 +0.008807 +0.008747 +0.008813 +0.00886 +0.00895 +0.009014 +0.008861 +0.00879 +0.008835 +0.008915 +0.008998 +0.009062 +0.008912 +0.008838 +0.008927 +0.008969 +0.009038 +0.0091 +0.008955 +0.008876 +0.008926 +0.008997 +0.009103 +0.009154 +0.00901 +0.008941 +0.00899 +0.009056 +0.00914 +0.009209 +0.00904 +0.008966 +0.009025 +0.009106 +0.009189 +0.00925 +0.009101 +0.009023 +0.009079 +0.009148 +0.009233 +0.009294 +0.009144 +0.00907 +0.009147 +0.009231 +0.009288 +0.009334 +0.009178 +0.009106 +0.009157 +0.009241 +0.00933 +0.009398 +0.009237 +0.009168 +0.009238 +0.009314 +0.009388 +0.009429 +0.009273 +0.009195 +0.009254 +0.009345 +0.009426 +0.009504 +0.009358 +0.009267 +0.009317 +0.009398 +0.009492 +0.009559 +0.009378 +0.009285 +0.009347 +0.009437 +0.009561 +0.009574 +0.00942 +0.009348 +0.009403 +0.00948 +0.009575 +0.009649 +0.009482 +0.009408 +0.009475 +0.009543 +0.009638 +0.009687 +0.009525 +0.009441 +0.009504 +0.009589 +0.009673 +0.009761 +0.009595 +0.009529 +0.009581 +0.009643 +0.009715 +0.009774 +0.009624 +0.009544 +0.009615 +0.009707 +0.009765 +0.009833 +0.009672 +0.009594 +0.009653 +0.009732 +0.009846 +0.009897 +0.009744 +0.009666 +0.009708 +0.009775 +0.009882 +0.009947 +0.009774 +0.009692 +0.009756 +0.00988 +0.009968 +0.010004 +0.009827 +0.009751 +0.009797 +0.00988 +0.009982 +0.01006 +0.009904 +0.009785 +0.009854 +0.009928 +0.010026 +0.010116 +0.009936 +0.009846 +0.009912 +0.010002 +0.010096 +0.010184 +0.009996 +0.00988 +0.009954 +0.010057 +0.010193 +0.010225 +0.010042 +0.009933 +0.01 +0.010107 +0.01021 +0.010253 +0.010087 +0.009998 +0.010059 +0.01017 +0.010266 +0.010298 +0.01015 +0.010065 +0.010125 +0.010227 +0.010296 +0.010364 +0.0102 +0.010101 +0.010163 +0.010255 +0.01039 +0.01048 +0.010247 +0.010153 +0.010233 +0.010309 +0.0104 +0.010464 +0.010291 +0.010227 +0.010264 +0.010364 +0.010478 +0.010532 +0.010353 +0.010267 +0.010326 +0.010421 +0.010546 +0.010603 +0.010417 +0.010307 +0.010378 +0.010465 +0.01056 +0.010654 +0.010397 +0.010291 +0.010321 +0.010367 +0.01046 +0.010442 +0.010212 +0.010084 +0.010099 +0.010124 +0.010183 +0.010172 +0.009955 +0.009831 +0.009823 +0.009852 +0.009895 +0.009917 +0.009689 +0.009569 +0.009568 +0.009601 +0.009629 +0.009654 +0.009467 +0.009307 +0.009308 +0.009332 +0.009378 +0.009413 +0.009205 +0.009084 +0.009119 +0.009109 +0.009143 +0.009169 +0.008988 +0.008868 +0.008882 +0.008926 +0.008977 +0.009011 +0.00885 +0.008737 +0.008751 +0.008786 +0.008834 +0.008867 +0.008695 +0.008593 +0.008626 +0.008673 +0.008751 +0.008764 +0.008591 +0.008496 +0.008527 +0.008585 +0.008623 +0.008665 +0.008518 +0.00844 +0.008476 +0.008562 +0.008606 +0.008655 +0.008521 +0.008454 +0.00851 +0.008569 +0.008653 +0.008709 +0.008573 +0.008497 +0.008547 +0.008622 +0.00871 +0.008756 +0.00861 +0.008545 +0.0086 +0.00869 +0.008745 +0.008789 +0.008669 +0.008576 +0.008633 +0.008718 +0.008798 +0.008862 +0.008696 +0.008643 +0.008674 +0.008746 +0.00884 +0.008886 +0.008736 +0.008671 +0.008723 +0.008801 +0.008896 +0.008952 +0.008788 +0.008712 +0.008775 +0.008837 +0.008925 +0.008991 +0.008851 +0.008779 +0.00883 +0.008883 +0.008973 +0.009029 +0.008883 +0.008816 +0.008854 +0.008931 +0.009027 +0.009076 +0.008935 +0.00885 +0.008908 +0.008982 +0.009071 +0.009142 +0.008972 +0.008892 +0.008954 +0.009031 +0.009114 +0.009189 +0.009041 +0.008938 +0.008998 +0.009081 +0.009168 +0.009261 +0.009075 +0.00898 +0.009036 +0.009118 +0.009219 +0.009293 +0.009133 +0.009034 +0.009088 +0.009164 +0.009261 +0.009327 +0.00916 +0.00908 +0.00914 +0.009229 +0.009315 +0.009384 +0.009223 +0.009118 +0.009181 +0.00927 +0.009357 +0.009421 +0.009265 +0.009196 +0.009277 +0.009342 +0.009407 +0.009459 +0.0093 +0.009215 +0.009269 +0.009361 +0.009454 +0.009541 +0.009372 +0.009266 +0.009331 +0.009404 +0.0095 +0.009573 +0.009393 +0.009321 +0.00938 +0.009487 +0.009569 +0.009629 +0.009453 +0.009365 +0.009424 +0.009517 +0.009614 +0.009697 +0.009492 +0.009441 +0.009474 +0.009569 +0.009673 +0.009712 +0.009538 +0.009471 +0.009528 +0.009606 +0.009712 +0.009775 +0.009595 +0.009516 +0.009576 +0.009655 +0.00976 +0.009824 +0.009653 +0.009578 +0.009644 +0.009729 +0.009811 +0.009869 +0.009713 +0.009646 +0.009674 +0.00975 +0.009852 +0.009939 +0.00976 +0.009679 +0.00974 +0.009829 +0.009896 +0.009965 +0.009806 +0.00971 +0.009779 +0.009867 +0.009992 +0.010037 +0.009865 +0.009779 +0.009837 +0.009909 +0.010009 +0.010079 +0.009901 +0.009837 +0.009916 +0.009971 +0.010054 +0.010119 +0.009953 +0.009882 +0.009951 +0.010013 +0.010134 +0.010205 +0.010014 +0.009921 +0.009962 +0.010068 +0.010177 +0.01024 +0.010061 +0.009973 +0.010043 +0.01013 +0.010236 +0.010298 +0.010115 +0.010027 +0.010091 +0.010226 +0.010284 +0.010352 +0.010177 +0.010072 +0.010142 +0.010221 +0.010341 +0.010415 +0.010204 +0.010132 +0.010214 +0.010303 +0.010403 +0.010464 +0.010252 +0.010183 +0.010247 +0.010336 +0.010441 +0.010528 +0.010346 +0.010248 +0.010316 +0.010438 +0.010498 +0.010534 +0.010362 +0.010288 +0.01037 +0.01045 +0.010544 +0.010616 +0.010437 +0.010359 +0.01041 +0.01049 +0.010608 +0.010674 +0.010476 +0.010365 +0.010406 +0.010477 +0.010534 +0.010557 +0.01033 +0.0102 +0.010256 +0.010323 +0.010365 +0.010322 +0.010142 +0.009964 +0.009992 +0.010028 +0.010064 +0.010083 +0.009851 +0.00971 +0.009726 +0.009753 +0.009782 +0.00979 +0.009557 +0.009437 +0.009463 +0.009512 +0.009541 +0.009542 +0.009341 +0.00919 +0.009217 +0.009264 +0.009295 +0.009334 +0.009099 +0.008978 +0.009016 +0.009068 +0.009133 +0.009112 +0.008918 +0.008803 +0.008844 +0.008883 +0.008932 +0.008966 +0.008773 +0.008678 +0.008732 +0.008775 +0.008811 +0.008836 +0.008666 +0.008559 +0.008595 +0.008638 +0.008703 +0.008724 +0.008572 +0.00849 +0.008534 +0.00861 +0.008657 +0.008681 +0.008527 +0.008467 +0.008517 +0.008606 +0.008674 +0.008755 +0.008596 +0.008516 +0.008568 +0.008643 +0.00872 +0.008769 +0.008627 +0.008553 +0.008612 +0.008689 +0.008769 +0.00883 +0.008684 +0.008609 +0.008661 +0.008744 +0.008814 +0.008859 +0.008718 +0.008649 +0.008726 +0.008783 +0.008855 +0.008924 +0.008764 +0.008685 +0.008742 +0.008816 +0.008898 +0.008966 +0.008838 +0.008729 +0.008783 +0.008875 +0.008958 +0.009015 +0.008859 +0.008776 +0.008829 +0.00891 +0.009 +0.009043 +0.008896 +0.00883 +0.008878 +0.008963 +0.009048 +0.009114 +0.008984 +0.00889 +0.008938 +0.008989 +0.009084 +0.009148 +0.008994 +0.008917 +0.008972 +0.009059 +0.009136 +0.009203 +0.009052 +0.00897 +0.009013 +0.009097 +0.009192 +0.009243 +0.009101 +0.00903 +0.009067 +0.00914 +0.009236 +0.0093 +0.009135 +0.009058 +0.009112 +0.009205 +0.009324 +0.009355 +0.009191 +0.009108 +0.009168 +0.009231 +0.009323 +0.009379 +0.009232 +0.009141 +0.009207 +0.009303 +0.009402 +0.009454 +0.00929 +0.009214 +0.009247 +0.009323 +0.009423 +0.009485 +0.00933 +0.009256 +0.009306 +0.009394 +0.009478 +0.009548 +0.009393 +0.009313 +0.009345 +0.009425 +0.009525 +0.009601 +0.009438 +0.009365 +0.009395 +0.009486 +0.009567 +0.009634 +0.009477 +0.009379 +0.009455 +0.009554 +0.009648 +0.009699 +0.00953 +0.009438 +0.009502 +0.009578 +0.009683 +0.009732 +0.009576 +0.009493 +0.009551 +0.009672 +0.009737 +0.009786 +0.009612 +0.009537 +0.009604 +0.00968 +0.009797 +0.00989 +0.009654 +0.009582 +0.009652 +0.009728 +0.009828 +0.009895 +0.009723 +0.009647 +0.009727 +0.009808 +0.009888 +0.009938 +0.009781 +0.009679 +0.009738 +0.009847 +0.009956 +0.010042 +0.009828 +0.009746 +0.009809 +0.009903 +0.009979 +0.010035 +0.009875 +0.009788 +0.009869 +0.009964 +0.01003 +0.010095 +0.009919 +0.009849 +0.009905 +0.009983 +0.010099 +0.01016 +0.01 +0.00992 +0.009965 +0.010028 +0.010152 +0.010226 +0.010065 +0.00993 +0.009995 +0.010095 +0.010219 +0.010266 +0.010093 +0.010004 +0.010037 +0.010143 +0.010248 +0.01034 +0.010131 +0.010043 +0.010113 +0.010202 +0.010307 +0.010374 +0.010186 +0.010105 +0.010178 +0.010246 +0.010382 +0.010451 +0.010274 +0.010163 +0.010202 +0.010299 +0.010404 +0.010497 +0.010285 +0.010222 +0.010292 +0.010371 +0.010458 +0.01053 +0.010341 +0.010262 +0.010332 +0.010408 +0.010544 +0.010613 +0.010412 +0.010323 +0.010393 +0.01045 +0.010562 +0.010649 +0.010498 +0.010397 +0.010419 +0.010508 +0.010603 +0.010678 +0.010487 +0.010388 +0.010417 +0.010475 +0.010533 +0.010576 +0.01037 +0.010211 +0.010239 +0.010286 +0.010322 +0.010319 +0.010094 +0.009962 +0.009973 +0.010009 +0.010061 +0.010058 +0.009846 +0.009729 +0.009725 +0.00974 +0.009779 +0.009795 +0.009595 +0.009459 +0.009473 +0.009529 +0.00958 +0.009559 +0.009351 +0.009217 +0.00923 +0.009266 +0.009318 +0.009325 +0.009135 +0.009033 +0.009056 +0.009091 +0.009131 +0.009164 +0.008965 +0.008842 +0.008884 +0.008919 +0.008999 +0.00902 +0.008813 +0.008697 +0.00873 +0.008779 +0.008826 +0.008857 +0.008693 +0.008598 +0.008634 +0.008684 +0.008739 +0.008788 +0.008635 +0.008547 +0.00859 +0.00865 +0.008723 +0.008793 +0.008654 +0.008571 +0.00862 +0.008696 +0.008777 +0.00885 +0.008699 +0.00862 +0.008677 +0.008771 +0.008805 +0.00887 +0.008748 +0.008649 +0.008709 +0.00878 +0.008861 +0.008926 +0.008785 +0.008699 +0.008758 +0.008838 +0.008918 +0.008996 +0.008817 +0.00875 +0.008794 +0.008871 +0.008967 +0.009017 +0.008868 +0.008795 +0.00885 +0.00892 +0.009014 +0.009076 +0.008924 +0.008876 +0.008897 +0.00897 +0.009058 +0.009102 +0.008963 +0.008908 +0.008931 +0.009006 +0.009105 +0.009155 +0.009008 +0.008938 +0.008979 +0.009077 +0.009164 +0.009211 +0.009055 +0.008975 +0.009036 +0.009103 +0.009198 +0.009256 +0.009107 +0.009038 +0.009098 +0.009173 +0.009269 +0.009325 +0.009128 +0.009059 +0.009118 +0.009198 +0.0093 +0.009355 +0.009213 +0.009121 +0.009164 +0.009246 +0.009345 +0.009409 +0.009249 +0.009161 +0.009229 +0.009299 +0.009399 +0.009466 +0.009294 +0.009213 +0.009266 +0.009356 +0.009448 +0.009495 +0.009356 +0.009284 +0.009343 +0.009406 +0.009493 +0.009537 +0.009378 +0.009308 +0.009361 +0.009445 +0.009548 +0.009616 +0.009433 +0.009366 +0.009414 +0.009517 +0.0096 +0.009645 +0.009483 +0.0094 +0.009471 +0.009541 +0.009634 +0.009716 +0.009554 +0.00947 +0.009524 +0.009613 +0.009724 +0.009723 +0.009593 +0.00949 +0.00955 +0.009652 +0.00973 +0.009808 +0.009663 +0.009563 +0.00962 +0.009704 +0.009778 +0.009845 +0.009682 +0.009602 +0.009662 +0.009749 +0.009875 +0.009916 +0.009747 +0.009669 +0.009713 +0.009782 +0.009893 +0.00997 +0.009817 +0.009707 +0.009749 +0.009842 +0.009968 +0.009999 +0.009827 +0.009748 +0.009808 +0.009898 +0.010001 +0.010071 +0.009896 +0.009805 +0.009871 +0.009945 +0.01005 +0.010124 +0.009937 +0.009853 +0.009934 +0.010012 +0.010129 +0.010185 +0.010002 +0.009929 +0.009939 +0.010044 +0.010141 +0.010217 +0.010044 +0.009988 +0.010014 +0.010133 +0.010219 +0.010259 +0.010096 +0.010001 +0.010067 +0.010164 +0.010258 +0.010331 +0.010177 +0.010075 +0.010132 +0.010242 +0.010305 +0.010369 +0.010203 +0.010129 +0.01019 +0.01025 +0.010366 +0.010426 +0.010277 +0.010171 +0.01024 +0.010348 +0.010409 +0.010473 +0.010309 +0.01021 +0.010272 +0.010401 +0.010492 +0.010553 +0.010378 +0.010271 +0.010315 +0.010433 +0.010525 +0.010594 +0.010438 +0.01034 +0.010425 +0.010487 +0.010554 +0.010642 +0.010463 +0.010383 +0.010437 +0.010546 +0.010648 +0.010719 +0.010525 +0.010429 +0.010493 +0.010592 +0.010664 +0.010731 +0.010548 +0.010436 +0.010484 +0.010541 +0.010573 +0.010587 +0.010382 +0.010281 +0.010295 +0.010291 +0.010387 +0.010351 +0.010149 +0.009994 +0.009997 +0.010031 +0.010071 +0.010076 +0.009866 +0.009728 +0.009725 +0.009783 +0.009819 +0.009828 +0.009606 +0.009479 +0.009461 +0.009503 +0.009549 +0.009558 +0.009342 +0.009221 +0.009232 +0.009312 +0.009305 +0.009312 +0.009115 +0.008997 +0.009035 +0.009031 +0.00909 +0.009119 +0.008934 +0.00884 +0.00886 +0.008896 +0.008948 +0.008973 +0.008805 +0.008699 +0.008721 +0.00876 +0.00883 +0.008858 +0.008703 +0.008597 +0.008626 +0.008669 +0.008713 +0.008766 +0.008597 +0.008533 +0.008556 +0.008608 +0.008669 +0.008737 +0.008607 +0.008528 +0.008575 +0.008675 +0.008719 +0.008769 +0.008628 +0.008554 +0.008617 +0.008678 +0.008766 +0.008842 +0.008693 +0.008614 +0.008664 +0.008746 +0.008821 +0.008856 +0.008718 +0.008651 +0.008703 +0.008774 +0.008882 +0.00893 +0.008793 +0.008689 +0.008741 +0.008835 +0.008893 +0.008956 +0.008801 +0.008736 +0.008794 +0.008871 +0.008993 +0.009017 +0.008872 +0.008777 +0.008833 +0.008909 +0.009 +0.009058 +0.008908 +0.008833 +0.008893 +0.008957 +0.009053 +0.009129 +0.008966 +0.008874 +0.008931 +0.00901 +0.00913 +0.009144 +0.009016 +0.008904 +0.008968 +0.009075 +0.009148 +0.009207 +0.009054 +0.008971 +0.009002 +0.009095 +0.009197 +0.009242 +0.009092 +0.009016 +0.009071 +0.009167 +0.009254 +0.009321 +0.009144 +0.00906 +0.009117 +0.009199 +0.009276 +0.009343 +0.009185 +0.009141 +0.009179 +0.009243 +0.009325 +0.009399 +0.00925 +0.009141 +0.009197 +0.00929 +0.009388 +0.009456 +0.009296 +0.00922 +0.009255 +0.00935 +0.009438 +0.009494 +0.009331 +0.009251 +0.009312 +0.009384 +0.009482 +0.009567 +0.009394 +0.009304 +0.009349 +0.009446 +0.009574 +0.009593 +0.009415 +0.009337 +0.009407 +0.009496 +0.009612 +0.009648 +0.009479 +0.009383 +0.009449 +0.009544 +0.009628 +0.009691 +0.009539 +0.009437 +0.009497 +0.009589 +0.009675 +0.009743 +0.009576 +0.0095 +0.009561 +0.009642 +0.009751 +0.009813 +0.009623 +0.009545 +0.009617 +0.009711 +0.00977 +0.009831 +0.009653 +0.009598 +0.009685 +0.009757 +0.009843 +0.009903 +0.009713 +0.009631 +0.009703 +0.009785 +0.009882 +0.009967 +0.009766 +0.00969 +0.009756 +0.009844 +0.009933 +0.01 +0.009828 +0.009745 +0.009816 +0.009915 +0.010043 +0.010032 +0.009863 +0.009786 +0.009836 +0.009944 +0.010028 +0.010101 +0.00997 +0.009856 +0.00991 +0.010015 +0.010074 +0.010145 +0.009987 +0.009894 +0.009946 +0.010045 +0.010152 +0.01022 +0.010045 +0.009957 +0.010014 +0.010097 +0.01019 +0.010294 +0.010094 +0.009993 +0.010047 +0.010135 +0.010232 +0.010306 +0.01014 +0.010062 +0.010103 +0.010206 +0.010321 +0.010387 +0.010208 +0.010098 +0.010161 +0.010243 +0.010366 +0.010413 +0.010237 +0.010172 +0.010234 +0.010335 +0.010433 +0.010502 +0.010294 +0.010192 +0.010282 +0.010344 +0.010471 +0.010517 +0.010343 +0.010265 +0.010328 +0.010413 +0.010524 +0.010586 +0.010408 +0.010327 +0.010404 +0.010474 +0.010578 +0.010632 +0.010456 +0.010381 +0.010426 +0.010518 +0.010651 +0.010749 +0.010542 +0.010424 +0.010468 +0.010558 +0.010696 +0.010691 +0.010508 +0.010412 +0.010452 +0.010489 +0.010558 +0.010592 +0.010336 +0.010197 +0.010223 +0.010237 +0.010297 +0.0103 +0.010064 +0.009943 +0.009937 +0.009977 +0.01002 +0.010024 +0.009828 +0.009657 +0.009672 +0.009712 +0.009744 +0.009733 +0.009538 +0.00942 +0.009414 +0.009435 +0.00947 +0.009503 +0.009306 +0.009183 +0.009192 +0.009228 +0.009261 +0.009261 +0.009084 +0.008964 +0.008988 +0.00903 +0.009079 +0.009094 +0.008928 +0.00882 +0.008859 +0.008895 +0.008922 +0.008945 +0.008783 +0.008679 +0.008716 +0.008737 +0.0088 +0.00884 +0.008686 +0.008571 +0.008595 +0.008648 +0.008699 +0.008732 +0.008578 +0.008494 +0.008531 +0.008585 +0.00866 +0.008716 +0.008548 +0.008478 +0.008535 +0.008591 +0.008688 +0.008747 +0.008594 +0.008542 +0.008594 +0.008673 +0.008726 +0.008783 +0.008621 +0.008566 +0.008608 +0.00869 +0.008774 +0.00883 +0.008678 +0.008621 +0.008675 +0.008749 +0.008827 +0.008886 +0.008736 +0.008651 +0.008702 +0.008784 +0.008865 +0.008935 +0.00879 +0.008693 +0.008749 +0.00882 +0.008912 +0.008972 +0.008848 +0.008738 +0.008795 +0.008865 +0.00898 +0.009017 +0.008869 +0.008787 +0.008843 +0.008914 +0.009 +0.009071 +0.008922 +0.00884 +0.008894 +0.008956 +0.00907 +0.009133 +0.008966 +0.008899 +0.008941 +0.009004 +0.009096 +0.009164 +0.009003 +0.008933 +0.008985 +0.009072 +0.009172 +0.009203 +0.009038 +0.008966 +0.009031 +0.009109 +0.009222 +0.009253 +0.009101 +0.009033 +0.009082 +0.009147 +0.009251 +0.009304 +0.009142 +0.009071 +0.009126 +0.009195 +0.009296 +0.00937 +0.009206 +0.009127 +0.009184 +0.009238 +0.009337 +0.009408 +0.009246 +0.009194 +0.009221 +0.009287 +0.009392 +0.009458 +0.009306 +0.009214 +0.00926 +0.009339 +0.009437 +0.009495 +0.009341 +0.009263 +0.009318 +0.009401 +0.009495 +0.009547 +0.009391 +0.009328 +0.009357 +0.009432 +0.009533 +0.009611 +0.009433 +0.009374 +0.009435 +0.00951 +0.009633 +0.009638 +0.009466 +0.009391 +0.009451 +0.009537 +0.00964 +0.009713 +0.009551 +0.009462 +0.00952 +0.00959 +0.009692 +0.009743 +0.009581 +0.009499 +0.00955 +0.00966 +0.009759 +0.009816 +0.009637 +0.009568 +0.009595 +0.0097 +0.009795 +0.00986 +0.009714 +0.009603 +0.009651 +0.00974 +0.00983 +0.009898 +0.009719 +0.009641 +0.00972 +0.009805 +0.009897 +0.009977 +0.009809 +0.009688 +0.009763 +0.00984 +0.009943 +0.010015 +0.009826 +0.00975 +0.009815 +0.009914 +0.010017 +0.010079 +0.00988 +0.00983 +0.009861 +0.009945 +0.010038 +0.010102 +0.009974 +0.009856 +0.00992 +0.010016 +0.010103 +0.010145 +0.009989 +0.009907 +0.00996 +0.010062 +0.010153 +0.010212 +0.010045 +0.009951 +0.010012 +0.010099 +0.010206 +0.010277 +0.010106 +0.010032 +0.010127 +0.010144 +0.010248 +0.010312 +0.010142 +0.010057 +0.010116 +0.010229 +0.010312 +0.01038 +0.01021 +0.010131 +0.010158 +0.010261 +0.010369 +0.010442 +0.010248 +0.010167 +0.010238 +0.010333 +0.010439 +0.010487 +0.0103 +0.01021 +0.0103 +0.010379 +0.010469 +0.010534 +0.010339 +0.010269 +0.010322 +0.010423 +0.010539 +0.010584 +0.010413 +0.010336 +0.010402 +0.010495 +0.010583 +0.010638 +0.010458 +0.010407 +0.010446 +0.010513 +0.01064 +0.010691 +0.010509 +0.010399 +0.01046 +0.010479 +0.010556 +0.010525 +0.010324 +0.01021 +0.010236 +0.010288 +0.010335 +0.010317 +0.010107 +0.009966 +0.009975 +0.010012 +0.010034 +0.010047 +0.009836 +0.009688 +0.009703 +0.009732 +0.009768 +0.009774 +0.009547 +0.00944 +0.009431 +0.009462 +0.009523 +0.009529 +0.009323 +0.009196 +0.009185 +0.009219 +0.009258 +0.009285 +0.009084 +0.008978 +0.009008 +0.009043 +0.009095 +0.009114 +0.008914 +0.008805 +0.008836 +0.008884 +0.00892 +0.00895 +0.008767 +0.008686 +0.00871 +0.008762 +0.008817 +0.008835 +0.008654 +0.008556 +0.008597 +0.008648 +0.008721 +0.008726 +0.008568 +0.008487 +0.008546 +0.008608 +0.008664 +0.008717 +0.008572 +0.008496 +0.008547 +0.00861 +0.008701 +0.008765 +0.008617 +0.008535 +0.008612 +0.008677 +0.008757 +0.008816 +0.008669 +0.008587 +0.008629 +0.008718 +0.008793 +0.008846 +0.008703 +0.008648 +0.008694 +0.008782 +0.008831 +0.008892 +0.008759 +0.008666 +0.00873 +0.008791 +0.008879 +0.008948 +0.008796 +0.008732 +0.008773 +0.008844 +0.008926 +0.008991 +0.008842 +0.008802 +0.008804 +0.008885 +0.008982 +0.009038 +0.008901 +0.008824 +0.008882 +0.008925 +0.00902 +0.009097 +0.008937 +0.008876 +0.008905 +0.008976 +0.009073 +0.009131 +0.009 +0.008913 +0.008959 +0.009044 +0.009111 +0.009157 +0.009021 +0.008953 +0.008998 +0.009076 +0.009185 +0.009248 +0.009086 +0.009006 +0.009064 +0.009114 +0.009206 +0.009285 +0.009111 +0.009038 +0.009091 +0.009175 +0.009304 +0.009362 +0.009167 +0.009089 +0.009145 +0.009217 +0.009313 +0.009371 +0.00921 +0.009137 +0.009193 +0.009261 +0.009361 +0.009421 +0.00926 +0.009182 +0.009242 +0.00932 +0.009418 +0.009489 +0.009315 +0.00923 +0.009303 +0.009358 +0.009456 +0.009523 +0.009366 +0.00931 +0.009343 +0.009426 +0.009528 +0.009564 +0.00942 +0.009331 +0.00937 +0.009456 +0.00956 +0.009608 +0.00945 +0.009394 +0.009429 +0.009531 +0.00962 +0.009678 +0.009523 +0.00943 +0.009468 +0.009559 +0.009659 +0.009733 +0.009557 +0.009467 +0.009553 +0.009646 +0.009754 +0.009772 +0.009597 +0.009509 +0.009594 +0.009655 +0.009752 +0.009818 +0.009658 +0.009571 +0.009636 +0.009743 +0.009832 +0.009884 +0.009703 +0.009602 +0.009675 +0.009768 +0.00986 +0.00993 +0.009772 +0.009693 +0.009746 +0.009828 +0.009928 +0.009997 +0.009798 +0.009711 +0.009775 +0.009858 +0.009989 +0.010027 +0.009871 +0.009772 +0.009816 +0.00992 +0.010021 +0.010082 +0.009915 +0.009828 +0.00989 +0.009996 +0.01006 +0.010141 +0.009952 +0.009879 +0.009953 +0.010025 +0.010128 +0.010203 +0.010057 +0.009951 +0.009993 +0.010064 +0.010163 +0.010239 +0.010065 +0.009979 +0.010042 +0.010149 +0.010225 +0.010315 +0.010126 +0.010036 +0.010082 +0.010179 +0.010282 +0.010355 +0.010176 +0.010077 +0.010157 +0.010238 +0.010357 +0.010422 +0.010235 +0.010165 +0.010214 +0.010273 +0.010376 +0.010447 +0.010269 +0.010183 +0.010257 +0.010362 +0.010464 +0.010518 +0.010324 +0.010235 +0.010301 +0.010398 +0.0105 +0.010575 +0.010385 +0.010293 +0.010364 +0.010465 +0.01056 +0.010612 +0.010446 +0.010387 +0.010423 +0.010529 +0.010614 +0.010639 +0.010458 +0.010361 +0.010406 +0.01047 +0.010514 +0.010539 +0.010336 +0.010227 +0.010237 +0.010251 +0.010301 +0.010295 +0.010075 +0.009942 +0.009954 +0.010007 +0.010036 +0.010044 +0.009831 +0.009711 +0.009713 +0.009719 +0.009748 +0.009754 +0.009553 +0.009444 +0.009434 +0.009479 +0.009522 +0.009519 +0.009313 +0.009188 +0.00919 +0.009235 +0.009275 +0.009283 +0.009088 +0.008972 +0.009024 +0.009051 +0.009088 +0.009123 +0.008918 +0.008806 +0.008844 +0.008892 +0.008948 +0.00897 +0.008786 +0.008688 +0.008706 +0.008753 +0.008804 +0.008824 +0.008658 +0.008579 +0.008595 +0.008644 +0.008695 +0.008735 +0.008571 +0.008487 +0.008536 +0.008589 +0.008648 +0.008715 +0.008554 +0.008474 +0.008535 +0.008598 +0.008696 +0.008742 +0.0086 +0.008527 +0.008581 +0.008675 +0.008742 +0.008776 +0.008625 +0.008557 +0.008613 +0.008697 +0.008767 +0.008834 +0.008686 +0.00861 +0.008684 +0.008745 +0.008821 +0.008869 +0.008727 +0.008646 +0.008706 +0.008805 +0.008854 +0.00893 +0.008773 +0.008712 +0.008763 +0.008839 +0.008906 +0.008958 +0.008823 +0.00875 +0.008826 +0.008859 +0.008967 +0.009022 +0.008854 +0.00879 +0.008834 +0.008913 +0.008998 +0.00907 +0.00891 +0.008836 +0.008894 +0.008961 +0.00905 +0.009118 +0.008961 +0.008878 +0.008932 +0.009019 +0.009093 +0.00916 +0.009005 +0.008929 +0.008979 +0.009056 +0.00917 +0.009235 +0.009068 +0.008961 +0.00902 +0.009097 +0.009202 +0.009261 +0.009082 +0.009016 +0.009077 +0.009149 +0.009252 +0.009315 +0.009138 +0.009067 +0.009117 +0.009193 +0.009277 +0.009352 +0.009192 +0.00912 +0.009182 +0.009259 +0.009363 +0.009399 +0.009229 +0.009159 +0.009213 +0.009325 +0.009378 +0.009439 +0.009273 +0.009207 +0.009288 +0.009352 +0.009469 +0.009492 +0.009317 +0.009239 +0.009303 +0.009395 +0.009481 +0.009542 +0.009412 +0.009326 +0.009368 +0.009458 +0.009538 +0.009578 +0.009427 +0.009349 +0.009398 +0.009491 +0.009607 +0.009666 +0.009495 +0.009393 +0.009445 +0.009531 +0.009624 +0.009694 +0.009529 +0.009446 +0.009543 +0.009597 +0.009684 +0.00975 +0.009571 +0.009499 +0.009554 +0.009626 +0.009719 +0.009805 +0.009636 +0.009567 +0.009628 +0.00972 +0.009782 +0.009838 +0.009682 +0.009613 +0.009663 +0.009758 +0.009825 +0.009876 +0.009729 +0.009643 +0.009706 +0.009794 +0.009886 +0.009949 +0.009787 +0.0097 +0.009749 +0.009845 +0.009949 +0.010002 +0.009835 +0.009756 +0.009798 +0.00989 +0.010011 +0.010055 +0.009884 +0.00982 +0.009877 +0.009976 +0.010034 +0.010094 +0.009951 +0.009836 +0.009903 +0.009989 +0.010095 +0.010169 +0.009993 +0.009914 +0.009956 +0.01004 +0.01014 +0.010218 +0.010021 +0.009949 +0.010031 +0.010107 +0.010228 +0.010297 +0.010096 +0.009984 +0.010059 +0.010163 +0.010291 +0.010305 +0.010118 +0.010068 +0.010134 +0.010248 +0.010306 +0.010363 +0.010176 +0.010097 +0.010163 +0.010264 +0.010382 +0.010419 +0.010251 +0.010155 +0.010219 +0.010317 +0.010408 +0.010493 +0.010311 +0.010229 +0.0103 +0.01039 +0.010499 +0.010523 +0.010336 +0.010252 +0.010319 +0.010419 +0.010525 +0.010631 +0.010422 +0.010327 +0.010369 +0.010466 +0.010554 +0.010618 +0.01043 +0.010306 +0.01037 +0.01043 +0.010493 +0.010539 +0.010309 +0.010171 +0.010185 +0.010212 +0.010272 +0.010295 +0.010092 +0.009894 +0.009912 +0.009945 +0.009988 +0.009999 +0.009774 +0.009665 +0.009676 +0.009701 +0.009737 +0.009758 +0.009516 +0.009403 +0.009424 +0.009438 +0.00949 +0.009512 +0.009307 +0.009198 +0.009203 +0.009263 +0.009283 +0.009298 +0.009106 +0.009001 +0.009044 +0.009064 +0.009084 +0.009111 +0.008939 +0.008858 +0.008875 +0.008912 +0.008962 +0.008991 +0.008796 +0.008707 +0.00874 +0.008772 +0.008827 +0.008859 +0.008692 +0.008592 +0.008634 +0.008688 +0.008735 +0.008782 +0.0086 +0.008528 +0.008561 +0.008617 +0.008688 +0.00875 +0.008626 +0.008543 +0.008579 +0.008651 +0.00874 +0.008781 +0.008649 +0.008562 +0.008623 +0.008701 +0.008781 +0.008845 +0.008689 +0.008622 +0.00867 +0.008741 +0.008828 +0.008888 +0.008735 +0.008658 +0.008722 +0.00879 +0.008879 +0.008945 +0.008802 +0.008732 +0.008746 +0.008827 +0.008909 +0.009003 +0.008821 +0.008746 +0.008789 +0.008873 +0.008995 +0.009023 +0.00888 +0.008804 +0.008844 +0.008914 +0.009005 +0.009078 +0.008923 +0.008842 +0.008894 +0.008975 +0.009054 +0.00913 +0.008983 +0.00889 +0.008946 +0.009015 +0.00911 +0.009168 +0.009012 +0.008938 +0.008984 +0.009093 +0.009154 +0.00922 +0.009059 +0.008983 +0.009034 +0.009103 +0.009222 +0.009258 +0.009099 +0.009022 +0.009073 +0.009182 +0.009255 +0.009316 +0.009162 +0.009088 +0.009115 +0.009198 +0.009298 +0.00936 +0.009193 +0.009141 +0.009166 +0.009253 +0.009352 +0.009413 +0.009285 +0.009168 +0.009207 +0.009294 +0.009387 +0.009465 +0.009291 +0.009228 +0.009262 +0.009374 +0.009455 +0.009504 +0.009355 +0.009253 +0.009312 +0.009405 +0.009483 +0.009574 +0.009408 +0.009326 +0.009372 +0.009454 +0.009546 +0.009612 +0.009434 +0.009357 +0.009422 +0.009526 +0.009604 +0.009664 +0.009494 +0.009396 +0.009473 +0.009547 +0.009641 +0.009712 +0.009529 +0.00945 +0.009534 +0.009609 +0.009702 +0.009771 +0.009592 +0.0095 +0.009568 +0.009652 +0.009756 +0.009802 +0.009634 +0.009561 +0.009625 +0.009715 +0.009813 +0.009896 +0.009665 +0.009595 +0.009663 +0.00976 +0.00984 +0.009899 +0.00973 +0.009656 +0.009709 +0.009797 +0.00991 +0.009952 +0.009798 +0.009717 +0.009764 +0.009847 +0.009961 +0.010023 +0.009849 +0.009754 +0.009822 +0.009908 +0.009997 +0.010065 +0.009929 +0.009824 +0.009877 +0.009966 +0.010057 +0.010103 +0.009961 +0.009851 +0.009904 +0.009997 +0.010098 +0.010184 +0.010013 +0.009931 +0.009981 +0.010051 +0.010151 +0.010227 +0.010045 +0.009962 +0.010031 +0.010103 +0.01021 +0.010282 +0.010101 +0.010015 +0.010073 +0.010196 +0.010291 +0.010319 +0.010157 +0.01006 +0.010127 +0.010232 +0.010307 +0.010386 +0.010191 +0.010111 +0.010191 +0.010289 +0.010391 +0.010457 +0.010253 +0.010161 +0.010233 +0.010324 +0.010417 +0.010498 +0.010307 +0.010214 +0.010296 +0.010393 +0.010516 +0.010532 +0.010354 +0.010267 +0.01034 +0.010427 +0.010541 +0.010632 +0.010403 +0.010322 +0.010396 +0.010482 +0.010583 +0.010674 +0.010471 +0.010391 +0.010469 +0.010546 +0.010638 +0.010693 +0.010491 +0.010397 +0.010458 +0.010543 +0.01064 +0.010578 +0.010376 +0.010254 +0.010266 +0.010326 +0.010372 +0.010362 +0.010151 +0.010009 +0.010028 +0.010103 +0.010114 +0.010123 +0.00989 +0.00976 +0.009763 +0.009806 +0.00983 +0.009832 +0.009624 +0.009501 +0.009497 +0.009538 +0.009576 +0.009599 +0.009374 +0.009254 +0.009247 +0.009296 +0.009331 +0.00934 +0.009159 +0.009025 +0.009061 +0.009095 +0.009138 +0.009162 +0.008966 +0.008866 +0.008898 +0.008953 +0.009008 +0.009018 +0.008839 +0.008728 +0.008751 +0.008817 +0.008855 +0.008866 +0.008696 +0.008606 +0.008635 +0.008715 +0.008752 +0.008777 +0.0086 +0.008541 +0.008546 +0.008615 +0.008674 +0.008717 +0.008575 +0.008501 +0.008551 +0.008632 +0.008726 +0.008775 +0.008629 +0.00855 +0.0086 +0.008673 +0.008739 +0.008809 +0.008654 +0.008577 +0.008645 +0.008732 +0.008795 +0.008855 +0.008711 +0.008635 +0.008712 +0.008761 +0.008836 +0.008896 +0.008753 +0.008681 +0.008747 +0.008837 +0.008877 +0.008945 +0.008787 +0.008715 +0.008775 +0.008854 +0.008936 +0.008989 +0.008859 +0.008781 +0.008831 +0.008906 +0.008992 +0.009026 +0.008886 +0.008819 +0.008864 +0.00894 +0.00903 +0.009095 +0.008962 +0.008865 +0.008922 +0.008992 +0.00908 +0.009126 +0.008978 +0.008902 +0.008957 +0.009054 +0.009116 +0.009176 +0.009045 +0.008967 +0.009011 +0.009093 +0.009187 +0.00924 +0.009067 +0.008994 +0.009042 +0.009127 +0.009219 +0.009303 +0.009118 +0.009039 +0.009102 +0.009188 +0.009312 +0.009321 +0.009157 +0.009077 +0.009145 +0.009234 +0.00932 +0.009393 +0.009228 +0.009128 +0.009189 +0.009272 +0.009372 +0.009424 +0.009257 +0.009198 +0.009258 +0.009345 +0.009432 +0.009487 +0.009309 +0.009226 +0.009284 +0.009364 +0.009451 +0.009534 +0.009372 +0.009309 +0.009349 +0.009446 +0.009528 +0.009572 +0.009396 +0.009317 +0.009374 +0.009469 +0.009554 +0.009622 +0.009488 +0.009406 +0.009449 +0.009529 +0.009619 +0.009664 +0.009496 +0.00942 +0.009472 +0.00957 +0.00969 +0.009729 +0.009558 +0.009483 +0.009543 +0.009649 +0.009706 +0.009755 +0.009602 +0.00952 +0.009611 +0.009674 +0.009766 +0.009835 +0.009651 +0.00958 +0.009643 +0.009714 +0.009801 +0.009886 +0.009709 +0.009637 +0.009704 +0.009793 +0.00986 +0.009924 +0.009768 +0.009668 +0.009734 +0.009818 +0.009936 +0.010014 +0.009811 +0.009729 +0.009795 +0.009864 +0.009965 +0.010046 +0.009859 +0.009775 +0.009824 +0.009935 +0.010051 +0.010099 +0.009916 +0.009837 +0.009873 +0.009959 +0.010065 +0.010133 +0.009967 +0.009887 +0.009948 +0.01005 +0.01015 +0.010218 +0.010048 +0.009897 +0.009978 +0.010072 +0.010173 +0.010242 +0.010069 +0.009985 +0.010048 +0.010136 +0.010236 +0.010307 +0.010116 +0.010032 +0.0101 +0.010191 +0.010293 +0.010385 +0.010162 +0.01008 +0.010149 +0.010239 +0.010333 +0.010421 +0.010259 +0.01015 +0.010224 +0.010284 +0.01038 +0.01046 +0.010276 +0.010184 +0.010257 +0.010335 +0.010456 +0.010533 +0.010343 +0.010254 +0.01031 +0.010383 +0.0105 +0.010568 +0.010384 +0.010306 +0.010371 +0.010458 +0.01058 +0.010645 +0.010473 +0.010349 +0.010386 +0.010473 +0.010612 +0.010625 +0.010405 +0.010302 +0.010344 +0.010405 +0.010474 +0.010464 +0.010237 +0.010096 +0.010121 +0.01015 +0.010204 +0.010197 +0.009982 +0.00985 +0.009852 +0.009895 +0.009921 +0.009919 +0.009699 +0.009573 +0.009595 +0.009639 +0.009663 +0.009671 +0.009455 +0.009353 +0.009337 +0.00936 +0.009412 +0.009424 +0.009217 +0.009089 +0.009125 +0.009156 +0.009194 +0.009209 +0.00902 +0.008897 +0.008912 +0.008968 +0.009022 +0.009058 +0.008868 +0.008764 +0.008792 +0.008837 +0.008883 +0.008924 +0.008761 +0.008629 +0.008657 +0.008697 +0.00876 +0.008806 +0.008628 +0.008544 +0.008576 +0.008637 +0.008686 +0.008724 +0.008572 +0.00849 +0.008533 +0.008602 +0.008697 +0.008768 +0.008604 +0.008527 +0.008586 +0.008652 +0.008739 +0.00878 +0.00864 +0.008566 +0.008618 +0.008702 +0.008791 +0.008857 +0.008687 +0.008621 +0.008666 +0.008745 +0.008834 +0.008874 +0.008731 +0.008657 +0.008737 +0.008781 +0.008864 +0.008933 +0.008775 +0.008714 +0.008749 +0.008838 +0.008904 +0.008972 +0.00883 +0.00875 +0.008802 +0.008874 +0.008971 +0.009025 +0.008867 +0.0088 +0.008849 +0.008953 +0.009038 +0.00907 +0.008897 +0.008837 +0.008896 +0.008967 +0.009056 +0.009121 +0.008968 +0.00889 +0.008939 +0.009017 +0.009102 +0.009166 +0.009002 +0.008947 +0.008982 +0.009062 +0.009172 +0.009224 +0.009057 +0.008976 +0.009037 +0.009116 +0.009196 +0.009265 +0.009108 +0.009054 +0.009073 +0.009155 +0.009255 +0.009317 +0.00915 +0.009069 +0.009119 +0.009209 +0.009301 +0.009356 +0.009194 +0.009129 +0.009208 +0.00926 +0.009359 +0.009404 +0.009231 +0.009158 +0.00922 +0.009283 +0.009393 +0.009468 +0.009293 +0.009223 +0.00929 +0.009374 +0.009485 +0.009499 +0.009334 +0.009245 +0.009313 +0.00941 +0.009488 +0.00955 +0.0094 +0.009312 +0.009361 +0.009439 +0.00954 +0.009606 +0.009435 +0.009389 +0.00941 +0.009504 +0.009618 +0.009665 +0.009476 +0.009403 +0.009453 +0.009547 +0.009648 +0.00971 +0.009571 +0.009466 +0.00952 +0.009606 +0.009701 +0.009758 +0.009571 +0.009501 +0.009574 +0.009651 +0.009733 +0.009802 +0.009647 +0.009571 +0.009629 +0.009724 +0.009789 +0.009842 +0.009687 +0.009583 +0.009662 +0.009756 +0.009845 +0.009915 +0.009754 +0.009679 +0.009761 +0.009809 +0.009873 +0.009943 +0.009771 +0.009715 +0.009775 +0.009837 +0.009975 +0.010039 +0.009843 +0.009765 +0.009815 +0.009887 +0.010001 +0.010058 +0.009884 +0.009814 +0.009884 +0.009938 +0.010051 +0.010121 +0.009944 +0.009863 +0.009914 +0.010044 +0.010118 +0.010163 +0.009994 +0.00991 +0.009973 +0.010058 +0.010156 +0.010235 +0.010056 +0.009953 +0.010053 +0.010114 +0.010225 +0.010296 +0.010092 +0.01 +0.010074 +0.010154 +0.01025 +0.010344 +0.010154 +0.010072 +0.010141 +0.01025 +0.01038 +0.010366 +0.010192 +0.010109 +0.010183 +0.010276 +0.010365 +0.010446 +0.010279 +0.010182 +0.010243 +0.010345 +0.010421 +0.010507 +0.010298 +0.010216 +0.010282 +0.01038 +0.010501 +0.010567 +0.010386 +0.01029 +0.01033 +0.010434 +0.010576 +0.010598 +0.010411 +0.010329 +0.010396 +0.0105 +0.010621 +0.010621 +0.010446 +0.010331 +0.010396 +0.010461 +0.010503 +0.01053 +0.01034 +0.010184 +0.010208 +0.010256 +0.010299 +0.010294 +0.010085 +0.009959 +0.00998 +0.010049 +0.010089 +0.01007 +0.009823 +0.0097 +0.009718 +0.009759 +0.009794 +0.009788 +0.009599 +0.009486 +0.009477 +0.009505 +0.009542 +0.009521 +0.009322 +0.009226 +0.009221 +0.009259 +0.009301 +0.009303 +0.009125 +0.009011 +0.009038 +0.009062 +0.009098 +0.009108 +0.008928 +0.008856 +0.008854 +0.008892 +0.008939 +0.008951 +0.008784 +0.008684 +0.008714 +0.008752 +0.008804 +0.008817 +0.008667 +0.008563 +0.008588 +0.008633 +0.008683 +0.008735 +0.00855 +0.00847 +0.008515 +0.008579 +0.008627 +0.008682 +0.008538 +0.00847 +0.008532 +0.008591 +0.008694 +0.008751 +0.008615 +0.008493 +0.008555 +0.008643 +0.008709 +0.008768 +0.00862 +0.008538 +0.008602 +0.008697 +0.008763 +0.008824 +0.008674 +0.008595 +0.00863 +0.008715 +0.008813 +0.008855 +0.008707 +0.008637 +0.00869 +0.008793 +0.008852 +0.008913 +0.008766 +0.008681 +0.008719 +0.008803 +0.008902 +0.008963 +0.008786 +0.008716 +0.008769 +0.008845 +0.008963 +0.008992 +0.008832 +0.008763 +0.008812 +0.008888 +0.008977 +0.009049 +0.008883 +0.008808 +0.008881 +0.008933 +0.009023 +0.009091 +0.008939 +0.008853 +0.008903 +0.008974 +0.009072 +0.009136 +0.008992 +0.008918 +0.008983 +0.009048 +0.009102 +0.009166 +0.009009 +0.008931 +0.009001 +0.009061 +0.009182 +0.009243 +0.009067 +0.008996 +0.009045 +0.009126 +0.00919 +0.009259 +0.009119 +0.009021 +0.009087 +0.00916 +0.009248 +0.009332 +0.009167 +0.009091 +0.009131 +0.009205 +0.009297 +0.009365 +0.009225 +0.009121 +0.009166 +0.009253 +0.009338 +0.009411 +0.009253 +0.009162 +0.009224 +0.009299 +0.009412 +0.009452 +0.009293 +0.009212 +0.009263 +0.009362 +0.009437 +0.009494 +0.009348 +0.009267 +0.009315 +0.009391 +0.009483 +0.00954 +0.009377 +0.0093 +0.009373 +0.009462 +0.009532 +0.009611 +0.009423 +0.009342 +0.009413 +0.009482 +0.009575 +0.009659 +0.009494 +0.009406 +0.00947 +0.009546 +0.009664 +0.009721 +0.00953 +0.009452 +0.009507 +0.009594 +0.009675 +0.009737 +0.009603 +0.009485 +0.009564 +0.009658 +0.009749 +0.009823 +0.009608 +0.009538 +0.00959 +0.009705 +0.009764 +0.009822 +0.009669 +0.009609 +0.009656 +0.009746 +0.009837 +0.009889 +0.009717 +0.009645 +0.009691 +0.009777 +0.009878 +0.009963 +0.009775 +0.009692 +0.009758 +0.009832 +0.009921 +0.009992 +0.009838 +0.009746 +0.009806 +0.009872 +0.009969 +0.010048 +0.009863 +0.009784 +0.009841 +0.009921 +0.010026 +0.010101 +0.009926 +0.009856 +0.009895 +0.009997 +0.010099 +0.010134 +0.00997 +0.009867 +0.009933 +0.010039 +0.010141 +0.010215 +0.010044 +0.009956 +0.010008 +0.01007 +0.010174 +0.010234 +0.010064 +0.009975 +0.010044 +0.010165 +0.010263 +0.010299 +0.010105 +0.010023 +0.010091 +0.010184 +0.010293 +0.010338 +0.010189 +0.010112 +0.010158 +0.010247 +0.010346 +0.010387 +0.010206 +0.010137 +0.010233 +0.010321 +0.010392 +0.010434 +0.010255 +0.010181 +0.010238 +0.010331 +0.010443 +0.010499 +0.010334 +0.010242 +0.010312 +0.010411 +0.010501 +0.010551 +0.01037 +0.01029 +0.010348 +0.010437 +0.010559 +0.010639 +0.010433 +0.010347 +0.010414 +0.010462 +0.010541 +0.010604 +0.010344 +0.010232 +0.010301 +0.010343 +0.010409 +0.01043 +0.0102 +0.010056 +0.010084 +0.010127 +0.010191 +0.010204 +0.009968 +0.00983 +0.009834 +0.009873 +0.009921 +0.009945 +0.009695 +0.009559 +0.009592 +0.009653 +0.009719 +0.009673 +0.009449 +0.009312 +0.009356 +0.009379 +0.009414 +0.009423 +0.009226 +0.009089 +0.009111 +0.00916 +0.009203 +0.009217 +0.009025 +0.008901 +0.008922 +0.008976 +0.00903 +0.009064 +0.008874 +0.008747 +0.008788 +0.00883 +0.008892 +0.008918 +0.008733 +0.008649 +0.008675 +0.008713 +0.00878 +0.008809 +0.008635 +0.008542 +0.008574 +0.008632 +0.008698 +0.00873 +0.008573 +0.008489 +0.008536 +0.008613 +0.008707 +0.008772 +0.008613 +0.00854 +0.00857 +0.008651 +0.008722 +0.008794 +0.008648 +0.008573 +0.008612 +0.008691 +0.008786 +0.008855 +0.008714 +0.008616 +0.008656 +0.008735 +0.008818 +0.008883 +0.008735 +0.008654 +0.00872 +0.008814 +0.008883 +0.008923 +0.008768 +0.008699 +0.008746 +0.008829 +0.00891 +0.008966 +0.008822 +0.008754 +0.008816 +0.008878 +0.008952 +0.009018 +0.008857 +0.008787 +0.008837 +0.008918 +0.009024 +0.009081 +0.008911 +0.008833 +0.008887 +0.008964 +0.009061 +0.009095 +0.008946 +0.008878 +0.008917 +0.009005 +0.009104 +0.009147 +0.008994 +0.008926 +0.008979 +0.009044 +0.009136 +0.009197 +0.009047 +0.008964 +0.009028 +0.009117 +0.009187 +0.00924 +0.009081 +0.00901 +0.009093 +0.009145 +0.009224 +0.009285 +0.009165 +0.009063 +0.009105 +0.009188 +0.009279 +0.00934 +0.009166 +0.009089 +0.009158 +0.00923 +0.009334 +0.009398 +0.009212 +0.00914 +0.009191 +0.009279 +0.009364 +0.009426 +0.00928 +0.009197 +0.009256 +0.009352 +0.009438 +0.009505 +0.009313 +0.009225 +0.009275 +0.009363 +0.009467 +0.009535 +0.009364 +0.009299 +0.009352 +0.009435 +0.009523 +0.009566 +0.00941 +0.009327 +0.009389 +0.009457 +0.009558 +0.009619 +0.009461 +0.009374 +0.009438 +0.009532 +0.009604 +0.009664 +0.009515 +0.009447 +0.009509 +0.009571 +0.009658 +0.009718 +0.009544 +0.009471 +0.00953 +0.009637 +0.009705 +0.009755 +0.009606 +0.009538 +0.009579 +0.009675 +0.009775 +0.009814 +0.009643 +0.009561 +0.009622 +0.009705 +0.009825 +0.009865 +0.009695 +0.009601 +0.009681 +0.009776 +0.009884 +0.009906 +0.009733 +0.009672 +0.009738 +0.00982 +0.009893 +0.009964 +0.009796 +0.009718 +0.009765 +0.009856 +0.009947 +0.010048 +0.009863 +0.00977 +0.00983 +0.009905 +0.010009 +0.010056 +0.009899 +0.0098 +0.009853 +0.009957 +0.010085 +0.010172 +0.009946 +0.009888 +0.009896 +0.009996 +0.010097 +0.010162 +0.009987 +0.009916 +0.009956 +0.010049 +0.010165 +0.010213 +0.010057 +0.00996 +0.010018 +0.01011 +0.010228 +0.010289 +0.010101 +0.010012 +0.010073 +0.010148 +0.010262 +0.010333 +0.010168 +0.010051 +0.010126 +0.010221 +0.010356 +0.010353 +0.010174 +0.010109 +0.010162 +0.010262 +0.01036 +0.010446 +0.010264 +0.010182 +0.010227 +0.010316 +0.010402 +0.01047 +0.010298 +0.010209 +0.010295 +0.010363 +0.010463 +0.010528 +0.01038 +0.010258 +0.010304 +0.010415 +0.010526 +0.010607 +0.010447 +0.010313 +0.010359 +0.010458 +0.010571 +0.010637 +0.010454 +0.010373 +0.010441 +0.010524 +0.010621 +0.010664 +0.010443 +0.010334 +0.010362 +0.010416 +0.010504 +0.01054 +0.010305 +0.010173 +0.010224 +0.010257 +0.010258 +0.01028 +0.010064 +0.009938 +0.009974 +0.009969 +0.01003 +0.010033 +0.009819 +0.009706 +0.009688 +0.009723 +0.009785 +0.009797 +0.00958 +0.00946 +0.009472 +0.00949 +0.009535 +0.009562 +0.009348 +0.009219 +0.009254 +0.009286 +0.009357 +0.009342 +0.009146 +0.009049 +0.009057 +0.009093 +0.009132 +0.009163 +0.008982 +0.00887 +0.008891 +0.008925 +0.009002 +0.009018 +0.008844 +0.00874 +0.008755 +0.008791 +0.008846 +0.008893 +0.008698 +0.008596 +0.008628 +0.00868 +0.008734 +0.008786 +0.008645 +0.008549 +0.008611 +0.008665 +0.008698 +0.008754 +0.008621 +0.008553 +0.008601 +0.008679 +0.008747 +0.008836 +0.008674 +0.008605 +0.008658 +0.008727 +0.008797 +0.008846 +0.008716 +0.008632 +0.008687 +0.008768 +0.008858 +0.008908 +0.008754 +0.008686 +0.008732 +0.008793 +0.008882 +0.008953 +0.008818 +0.008738 +0.008773 +0.008848 +0.008988 +0.008995 +0.008851 +0.008763 +0.008804 +0.008892 +0.008979 +0.009041 +0.008886 +0.008823 +0.008859 +0.008935 +0.00904 +0.009097 +0.008944 +0.008864 +0.008904 +0.008976 +0.009061 +0.009136 +0.00898 +0.008895 +0.008947 +0.009053 +0.009154 +0.009199 +0.009021 +0.008941 +0.00898 +0.009067 +0.009178 +0.009209 +0.00906 +0.009005 +0.009033 +0.009116 +0.009212 +0.009278 +0.00911 +0.009031 +0.009089 +0.00916 +0.009257 +0.009321 +0.009158 +0.009095 +0.009129 +0.009222 +0.009297 +0.009359 +0.009216 +0.009131 +0.009199 +0.009253 +0.009345 +0.009414 +0.009261 +0.009186 +0.009213 +0.009328 +0.009393 +0.009441 +0.009289 +0.009209 +0.00927 +0.009361 +0.009455 +0.009528 +0.009356 +0.009269 +0.009316 +0.009392 +0.009483 +0.00954 +0.009388 +0.009305 +0.009356 +0.009455 +0.009566 +0.009623 +0.009432 +0.009342 +0.009404 +0.009486 +0.009581 +0.009647 +0.009481 +0.009408 +0.009458 +0.009544 +0.009662 +0.009688 +0.00953 +0.009453 +0.009502 +0.009581 +0.009687 +0.009781 +0.009578 +0.009486 +0.009562 +0.009636 +0.009721 +0.009793 +0.009629 +0.009562 +0.009601 +0.009713 +0.009777 +0.009845 +0.009674 +0.009577 +0.009647 +0.009733 +0.009823 +0.009886 +0.009726 +0.009638 +0.009709 +0.009808 +0.00989 +0.009939 +0.009755 +0.009677 +0.00974 +0.009831 +0.009929 +0.009991 +0.009814 +0.009739 +0.009823 +0.009892 +0.009975 +0.010019 +0.009892 +0.009781 +0.00983 +0.009924 +0.010036 +0.010088 +0.009918 +0.009842 +0.009886 +0.00998 +0.010074 +0.010144 +0.009967 +0.009892 +0.009967 +0.010026 +0.010129 +0.010198 +0.010022 +0.009926 +0.009992 +0.0101 +0.010193 +0.010257 +0.010071 +0.009989 +0.010056 +0.010141 +0.010213 +0.010293 +0.010107 +0.010023 +0.010107 +0.0102 +0.010301 +0.01034 +0.010186 +0.010065 +0.010137 +0.010226 +0.010333 +0.010417 +0.010234 +0.010121 +0.010196 +0.010302 +0.010426 +0.010433 +0.010263 +0.010171 +0.010246 +0.010334 +0.010463 +0.010519 +0.010312 +0.010229 +0.0103 +0.010395 +0.010495 +0.010554 +0.010383 +0.010303 +0.010373 +0.010453 +0.010531 +0.010602 +0.010418 +0.010334 +0.010401 +0.010508 +0.010644 +0.010677 +0.010419 +0.010338 +0.010396 +0.01047 +0.010508 +0.010532 +0.010329 +0.010202 +0.010239 +0.010286 +0.010335 +0.010321 +0.010075 +0.009952 +0.009975 +0.010011 +0.010052 +0.010036 +0.009835 +0.009709 +0.009726 +0.009764 +0.009799 +0.009777 +0.009544 +0.009429 +0.009455 +0.009474 +0.009499 +0.009507 +0.00931 +0.00921 +0.009221 +0.009248 +0.009286 +0.009283 +0.009088 +0.008985 +0.009016 +0.009058 +0.009087 +0.009141 +0.00894 +0.008843 +0.008875 +0.008921 +0.008947 +0.008968 +0.008803 +0.008708 +0.008753 +0.00878 +0.008832 +0.008837 +0.008667 +0.008603 +0.008614 +0.008663 +0.00871 +0.008749 +0.008575 +0.008496 +0.008547 +0.008598 +0.008658 +0.008698 +0.00856 +0.008471 +0.00853 +0.008597 +0.008679 +0.008739 +0.008589 +0.008519 +0.008558 +0.008641 +0.008727 +0.008798 +0.008657 +0.008568 +0.008606 +0.008678 +0.008759 +0.008821 +0.008678 +0.008592 +0.008654 +0.008746 +0.008826 +0.00887 +0.008723 +0.008651 +0.008696 +0.008783 +0.008845 +0.008909 +0.008762 +0.008691 +0.008744 +0.00882 +0.008901 +0.008963 +0.008817 +0.00873 +0.008772 +0.008853 +0.008941 +0.009039 +0.008848 +0.008779 +0.008821 +0.008894 +0.008977 +0.009048 +0.008891 +0.008818 +0.00888 +0.008944 +0.009027 +0.009117 +0.00896 +0.008865 +0.008921 +0.009002 +0.009061 +0.00913 +0.008981 +0.008927 +0.008952 +0.009031 +0.009122 +0.009187 +0.009043 +0.008965 +0.009013 +0.009121 +0.009154 +0.009246 +0.009052 +0.008986 +0.00905 +0.009117 +0.00921 +0.00927 +0.009122 +0.009038 +0.009094 +0.009182 +0.00926 +0.009323 +0.009169 +0.009089 +0.00914 +0.00922 +0.009316 +0.009368 +0.009211 +0.00914 +0.009185 +0.009259 +0.009347 +0.009428 +0.009286 +0.00919 +0.009241 +0.00931 +0.009415 +0.009461 +0.009291 +0.009211 +0.009273 +0.009359 +0.009443 +0.009516 +0.00938 +0.009286 +0.009335 +0.009419 +0.009491 +0.009543 +0.009387 +0.009311 +0.009363 +0.009451 +0.009544 +0.009613 +0.009461 +0.009382 +0.009442 +0.009543 +0.009579 +0.009629 +0.009476 +0.0094 +0.009459 +0.009577 +0.009628 +0.009708 +0.009533 +0.009455 +0.009525 +0.009596 +0.009685 +0.009752 +0.00959 +0.009492 +0.009557 +0.009655 +0.009737 +0.009804 +0.009646 +0.009546 +0.009608 +0.009698 +0.009803 +0.009887 +0.009678 +0.009605 +0.009651 +0.009731 +0.009835 +0.009894 +0.009726 +0.009654 +0.009698 +0.009784 +0.009922 +0.009969 +0.009782 +0.009696 +0.009736 +0.009835 +0.009944 +0.009995 +0.009826 +0.009739 +0.009826 +0.009901 +0.01 +0.010063 +0.009901 +0.009824 +0.009836 +0.009916 +0.010018 +0.01011 +0.009923 +0.00983 +0.009902 +0.009988 +0.010094 +0.010149 +0.009974 +0.009889 +0.009948 +0.010043 +0.010159 +0.010194 +0.010031 +0.009938 +0.010002 +0.010095 +0.01018 +0.010257 +0.010084 +0.010032 +0.010077 +0.010147 +0.010254 +0.010282 +0.010113 +0.010021 +0.010099 +0.010199 +0.010299 +0.010354 +0.010212 +0.010109 +0.010146 +0.010237 +0.010333 +0.010404 +0.010234 +0.010135 +0.010187 +0.010323 +0.010417 +0.010474 +0.010294 +0.0102 +0.010272 +0.010329 +0.010431 +0.010503 +0.010327 +0.010238 +0.010322 +0.010425 +0.010522 +0.010561 +0.010375 +0.010289 +0.010344 +0.010479 +0.010543 +0.010607 +0.01042 +0.010319 +0.010384 +0.010478 +0.010538 +0.010584 +0.01036 +0.010227 +0.010277 +0.010367 +0.010397 +0.01038 +0.010157 +0.01001 +0.010049 +0.010075 +0.010129 +0.010146 +0.009909 +0.009771 +0.0098 +0.009835 +0.009874 +0.009895 +0.009672 +0.009524 +0.009543 +0.009599 +0.009639 +0.009646 +0.009451 +0.009304 +0.009314 +0.009378 +0.009413 +0.009446 +0.0092 +0.009086 +0.009113 +0.009165 +0.009224 +0.009237 +0.009046 +0.008951 +0.008946 +0.009001 +0.009049 +0.009077 +0.00888 +0.008777 +0.008817 +0.008871 +0.008939 +0.008958 +0.008775 +0.008665 +0.00869 +0.008755 +0.008808 +0.008843 +0.008666 +0.008585 +0.008623 +0.008725 +0.008746 +0.008798 +0.00863 +0.008551 +0.008612 +0.008683 +0.008753 +0.008811 +0.008676 +0.008599 +0.008637 +0.008727 +0.008809 +0.008857 +0.008709 +0.008645 +0.008677 +0.008767 +0.008852 +0.008913 +0.008769 +0.008677 +0.008739 +0.008808 +0.008909 +0.008952 +0.008804 +0.00875 +0.00879 +0.00885 +0.008935 +0.009006 +0.008841 +0.008772 +0.008831 +0.008907 +0.008994 +0.009034 +0.008883 +0.008806 +0.008903 +0.00893 +0.009023 +0.009086 +0.008929 +0.008858 +0.008912 +0.009004 +0.009084 +0.009129 +0.008986 +0.008902 +0.008952 +0.009029 +0.009121 +0.009207 +0.009033 +0.008959 +0.009 +0.009082 +0.009177 +0.009229 +0.009061 +0.008984 +0.009049 +0.00912 +0.009215 +0.009289 +0.009116 +0.009026 +0.009109 +0.009164 +0.009251 +0.009315 +0.009161 +0.009082 +0.009141 +0.009219 +0.009315 +0.009382 +0.009209 +0.009127 +0.009196 +0.009287 +0.009344 +0.009402 +0.00924 +0.00918 +0.009232 +0.009341 +0.009422 +0.009471 +0.009282 +0.009217 +0.009269 +0.009351 +0.009443 +0.009507 +0.009346 +0.00927 +0.009317 +0.009428 +0.009502 +0.009565 +0.009392 +0.009312 +0.009365 +0.009447 +0.009549 +0.00963 +0.009444 +0.009366 +0.009428 +0.009514 +0.009602 +0.009656 +0.009475 +0.009398 +0.009485 +0.009524 +0.00963 +0.009718 +0.009532 +0.009438 +0.009519 +0.009597 +0.009689 +0.009763 +0.009577 +0.009497 +0.009553 +0.009639 +0.009751 +0.009813 +0.009633 +0.009574 +0.009628 +0.009693 +0.009772 +0.009846 +0.009676 +0.009605 +0.009662 +0.009749 +0.009844 +0.009901 +0.009753 +0.00964 +0.009696 +0.009782 +0.009892 +0.009933 +0.009778 +0.009715 +0.009762 +0.009858 +0.009941 +0.009987 +0.009824 +0.009743 +0.009805 +0.009924 +0.009981 +0.010052 +0.00991 +0.009796 +0.009858 +0.009928 +0.010021 +0.010094 +0.009933 +0.009834 +0.009921 +0.009985 +0.01008 +0.010162 +0.009968 +0.00989 +0.009949 +0.010033 +0.010141 +0.010209 +0.010031 +0.009962 +0.009999 +0.010105 +0.010233 +0.010245 +0.010072 +0.009972 +0.010043 +0.010178 +0.010257 +0.010313 +0.010141 +0.010034 +0.010092 +0.010192 +0.010297 +0.010347 +0.010178 +0.010079 +0.010151 +0.01028 +0.010361 +0.010403 +0.010232 +0.010133 +0.010202 +0.010308 +0.010421 +0.010477 +0.010297 +0.010196 +0.01026 +0.010361 +0.010437 +0.010495 +0.010337 +0.010228 +0.010299 +0.0104 +0.010514 +0.010591 +0.010408 +0.010308 +0.010355 +0.010445 +0.010539 +0.010616 +0.010441 +0.010348 +0.010403 +0.010513 +0.01064 +0.010692 +0.010487 +0.010377 +0.010463 +0.010549 +0.010646 +0.010703 +0.010514 +0.010414 +0.01043 +0.010492 +0.01054 +0.010562 +0.010355 +0.010225 +0.010239 +0.010306 +0.01036 +0.010334 +0.010103 +0.009959 +0.009978 +0.010021 +0.010061 +0.010096 +0.00986 +0.009753 +0.00972 +0.009757 +0.009789 +0.009784 +0.009582 +0.009451 +0.009475 +0.009497 +0.009568 +0.009542 +0.009345 +0.009231 +0.009231 +0.009265 +0.009305 +0.009329 +0.009134 +0.009021 +0.009052 +0.009085 +0.009132 +0.009145 +0.008969 +0.008885 +0.008896 +0.008919 +0.008966 +0.008992 +0.008853 +0.008716 +0.008755 +0.008798 +0.008856 +0.008861 +0.008706 +0.0086 +0.008632 +0.008676 +0.008732 +0.008775 +0.008614 +0.008527 +0.008566 +0.008642 +0.008697 +0.008749 +0.008596 +0.008528 +0.008574 +0.008648 +0.008736 +0.008791 +0.008662 +0.008573 +0.008626 +0.008695 +0.008777 +0.008837 +0.008677 +0.008625 +0.00865 +0.008726 +0.008796 +0.008869 +0.008717 +0.008656 +0.008718 +0.008791 +0.008881 +0.008931 +0.008776 +0.00869 +0.008747 +0.008838 +0.008896 +0.008969 +0.00881 +0.008736 +0.008793 +0.008865 +0.00898 +0.009021 +0.008854 +0.008785 +0.008822 +0.008906 +0.009 +0.00906 +0.008918 +0.008845 +0.008876 +0.008948 +0.009038 +0.009107 +0.008947 +0.008872 +0.008922 +0.008995 +0.009088 +0.009165 +0.009032 +0.008916 +0.008962 +0.009046 +0.009127 +0.009192 +0.009038 +0.008965 +0.009036 +0.009095 +0.009209 +0.009234 +0.009083 +0.00901 +0.009058 +0.009128 +0.009218 +0.009287 +0.009126 +0.009048 +0.009101 +0.009179 +0.009288 +0.009349 +0.009183 +0.0091 +0.00914 +0.009231 +0.00931 +0.009375 +0.009221 +0.009138 +0.009194 +0.00928 +0.009373 +0.009465 +0.009276 +0.00918 +0.009233 +0.009334 +0.009402 +0.009468 +0.009323 +0.009241 +0.009306 +0.009375 +0.009456 +0.009523 +0.009354 +0.009272 +0.009328 +0.009397 +0.009507 +0.009585 +0.009431 +0.009335 +0.009385 +0.009472 +0.009548 +0.009614 +0.009457 +0.009381 +0.009442 +0.009511 +0.009618 +0.009669 +0.009507 +0.009441 +0.009479 +0.009545 +0.00965 +0.009694 +0.009547 +0.009457 +0.009515 +0.009609 +0.009701 +0.009782 +0.009599 +0.009518 +0.009584 +0.009642 +0.009751 +0.009822 +0.00964 +0.009556 +0.009634 +0.009717 +0.009844 +0.009855 +0.00969 +0.009603 +0.009654 +0.009758 +0.00984 +0.009926 +0.009776 +0.009657 +0.009708 +0.00981 +0.009893 +0.009961 +0.009794 +0.009707 +0.00976 +0.009851 +0.009952 +0.010027 +0.009855 +0.009752 +0.00981 +0.009893 +0.010014 +0.010092 +0.009911 +0.009785 +0.009859 +0.009965 +0.01007 +0.010112 +0.00992 +0.009852 +0.009902 +0.010008 +0.010098 +0.010159 +0.009986 +0.009905 +0.00997 +0.01005 +0.010151 +0.010221 +0.010049 +0.009946 +0.010012 +0.010101 +0.010213 +0.010278 +0.010123 +0.010015 +0.010053 +0.010168 +0.010233 +0.010317 +0.010128 +0.010048 +0.010141 +0.010219 +0.0103 +0.010376 +0.010177 +0.010102 +0.01017 +0.010246 +0.010361 +0.010452 +0.010257 +0.010168 +0.010224 +0.010302 +0.010404 +0.010474 +0.010299 +0.010237 +0.010264 +0.010366 +0.010471 +0.010569 +0.010333 +0.010229 +0.010311 +0.010404 +0.01052 +0.010596 +0.010398 +0.010303 +0.010366 +0.010456 +0.010566 +0.01064 +0.010448 +0.010353 +0.010447 +0.010523 +0.01062 +0.010675 +0.010468 +0.010373 +0.010363 +0.010418 +0.010488 +0.010506 +0.010299 +0.010201 +0.010177 +0.010233 +0.010285 +0.010276 +0.010065 +0.009921 +0.009936 +0.009977 +0.010029 +0.010034 +0.009794 +0.009663 +0.009673 +0.009697 +0.009752 +0.009748 +0.009542 +0.009426 +0.009451 +0.00948 +0.009476 +0.009467 +0.009273 +0.009156 +0.009166 +0.009199 +0.009238 +0.009267 +0.009089 +0.008963 +0.008983 +0.009016 +0.009054 +0.009067 +0.008899 +0.008788 +0.008816 +0.008876 +0.008911 +0.008929 +0.008758 +0.008669 +0.008677 +0.008725 +0.008782 +0.008815 +0.008668 +0.008572 +0.008568 +0.008635 +0.008687 +0.008721 +0.008555 +0.008468 +0.008507 +0.008573 +0.008642 +0.0087 +0.008546 +0.00849 +0.008534 +0.008612 +0.008687 +0.008749 +0.008588 +0.008508 +0.008573 +0.008637 +0.008723 +0.00878 +0.008653 +0.008557 +0.00861 +0.00869 +0.008766 +0.008855 +0.008684 +0.008596 +0.008682 +0.008715 +0.008806 +0.008873 +0.008716 +0.008649 +0.008705 +0.008782 +0.008865 +0.008906 +0.008776 +0.008684 +0.008724 +0.008809 +0.008892 +0.008954 +0.008831 +0.008753 +0.008793 +0.008867 +0.008965 +0.009009 +0.008842 +0.008767 +0.008832 +0.008912 +0.009021 +0.009053 +0.008881 +0.008816 +0.008868 +0.008972 +0.009033 +0.009081 +0.00894 +0.008869 +0.008913 +0.008991 +0.009091 +0.009148 +0.008978 +0.008916 +0.008953 +0.009036 +0.009126 +0.009189 +0.009029 +0.008961 +0.009019 +0.009098 +0.009189 +0.009247 +0.009068 +0.009022 +0.009056 +0.009122 +0.009206 +0.009267 +0.009118 +0.009053 +0.009112 +0.009193 +0.009284 +0.009341 +0.009151 +0.009087 +0.009132 +0.009204 +0.009309 +0.009386 +0.009226 +0.009131 +0.009197 +0.009267 +0.009368 +0.009424 +0.009265 +0.009179 +0.009229 +0.009318 +0.009438 +0.009489 +0.009295 +0.009213 +0.00928 +0.009375 +0.009455 +0.009504 +0.009351 +0.009283 +0.009339 +0.009412 +0.009518 +0.009559 +0.009394 +0.00932 +0.009385 +0.00946 +0.009543 +0.009642 +0.00944 +0.009366 +0.009432 +0.009509 +0.009593 +0.00965 +0.009496 +0.00945 +0.009474 +0.009567 +0.009631 +0.009685 +0.009526 +0.009448 +0.009514 +0.009605 +0.009694 +0.009758 +0.009602 +0.009526 +0.009578 +0.009653 +0.009744 +0.00979 +0.009625 +0.009556 +0.009613 +0.009704 +0.009798 +0.00986 +0.009688 +0.009609 +0.009655 +0.009779 +0.009832 +0.009884 +0.009738 +0.009657 +0.009715 +0.009792 +0.009888 +0.009964 +0.009781 +0.009691 +0.009757 +0.009837 +0.009936 +0.010013 +0.009823 +0.009758 +0.009829 +0.009913 +0.00998 +0.010046 +0.009883 +0.009797 +0.00985 +0.009938 +0.010077 +0.010125 +0.009932 +0.009848 +0.009895 +0.00999 +0.010075 +0.010149 +0.009991 +0.009894 +0.009946 +0.010064 +0.01015 +0.010211 +0.010034 +0.009958 +0.009988 +0.010088 +0.010196 +0.010254 +0.010094 +0.00999 +0.01005 +0.010136 +0.010247 +0.010339 +0.01012 +0.010034 +0.010095 +0.010201 +0.010318 +0.010353 +0.010168 +0.010099 +0.010152 +0.010241 +0.010354 +0.010399 +0.010248 +0.010166 +0.01021 +0.010332 +0.010393 +0.010441 +0.010269 +0.010196 +0.010245 +0.010343 +0.010452 +0.010543 +0.01038 +0.010212 +0.010277 +0.010365 +0.010477 +0.010503 +0.01031 +0.0102 +0.010199 +0.010249 +0.010309 +0.010344 +0.010115 +0.00998 +0.010007 +0.010039 +0.010119 +0.010122 +0.009889 +0.009731 +0.009747 +0.00979 +0.009844 +0.00986 +0.009661 +0.009521 +0.009521 +0.009581 +0.009594 +0.00961 +0.009374 +0.00925 +0.009271 +0.009318 +0.009355 +0.009382 +0.00917 +0.009032 +0.009051 +0.009107 +0.009145 +0.009169 +0.008975 +0.008863 +0.00888 +0.008932 +0.008994 +0.009 +0.008802 +0.008694 +0.008721 +0.008786 +0.00884 +0.008843 +0.00866 +0.008558 +0.0086 +0.008678 +0.008705 +0.008727 +0.008555 +0.008468 +0.008475 +0.008538 +0.008611 +0.00864 +0.008489 +0.008415 +0.008442 +0.008508 +0.008585 +0.008654 +0.008508 +0.008429 +0.008474 +0.008554 +0.008629 +0.008699 +0.008563 +0.008489 +0.008546 +0.008601 +0.008666 +0.008724 +0.008579 +0.008512 +0.008567 +0.008624 +0.008722 +0.008791 +0.008651 +0.008572 +0.00861 +0.008684 +0.008753 +0.008833 +0.008665 +0.0086 +0.008646 +0.008724 +0.0088 +0.008866 +0.008714 +0.00864 +0.008699 +0.008755 +0.008844 +0.008919 +0.008766 +0.008737 +0.008755 +0.00881 +0.008889 +0.008939 +0.008796 +0.008731 +0.008773 +0.008849 +0.008941 +0.009006 +0.008848 +0.008785 +0.008838 +0.008911 +0.00898 +0.009043 +0.008881 +0.008812 +0.008865 +0.008945 +0.009025 +0.009087 +0.008943 +0.008856 +0.008908 +0.008991 +0.009082 +0.009161 +0.00899 +0.008931 +0.008948 +0.009027 +0.009129 +0.00919 +0.009019 +0.008937 +0.009004 +0.009083 +0.00916 +0.009227 +0.00907 +0.008984 +0.009058 +0.009143 +0.009232 +0.009277 +0.009117 +0.009031 +0.009087 +0.009168 +0.009264 +0.009311 +0.009161 +0.009085 +0.009138 +0.009245 +0.009301 +0.009364 +0.009203 +0.009135 +0.009184 +0.009261 +0.009359 +0.009437 +0.009262 +0.009164 +0.009222 +0.009312 +0.009404 +0.009462 +0.009306 +0.009222 +0.009288 +0.009379 +0.009464 +0.009509 +0.009334 +0.009267 +0.009333 +0.009401 +0.009502 +0.009568 +0.009423 +0.009324 +0.009376 +0.009455 +0.009542 +0.009611 +0.009428 +0.009352 +0.009426 +0.009506 +0.009601 +0.009665 +0.009481 +0.009407 +0.009454 +0.009571 +0.00964 +0.009687 +0.009545 +0.009449 +0.009529 +0.009634 +0.009707 +0.009741 +0.009584 +0.009495 +0.009587 +0.009641 +0.009732 +0.009791 +0.009648 +0.009574 +0.00962 +0.009701 +0.009788 +0.009839 +0.009673 +0.009596 +0.009647 +0.009749 +0.009834 +0.009919 +0.009733 +0.009677 +0.0097 +0.009777 +0.009884 +0.009945 +0.009775 +0.00971 +0.009782 +0.009865 +0.009987 +0.009981 +0.009812 +0.009732 +0.009795 +0.009884 +0.009993 +0.010074 +0.009897 +0.009813 +0.009839 +0.009942 +0.010033 +0.010103 +0.009931 +0.009836 +0.009914 +0.010017 +0.010104 +0.010158 +0.009988 +0.009875 +0.009943 +0.010043 +0.010152 +0.010239 +0.010032 +0.009948 +0.009986 +0.010086 +0.010183 +0.010248 +0.01007 +0.010003 +0.010046 +0.01014 +0.010266 +0.010301 +0.010125 +0.010047 +0.010099 +0.010194 +0.010301 +0.010354 +0.010183 +0.010102 +0.010166 +0.010258 +0.010345 +0.010439 +0.010231 +0.010132 +0.010192 +0.01028 +0.01044 +0.010476 +0.010285 +0.010207 +0.010252 +0.010329 +0.010453 +0.010488 +0.010316 +0.01022 +0.010267 +0.010328 +0.010413 +0.010421 +0.010204 +0.010074 +0.010122 +0.010176 +0.010214 +0.010245 +0.010029 +0.009896 +0.009876 +0.009896 +0.00994 +0.009949 +0.009726 +0.009596 +0.009636 +0.009649 +0.009686 +0.009685 +0.009486 +0.009351 +0.009366 +0.009423 +0.009472 +0.009438 +0.009253 +0.009129 +0.009139 +0.009181 +0.009226 +0.00922 +0.009032 +0.008942 +0.00896 +0.009032 +0.009041 +0.009053 +0.008876 +0.008775 +0.008799 +0.008834 +0.008887 +0.008909 +0.008731 +0.008632 +0.008654 +0.008718 +0.008757 +0.008795 +0.008613 +0.008528 +0.008558 +0.008592 +0.008673 +0.008671 +0.00851 +0.008427 +0.008479 +0.008528 +0.008598 +0.008645 +0.008505 +0.008435 +0.008514 +0.008556 +0.008601 +0.008665 +0.008524 +0.008457 +0.008498 +0.008577 +0.008664 +0.008744 +0.00858 +0.008508 +0.008559 +0.008637 +0.0087 +0.008756 +0.008616 +0.008531 +0.008591 +0.008673 +0.008764 +0.008803 +0.008655 +0.008591 +0.008643 +0.008714 +0.008779 +0.00885 +0.008725 +0.008648 +0.008685 +0.008762 +0.008868 +0.008876 +0.00874 +0.00866 +0.008719 +0.008804 +0.008881 +0.008931 +0.008794 +0.008735 +0.008783 +0.008846 +0.008928 +0.00899 +0.008827 +0.00875 +0.008814 +0.008881 +0.008968 +0.009033 +0.008883 +0.008809 +0.008864 +0.008945 +0.009035 +0.009104 +0.0089 +0.008837 +0.00889 +0.008969 +0.009064 +0.009119 +0.008962 +0.008894 +0.008943 +0.009015 +0.009114 +0.009174 +0.00901 +0.008934 +0.009015 +0.00906 +0.009149 +0.009213 +0.009061 +0.008981 +0.009027 +0.009111 +0.009193 +0.009264 +0.0091 +0.009024 +0.00911 +0.009174 +0.009256 +0.009303 +0.009152 +0.009072 +0.009107 +0.009191 +0.009288 +0.009367 +0.009188 +0.009117 +0.009185 +0.009269 +0.009349 +0.009403 +0.009245 +0.00916 +0.009207 +0.009293 +0.009372 +0.00944 +0.009298 +0.009218 +0.00925 +0.00934 +0.009439 +0.009514 +0.009341 +0.009236 +0.009292 +0.009386 +0.009482 +0.009544 +0.009392 +0.009296 +0.009366 +0.009442 +0.009519 +0.009588 +0.009417 +0.009331 +0.009403 +0.009486 +0.009597 +0.009649 +0.009508 +0.009379 +0.009436 +0.009531 +0.009611 +0.009675 +0.00951 +0.009445 +0.009534 +0.009591 +0.009683 +0.009737 +0.009553 +0.009484 +0.00953 +0.009616 +0.009733 +0.009774 +0.009606 +0.009573 +0.009602 +0.009687 +0.009776 +0.009827 +0.009644 +0.009568 +0.009635 +0.009712 +0.009821 +0.009879 +0.009715 +0.009647 +0.009704 +0.009798 +0.0099 +0.009897 +0.009749 +0.009673 +0.009739 +0.00981 +0.009905 +0.00998 +0.009817 +0.009723 +0.009777 +0.009873 +0.009967 +0.010032 +0.009865 +0.009774 +0.009845 +0.009929 +0.010009 +0.010077 +0.009912 +0.009815 +0.009885 +0.009966 +0.010077 +0.010179 +0.009954 +0.009879 +0.009922 +0.01001 +0.010121 +0.010182 +0.010001 +0.009938 +0.009966 +0.010072 +0.010197 +0.010251 +0.010064 +0.009969 +0.01004 +0.010107 +0.010222 +0.010275 +0.010101 +0.010033 +0.010094 +0.010185 +0.010295 +0.01036 +0.01017 +0.010052 +0.010114 +0.010214 +0.010337 +0.010387 +0.010201 +0.010129 +0.010188 +0.010271 +0.010375 +0.010437 +0.010262 +0.010181 +0.010253 +0.01034 +0.010453 +0.010481 +0.010301 +0.010219 +0.010286 +0.010372 +0.010485 +0.010557 +0.010383 +0.010259 +0.010291 +0.010336 +0.010405 +0.010424 +0.010217 +0.010114 +0.010127 +0.010194 +0.010244 +0.010228 +0.009998 +0.009857 +0.009871 +0.009919 +0.00995 +0.009961 +0.009756 +0.009639 +0.009659 +0.009697 +0.009739 +0.009736 +0.009517 +0.009405 +0.009434 +0.00949 +0.009512 +0.009484 +0.00929 +0.009183 +0.009214 +0.009256 +0.009278 +0.009305 +0.009099 +0.008995 +0.009022 +0.009061 +0.009101 +0.009114 +0.008936 +0.008821 +0.008853 +0.008899 +0.008942 +0.008966 +0.008784 +0.0087 +0.008721 +0.008766 +0.008829 +0.008866 +0.008704 +0.0086 +0.008612 +0.008665 +0.008726 +0.008761 +0.008613 +0.008522 +0.008567 +0.008628 +0.008696 +0.008762 +0.008605 +0.008544 +0.008604 +0.008649 +0.008725 +0.00879 +0.008644 +0.008561 +0.008619 +0.00869 +0.008773 +0.008844 +0.008708 +0.008621 +0.008675 +0.008746 +0.008815 +0.008904 +0.008727 +0.008653 +0.008697 +0.008781 +0.008866 +0.008943 +0.008789 +0.008705 +0.008763 +0.008847 +0.008902 +0.008957 +0.008806 +0.008737 +0.008793 +0.008911 +0.008952 +0.009003 +0.008856 +0.00879 +0.008839 +0.00891 +0.008997 +0.009058 +0.00891 +0.008837 +0.008903 +0.00899 +0.009061 +0.009102 +0.008936 +0.008868 +0.00893 +0.009001 +0.009095 +0.009163 +0.009011 +0.008908 +0.008962 +0.009045 +0.009136 +0.009194 +0.009039 +0.008964 +0.009016 +0.009098 +0.009186 +0.00925 +0.0091 +0.009003 +0.00907 +0.009142 +0.009228 +0.009298 +0.009135 +0.009084 +0.009101 +0.009179 +0.009279 +0.00935 +0.009193 +0.009099 +0.009149 +0.009229 +0.009318 +0.009391 +0.00922 +0.009136 +0.009192 +0.009274 +0.009387 +0.009446 +0.009279 +0.009183 +0.009249 +0.009326 +0.009407 +0.009485 +0.009308 +0.00923 +0.009302 +0.009392 +0.009513 +0.009527 +0.009357 +0.009273 +0.009345 +0.009415 +0.009503 +0.009574 +0.009409 +0.009332 +0.009373 +0.009469 +0.009578 +0.009612 +0.00946 +0.009368 +0.009431 +0.009516 +0.009619 +0.009675 +0.009514 +0.009434 +0.009463 +0.009561 +0.009665 +0.009718 +0.009552 +0.009473 +0.009555 +0.009649 +0.009714 +0.009773 +0.00959 +0.009495 +0.009568 +0.009654 +0.009748 +0.009827 +0.009651 +0.009549 +0.009626 +0.009693 +0.009796 +0.009871 +0.009691 +0.009616 +0.009685 +0.009774 +0.009866 +0.009929 +0.009731 +0.009647 +0.009711 +0.00981 +0.009924 +0.00998 +0.009779 +0.00973 +0.009793 +0.009869 +0.009944 +0.009987 +0.009832 +0.009746 +0.009811 +0.0099 +0.010011 +0.01006 +0.009889 +0.009809 +0.009861 +0.009951 +0.010051 +0.010118 +0.009942 +0.009867 +0.009926 +0.010004 +0.010102 +0.010179 +0.010018 +0.009894 +0.009956 +0.010048 +0.010158 +0.010251 +0.010052 +0.009959 +0.010007 +0.010084 +0.010196 +0.010262 +0.010086 +0.010005 +0.01008 +0.010163 +0.010267 +0.010337 +0.010141 +0.01004 +0.010112 +0.010197 +0.010306 +0.010385 +0.010235 +0.010102 +0.010151 +0.010241 +0.010348 +0.010429 +0.01023 +0.010156 +0.010258 +0.010326 +0.010412 +0.01047 +0.010278 +0.010201 +0.010273 +0.010353 +0.010458 +0.010544 +0.010358 +0.010267 +0.01034 +0.01041 +0.010477 +0.010557 +0.010383 +0.010285 +0.010271 +0.010327 +0.010371 +0.010385 +0.01018 +0.010051 +0.010081 +0.010121 +0.010188 +0.01017 +0.009981 +0.009837 +0.00985 +0.009879 +0.009911 +0.009937 +0.009731 +0.009606 +0.009606 +0.009655 +0.009704 +0.009711 +0.009498 +0.009391 +0.009392 +0.009426 +0.009445 +0.00948 +0.009252 +0.009152 +0.00917 +0.009199 +0.009249 +0.009296 +0.009086 +0.008981 +0.009004 +0.009028 +0.009076 +0.009099 +0.008926 +0.008815 +0.00884 +0.008901 +0.008943 +0.008975 +0.008794 +0.008698 +0.008719 +0.008748 +0.00881 +0.008836 +0.008691 +0.008577 +0.00861 +0.008643 +0.008707 +0.008763 +0.008586 +0.008501 +0.008545 +0.008608 +0.008674 +0.008734 +0.008593 +0.008543 +0.008578 +0.008651 +0.008713 +0.008781 +0.008629 +0.008557 +0.008615 +0.008678 +0.00876 +0.008832 +0.008693 +0.008611 +0.008666 +0.008741 +0.008818 +0.008882 +0.008713 +0.008634 +0.008692 +0.008759 +0.008843 +0.008919 +0.008819 +0.008691 +0.008749 +0.008815 +0.008898 +0.008943 +0.008804 +0.008727 +0.008783 +0.008862 +0.008942 +0.009001 +0.00885 +0.008784 +0.00883 +0.008899 +0.008998 +0.009042 +0.008905 +0.008825 +0.008881 +0.008975 +0.009046 +0.009096 +0.008936 +0.008865 +0.008907 +0.008993 +0.009074 +0.009142 +0.009007 +0.008901 +0.008959 +0.009044 +0.009141 +0.009194 +0.009022 +0.008957 +0.008998 +0.009081 +0.009175 +0.009236 +0.009072 +0.009 +0.009075 +0.009147 +0.009229 +0.009283 +0.009123 +0.009061 +0.009108 +0.009156 +0.009251 +0.009316 +0.009164 +0.009075 +0.009143 +0.009238 +0.009326 +0.009382 +0.009224 +0.009126 +0.009178 +0.009259 +0.009363 +0.009418 +0.009258 +0.009184 +0.009253 +0.009325 +0.009413 +0.009482 +0.009311 +0.009204 +0.009279 +0.009366 +0.009486 +0.009518 +0.009348 +0.009289 +0.009313 +0.009403 +0.009488 +0.009555 +0.009395 +0.009311 +0.009372 +0.00946 +0.009559 +0.009625 +0.009448 +0.009376 +0.009416 +0.009501 +0.0096 +0.009653 +0.009491 +0.009415 +0.009475 +0.009573 +0.009667 +0.009727 +0.009562 +0.00946 +0.009502 +0.00959 +0.009685 +0.009755 +0.009585 +0.00949 +0.009577 +0.009677 +0.009753 +0.009796 +0.009635 +0.009545 +0.009602 +0.0097 +0.009783 +0.009847 +0.009713 +0.009614 +0.009676 +0.009762 +0.009844 +0.009883 +0.009732 +0.009647 +0.009738 +0.009796 +0.009891 +0.00994 +0.009774 +0.009704 +0.009758 +0.00986 +0.009934 +0.009991 +0.009827 +0.009769 +0.009815 +0.009904 +0.009979 +0.010048 +0.009881 +0.009791 +0.009856 +0.009931 +0.010046 +0.010139 +0.009933 +0.009858 +0.009922 +0.010016 +0.010094 +0.010128 +0.009963 +0.009895 +0.009942 +0.010039 +0.010166 +0.010229 +0.010054 +0.009948 +0.009984 +0.01008 +0.010183 +0.010259 +0.010073 +0.009993 +0.010054 +0.010141 +0.010247 +0.010322 +0.010126 +0.010043 +0.010113 +0.0102 +0.010312 +0.010384 +0.0102 +0.010099 +0.010144 +0.010228 +0.010341 +0.01041 +0.010225 +0.010157 +0.010231 +0.010338 +0.010389 +0.010454 +0.010268 +0.010187 +0.010262 +0.010339 +0.010458 +0.010551 +0.010339 +0.010251 +0.010326 +0.010392 +0.010492 +0.010569 +0.0104 +0.010334 +0.010371 +0.010405 +0.010501 +0.010558 +0.010334 +0.010223 +0.01025 +0.010295 +0.010369 +0.010395 +0.010163 +0.009999 +0.010028 +0.010051 +0.010112 +0.010118 +0.009879 +0.009761 +0.009777 +0.009812 +0.009878 +0.009863 +0.009633 +0.009511 +0.009533 +0.009594 +0.009598 +0.009628 +0.00938 +0.009276 +0.009295 +0.00932 +0.009367 +0.009377 +0.009158 +0.009046 +0.009063 +0.009114 +0.009156 +0.009172 +0.008981 +0.008863 +0.008877 +0.008928 +0.008983 +0.009009 +0.008818 +0.008724 +0.008735 +0.008791 +0.008855 +0.008896 +0.008706 +0.008606 +0.008613 +0.008661 +0.008722 +0.008779 +0.008583 +0.00848 +0.008531 +0.008596 +0.00866 +0.008698 +0.008536 +0.008447 +0.008486 +0.008557 +0.008636 +0.008713 +0.008564 +0.008478 +0.008525 +0.0086 +0.008686 +0.00874 +0.008601 +0.008519 +0.00857 +0.008646 +0.008733 +0.008806 +0.008664 +0.008566 +0.008614 +0.008675 +0.008764 +0.008829 +0.008682 +0.008622 +0.008656 +0.008726 +0.008815 +0.008885 +0.008742 +0.008669 +0.008712 +0.008762 +0.00886 +0.008916 +0.008761 +0.008692 +0.008746 +0.008813 +0.008913 +0.008979 +0.008813 +0.008744 +0.008796 +0.008878 +0.008964 +0.008995 +0.008848 +0.008773 +0.008836 +0.008918 +0.00899 +0.009046 +0.008894 +0.008851 +0.008865 +0.008946 +0.009042 +0.009091 +0.008945 +0.00887 +0.008934 +0.00901 +0.009094 +0.009155 +0.008983 +0.008911 +0.008961 +0.009048 +0.009135 +0.009189 +0.009042 +0.008987 +0.009049 +0.009116 +0.009173 +0.009214 +0.009064 +0.008998 +0.009044 +0.009132 +0.009219 +0.009296 +0.00912 +0.009054 +0.009109 +0.009172 +0.009271 +0.009334 +0.00917 +0.009095 +0.009142 +0.009242 +0.009333 +0.009378 +0.009227 +0.009131 +0.009189 +0.009273 +0.009377 +0.009448 +0.009265 +0.009178 +0.009256 +0.009342 +0.009426 +0.009456 +0.009294 +0.009225 +0.009282 +0.009356 +0.00945 +0.009528 +0.009347 +0.009281 +0.009343 +0.009421 +0.009502 +0.009567 +0.009401 +0.009324 +0.009373 +0.009472 +0.009566 +0.009609 +0.00945 +0.009381 +0.009456 +0.009505 +0.009597 +0.009647 +0.009494 +0.009448 +0.00947 +0.009557 +0.009662 +0.009685 +0.009535 +0.009467 +0.009517 +0.009614 +0.009697 +0.009777 +0.009603 +0.009514 +0.009574 +0.009653 +0.009734 +0.009807 +0.009629 +0.00955 +0.009624 +0.009736 +0.00982 +0.009837 +0.009676 +0.009597 +0.009658 +0.009753 +0.009843 +0.009927 +0.009731 +0.009665 +0.009721 +0.009797 +0.009889 +0.00995 +0.009793 +0.009703 +0.009761 +0.009843 +0.009957 +0.010023 +0.009842 +0.00976 +0.009801 +0.009888 +0.010006 +0.010097 +0.009883 +0.009797 +0.009843 +0.009937 +0.010044 +0.010097 +0.009929 +0.009847 +0.009917 +0.009998 +0.010104 +0.010176 +0.009998 +0.009891 +0.009957 +0.010041 +0.010145 +0.010222 +0.010026 +0.009946 +0.01002 +0.01011 +0.010209 +0.010281 +0.010103 +0.009987 +0.010074 +0.010133 +0.010235 +0.010308 +0.010137 +0.010046 +0.010125 +0.010201 +0.010291 +0.010361 +0.010186 +0.010088 +0.010167 +0.010264 +0.010337 +0.010417 +0.010229 +0.010142 +0.010225 +0.010285 +0.010404 +0.010481 +0.010333 +0.010231 +0.010251 +0.010328 +0.010446 +0.010546 +0.01032 +0.010238 +0.010304 +0.010385 +0.010486 +0.010564 +0.010333 +0.010195 +0.010231 +0.010291 +0.010327 +0.010337 +0.010121 +0.009983 +0.009997 +0.010052 +0.010106 +0.01011 +0.009862 +0.009717 +0.009738 +0.009783 +0.009789 +0.009804 +0.00959 +0.009497 +0.009488 +0.009498 +0.009538 +0.009568 +0.009334 +0.00921 +0.00923 +0.009252 +0.009302 +0.009332 +0.009115 +0.008995 +0.009009 +0.009052 +0.009088 +0.009114 +0.008929 +0.008813 +0.008833 +0.008895 +0.00894 +0.008971 +0.00878 +0.00865 +0.008677 +0.008722 +0.008776 +0.008815 +0.008626 +0.008556 +0.008568 +0.008615 +0.008666 +0.008702 +0.008532 +0.008428 +0.008463 +0.00852 +0.008594 +0.00863 +0.008487 +0.008395 +0.008447 +0.008509 +0.008583 +0.008655 +0.008509 +0.008427 +0.008487 +0.008551 +0.008647 +0.008713 +0.008545 +0.008476 +0.008508 +0.008589 +0.008677 +0.008728 +0.00858 +0.008504 +0.008563 +0.008637 +0.008724 +0.008796 +0.008645 +0.008565 +0.008616 +0.008683 +0.008765 +0.008825 +0.008664 +0.008631 +0.008641 +0.008715 +0.008803 +0.008867 +0.008706 +0.008646 +0.008696 +0.008778 +0.008871 +0.008918 +0.008757 +0.008679 +0.008744 +0.008823 +0.008882 +0.008948 +0.008804 +0.008732 +0.008778 +0.008855 +0.008943 +0.009009 +0.008854 +0.008789 +0.008837 +0.008903 +0.00898 +0.009041 +0.008888 +0.008815 +0.008868 +0.00895 +0.009024 +0.009112 +0.008954 +0.008878 +0.008945 +0.00899 +0.009065 +0.009119 +0.008991 +0.008905 +0.008946 +0.009034 +0.009129 +0.009174 +0.009026 +0.008945 +0.008997 +0.009085 +0.009165 +0.009229 +0.00907 +0.008994 +0.009066 +0.009146 +0.009226 +0.009273 +0.009117 +0.009035 +0.009087 +0.009174 +0.009259 +0.00934 +0.009184 +0.009091 +0.009151 +0.009225 +0.009315 +0.009387 +0.009191 +0.009118 +0.00918 +0.00925 +0.009356 +0.009432 +0.009244 +0.009172 +0.009233 +0.009326 +0.009401 +0.009465 +0.009307 +0.009219 +0.009282 +0.009374 +0.009455 +0.009517 +0.009342 +0.009267 +0.009348 +0.009417 +0.009494 +0.009543 +0.009398 +0.009328 +0.009377 +0.00945 +0.00955 +0.009611 +0.009431 +0.009358 +0.009423 +0.009498 +0.009602 +0.009656 +0.009478 +0.009402 +0.009479 +0.009541 +0.009632 +0.009688 +0.009548 +0.009454 +0.009546 +0.009616 +0.009721 +0.009775 +0.009557 +0.009484 +0.00954 +0.009643 +0.009728 +0.009798 +0.00965 +0.009569 +0.009626 +0.009697 +0.009778 +0.009834 +0.009671 +0.009594 +0.009648 +0.009739 +0.009843 +0.009897 +0.009732 +0.009648 +0.009699 +0.009781 +0.009897 +0.009958 +0.009812 +0.009708 +0.00976 +0.009846 +0.009917 +0.009994 +0.009807 +0.009741 +0.009799 +0.009883 +0.009978 +0.010073 +0.009899 +0.009796 +0.009833 +0.00993 +0.010025 +0.010096 +0.009923 +0.00983 +0.00992 +0.010008 +0.010098 +0.010162 +0.009982 +0.009896 +0.00995 +0.010026 +0.010113 +0.010202 +0.010048 +0.009915 +0.00999 +0.010085 +0.010179 +0.010248 +0.010075 +0.009987 +0.010051 +0.010169 +0.010261 +0.010283 +0.010116 +0.01003 +0.010095 +0.010187 +0.010287 +0.010354 +0.0102 +0.010105 +0.010191 +0.010245 +0.01032 +0.010379 +0.010213 +0.010135 +0.010204 +0.010295 +0.010402 +0.010458 +0.010285 +0.010199 +0.010241 +0.010328 +0.010444 +0.01051 +0.010314 +0.010231 +0.010278 +0.010356 +0.010456 +0.010474 +0.01027 +0.010131 +0.010193 +0.010211 +0.010249 +0.010278 +0.010035 +0.009901 +0.009926 +0.009951 +0.010006 +0.010041 +0.009762 +0.009649 +0.009675 +0.009723 +0.00976 +0.00977 +0.009529 +0.009409 +0.009438 +0.009462 +0.009512 +0.009524 +0.009327 +0.009196 +0.009208 +0.009248 +0.009322 +0.009282 +0.009098 +0.008953 +0.008982 +0.009039 +0.009095 +0.009094 +0.008908 +0.008799 +0.008826 +0.00887 +0.008945 +0.008955 +0.00877 +0.008667 +0.008709 +0.008758 +0.008815 +0.008832 +0.008642 +0.008544 +0.008577 +0.008642 +0.008689 +0.008727 +0.008551 +0.00847 +0.008528 +0.008592 +0.008628 +0.008661 +0.008499 +0.008438 +0.008479 +0.00854 +0.008628 +0.008689 +0.008556 +0.008464 +0.008512 +0.008594 +0.008666 +0.008739 +0.008586 +0.008511 +0.008561 +0.008632 +0.008721 +0.008794 +0.008638 +0.008555 +0.008605 +0.00867 +0.008759 +0.008826 +0.008678 +0.008615 +0.00866 +0.00872 +0.008815 +0.008892 +0.008717 +0.008646 +0.00871 +0.008745 +0.008838 +0.008896 +0.008738 +0.008679 +0.008732 +0.008819 +0.008906 +0.008963 +0.008816 +0.00873 +0.00877 +0.008848 +0.00893 +0.008993 +0.008844 +0.008781 +0.008822 +0.0089 +0.008989 +0.009051 +0.008914 +0.008804 +0.008862 +0.008925 +0.009032 +0.009093 +0.008952 +0.008866 +0.008935 +0.008981 +0.009061 +0.009122 +0.008973 +0.008896 +0.008948 +0.009025 +0.009133 +0.009194 +0.009038 +0.008959 +0.009005 +0.009064 +0.009162 +0.009222 +0.009063 +0.008997 +0.009048 +0.009135 +0.009254 +0.009253 +0.009097 +0.009028 +0.009084 +0.00917 +0.009249 +0.009319 +0.00917 +0.009095 +0.009135 +0.009203 +0.009302 +0.00936 +0.009204 +0.009135 +0.009177 +0.009259 +0.009364 +0.009428 +0.009271 +0.009178 +0.009236 +0.009289 +0.009395 +0.009461 +0.009311 +0.009232 +0.009266 +0.009364 +0.009458 +0.009515 +0.009353 +0.009262 +0.009307 +0.009398 +0.009484 +0.009543 +0.009393 +0.009315 +0.009366 +0.009473 +0.00956 +0.009609 +0.009438 +0.009368 +0.009389 +0.009483 +0.009592 +0.009643 +0.009496 +0.009422 +0.009462 +0.009577 +0.009636 +0.009687 +0.009522 +0.009436 +0.009531 +0.00959 +0.009684 +0.009766 +0.009587 +0.009491 +0.00956 +0.009637 +0.009728 +0.009798 +0.009627 +0.009537 +0.009603 +0.009698 +0.009801 +0.009863 +0.009684 +0.00959 +0.009649 +0.009736 +0.009835 +0.009926 +0.009713 +0.009644 +0.009714 +0.009793 +0.009901 +0.009944 +0.009777 +0.009686 +0.009719 +0.009829 +0.009934 +0.009989 +0.00982 +0.009739 +0.0098 +0.009889 +0.009977 +0.010052 +0.009875 +0.009785 +0.009862 +0.009944 +0.010037 +0.010094 +0.009945 +0.009841 +0.009883 +0.009965 +0.010062 +0.010154 +0.009987 +0.009899 +0.009964 +0.010066 +0.010114 +0.010188 +0.010011 +0.009925 +0.00999 +0.010095 +0.010176 +0.010246 +0.010077 +0.009982 +0.010046 +0.01014 +0.010236 +0.010301 +0.010134 +0.010082 +0.010115 +0.01018 +0.010267 +0.010339 +0.010173 +0.010074 +0.010134 +0.010243 +0.010334 +0.010425 +0.010259 +0.010135 +0.010179 +0.010291 +0.010381 +0.010451 +0.010276 +0.010174 +0.010264 +0.01037 +0.010454 +0.010526 +0.010319 +0.010249 +0.010324 +0.010379 +0.010467 +0.010558 +0.010338 +0.010232 +0.010285 +0.01035 +0.010403 +0.010413 +0.010204 +0.010081 +0.010103 +0.010164 +0.0102 +0.010195 +0.009969 +0.009824 +0.009831 +0.009873 +0.009912 +0.009926 +0.009734 +0.009581 +0.009625 +0.009641 +0.009658 +0.00967 +0.009453 +0.009345 +0.009382 +0.009382 +0.009432 +0.009445 +0.009228 +0.009113 +0.009139 +0.00916 +0.009206 +0.009231 +0.009034 +0.008921 +0.008958 +0.008998 +0.00903 +0.009049 +0.008865 +0.008747 +0.008777 +0.008822 +0.008872 +0.0089 +0.008739 +0.008604 +0.008631 +0.008683 +0.008732 +0.008775 +0.008603 +0.008501 +0.00856 +0.008595 +0.008643 +0.008696 +0.008542 +0.008452 +0.008489 +0.008555 +0.008633 +0.008677 +0.008539 +0.008478 +0.00852 +0.008586 +0.008683 +0.008747 +0.008585 +0.008512 +0.008571 +0.008629 +0.008728 +0.008809 +0.008606 +0.008546 +0.008594 +0.008669 +0.008761 +0.008808 +0.008666 +0.008593 +0.008651 +0.008722 +0.008798 +0.008869 +0.008713 +0.008654 +0.008701 +0.008773 +0.008835 +0.008895 +0.008755 +0.008681 +0.008739 +0.008799 +0.00889 +0.008948 +0.00881 +0.00874 +0.00879 +0.008888 +0.008932 +0.008982 +0.008857 +0.008757 +0.008811 +0.008887 +0.008967 +0.009048 +0.008905 +0.008816 +0.008872 +0.008948 +0.00904 +0.009082 +0.008922 +0.008859 +0.008892 +0.008979 +0.009086 +0.009138 +0.008978 +0.008905 +0.008955 +0.009026 +0.009121 +0.00918 +0.009027 +0.008969 +0.008988 +0.009065 +0.009154 +0.009215 +0.009086 +0.008979 +0.009031 +0.00912 +0.009217 +0.009268 +0.009126 +0.009052 +0.009071 +0.009161 +0.00926 +0.00932 +0.00916 +0.009072 +0.009131 +0.009209 +0.009311 +0.009383 +0.009211 +0.009135 +0.009172 +0.009264 +0.009367 +0.009413 +0.009242 +0.009161 +0.009215 +0.00932 +0.009401 +0.00948 +0.009302 +0.009226 +0.009255 +0.00934 +0.009444 +0.009483 +0.009345 +0.00927 +0.009306 +0.009406 +0.009487 +0.009563 +0.009391 +0.009301 +0.009375 +0.009435 +0.009548 +0.009624 +0.00945 +0.009379 +0.009407 +0.009492 +0.009585 +0.009641 +0.009488 +0.009384 +0.009462 +0.009555 +0.009668 +0.009689 +0.009531 +0.009437 +0.009501 +0.00959 +0.009689 +0.009743 +0.009575 +0.009505 +0.009539 +0.009636 +0.009737 +0.009801 +0.009631 +0.009557 +0.009615 +0.009722 +0.009806 +0.00986 +0.009646 +0.009579 +0.009646 +0.009726 +0.009828 +0.009891 +0.009722 +0.009648 +0.009727 +0.009796 +0.009888 +0.009931 +0.00977 +0.009689 +0.009744 +0.00984 +0.009923 +0.01001 +0.009844 +0.009744 +0.009818 +0.009902 +0.009989 +0.010062 +0.009853 +0.009763 +0.009874 +0.009935 +0.010011 +0.010087 +0.009914 +0.009841 +0.00989 +0.009974 +0.01009 +0.010154 +0.009992 +0.009905 +0.009943 +0.01002 +0.010137 +0.010193 +0.010016 +0.009945 +0.009983 +0.010104 +0.010213 +0.010278 +0.010103 +0.009976 +0.01003 +0.010119 +0.010232 +0.010322 +0.010104 +0.010026 +0.010105 +0.010197 +0.010304 +0.010358 +0.010156 +0.01008 +0.010143 +0.010217 +0.010332 +0.010421 +0.01022 +0.010137 +0.010203 +0.010293 +0.010399 +0.01046 +0.010312 +0.010183 +0.010241 +0.010351 +0.010435 +0.010499 +0.010305 +0.010223 +0.010279 +0.010327 +0.010405 +0.010442 +0.010218 +0.010085 +0.010121 +0.010146 +0.0102 +0.010203 +0.009976 +0.00983 +0.009857 +0.009917 +0.009958 +0.009949 +0.009728 +0.009636 +0.009582 +0.009625 +0.009673 +0.00968 +0.009479 +0.009341 +0.009366 +0.009418 +0.009452 +0.009455 +0.009244 +0.009111 +0.009139 +0.009183 +0.00923 +0.00923 +0.009028 +0.008917 +0.00893 +0.008979 +0.009022 +0.009041 +0.008847 +0.008738 +0.008773 +0.008839 +0.008896 +0.008884 +0.008694 +0.008615 +0.008615 +0.008681 +0.00872 +0.008749 +0.008573 +0.008489 +0.008512 +0.008572 +0.008624 +0.00866 +0.008488 +0.008392 +0.008438 +0.008496 +0.00856 +0.008602 +0.008454 +0.008374 +0.00843 +0.008483 +0.008582 +0.008618 +0.008489 +0.008412 +0.008452 +0.008547 +0.008615 +0.008663 +0.008528 +0.008455 +0.008519 +0.008584 +0.008641 +0.008702 +0.008567 +0.008482 +0.008534 +0.008614 +0.008697 +0.008766 +0.008618 +0.008542 +0.008592 +0.00868 +0.008732 +0.008798 +0.008637 +0.008573 +0.008623 +0.008714 +0.008794 +0.008857 +0.008691 +0.008619 +0.008692 +0.008744 +0.008817 +0.008881 +0.008728 +0.008661 +0.008722 +0.008807 +0.008891 +0.008938 +0.008772 +0.008695 +0.008757 +0.008832 +0.008914 +0.008973 +0.008837 +0.008749 +0.008802 +0.008874 +0.008963 +0.009025 +0.008866 +0.008798 +0.00885 +0.008924 +0.00901 +0.009078 +0.008957 +0.00884 +0.008881 +0.008956 +0.00904 +0.00911 +0.008959 +0.008874 +0.008938 +0.009024 +0.009128 +0.009165 +0.009009 +0.008925 +0.008964 +0.009051 +0.009146 +0.00919 +0.009043 +0.008978 +0.009043 +0.009122 +0.009207 +0.009265 +0.009106 +0.009012 +0.009056 +0.009143 +0.009265 +0.009291 +0.009167 +0.009053 +0.00911 +0.009198 +0.009268 +0.009335 +0.009186 +0.0091 +0.009154 +0.009249 +0.009339 +0.00941 +0.009246 +0.009159 +0.009207 +0.009276 +0.009368 +0.009437 +0.009278 +0.009193 +0.009265 +0.009357 +0.009446 +0.009507 +0.009336 +0.009266 +0.009294 +0.009353 +0.009463 +0.009543 +0.009369 +0.009275 +0.009344 +0.009444 +0.009516 +0.009584 +0.009419 +0.00933 +0.00939 +0.009465 +0.009582 +0.009644 +0.009471 +0.009383 +0.009443 +0.009536 +0.009616 +0.009688 +0.009503 +0.009426 +0.00949 +0.009617 +0.009686 +0.009723 +0.009548 +0.009467 +0.009541 +0.009615 +0.009702 +0.009772 +0.009613 +0.009534 +0.009591 +0.009683 +0.009767 +0.009812 +0.009673 +0.009566 +0.009619 +0.009721 +0.00982 +0.009866 +0.009707 +0.009623 +0.00968 +0.009765 +0.009866 +0.009953 +0.009746 +0.009668 +0.009733 +0.009825 +0.009906 +0.009971 +0.009793 +0.009744 +0.009763 +0.009852 +0.009961 +0.010038 +0.00986 +0.00978 +0.009834 +0.009901 +0.01001 +0.010069 +0.00989 +0.009813 +0.009885 +0.009963 +0.010068 +0.01013 +0.009975 +0.009899 +0.009905 +0.009998 +0.010105 +0.010182 +0.009996 +0.009914 +0.009977 +0.010071 +0.010165 +0.010234 +0.010047 +0.009956 +0.010028 +0.01011 +0.01022 +0.010305 +0.010116 +0.010008 +0.010071 +0.010172 +0.01027 +0.010332 +0.010149 +0.010096 +0.010149 +0.010223 +0.010344 +0.010353 +0.010192 +0.010103 +0.010165 +0.01027 +0.010359 +0.010446 +0.010285 +0.01017 +0.010223 +0.010311 +0.010368 +0.01042 +0.010234 +0.010085 +0.010144 +0.010195 +0.010261 +0.010266 +0.010033 +0.009911 +0.009945 +0.009999 +0.010011 +0.010015 +0.009843 +0.009682 +0.009677 +0.009723 +0.009762 +0.009748 +0.009535 +0.009408 +0.00942 +0.009474 +0.00949 +0.009503 +0.009278 +0.009156 +0.00917 +0.009204 +0.009237 +0.009257 +0.009059 +0.008944 +0.008956 +0.009011 +0.009049 +0.009082 +0.008862 +0.008755 +0.008774 +0.008824 +0.008884 +0.008919 +0.008728 +0.008615 +0.008658 +0.008694 +0.008748 +0.008767 +0.008589 +0.008493 +0.008522 +0.008583 +0.00864 +0.008671 +0.008506 +0.008416 +0.008451 +0.008509 +0.008587 +0.008619 +0.00846 +0.008376 +0.008438 +0.008505 +0.008611 +0.008631 +0.008486 +0.008424 +0.008476 +0.008554 +0.008624 +0.008684 +0.008524 +0.008484 +0.008504 +0.008582 +0.008667 +0.008726 +0.00858 +0.008497 +0.008559 +0.008625 +0.008713 +0.008776 +0.008631 +0.00855 +0.0086 +0.008675 +0.008755 +0.008842 +0.008662 +0.008598 +0.008643 +0.00874 +0.008827 +0.008839 +0.008698 +0.008624 +0.00869 +0.008755 +0.008841 +0.00891 +0.008767 +0.008684 +0.008725 +0.008802 +0.008887 +0.008947 +0.008798 +0.008733 +0.008772 +0.008849 +0.008948 +0.009008 +0.008845 +0.008761 +0.008816 +0.008893 +0.00897 +0.009042 +0.008886 +0.008835 +0.008862 +0.00895 +0.009042 +0.009087 +0.008935 +0.008856 +0.008896 +0.008973 +0.009068 +0.009123 +0.00897 +0.008908 +0.008959 +0.009014 +0.009112 +0.00918 +0.009021 +0.00894 +0.008997 +0.009078 +0.009162 +0.00923 +0.009089 +0.00899 +0.009034 +0.009116 +0.009214 +0.009294 +0.009105 +0.009022 +0.009077 +0.009165 +0.009303 +0.009309 +0.009161 +0.009083 +0.009124 +0.009196 +0.009288 +0.009363 +0.009198 +0.009121 +0.009192 +0.009264 +0.009346 +0.009409 +0.009253 +0.009161 +0.009207 +0.009298 +0.009401 +0.009463 +0.00932 +0.009236 +0.009318 +0.009342 +0.009432 +0.009499 +0.009333 +0.00926 +0.009302 +0.009413 +0.009502 +0.009554 +0.009396 +0.009315 +0.009371 +0.009432 +0.009531 +0.009609 +0.009426 +0.00935 +0.009413 +0.009494 +0.009582 +0.009648 +0.009484 +0.009411 +0.009448 +0.009551 +0.009672 +0.009702 +0.009537 +0.009445 +0.00949 +0.009585 +0.009669 +0.009742 +0.009573 +0.009504 +0.009559 +0.009636 +0.009757 +0.009801 +0.009633 +0.009548 +0.009595 +0.009677 +0.00978 +0.009839 +0.009659 +0.009594 +0.009665 +0.009751 +0.009838 +0.009902 +0.009753 +0.009628 +0.009713 +0.009763 +0.009866 +0.009958 +0.009753 +0.009678 +0.009744 +0.009822 +0.009924 +0.00999 +0.009815 +0.009735 +0.009812 +0.00991 +0.009998 +0.010023 +0.00987 +0.009773 +0.009835 +0.009933 +0.010019 +0.010086 +0.009945 +0.009872 +0.009928 +0.009983 +0.010062 +0.010139 +0.009962 +0.009868 +0.009929 +0.010023 +0.010125 +0.010209 +0.010033 +0.009951 +0.01 +0.01007 +0.01017 +0.010241 +0.01006 +0.009983 +0.010038 +0.010128 +0.010227 +0.010292 +0.010124 +0.010039 +0.010112 +0.010204 +0.010279 +0.010339 +0.010164 +0.010089 +0.010173 +0.010224 +0.010337 +0.010385 +0.010214 +0.010138 +0.010191 +0.010292 +0.010411 +0.010481 +0.01025 +0.01018 +0.010236 +0.010336 +0.010438 +0.01048 +0.010297 +0.010223 +0.010278 +0.010358 +0.010366 +0.010356 +0.010153 +0.010033 +0.010063 +0.01012 +0.01018 +0.010196 +0.009951 +0.009815 +0.009838 +0.009868 +0.009882 +0.009895 +0.009687 +0.009579 +0.009596 +0.009611 +0.009643 +0.009656 +0.009459 +0.009324 +0.009345 +0.009386 +0.009424 +0.009472 +0.009238 +0.009119 +0.009129 +0.009157 +0.009212 +0.009216 +0.009031 +0.008929 +0.008956 +0.008991 +0.009053 +0.009087 +0.00888 +0.008786 +0.008812 +0.008845 +0.008897 +0.008909 +0.008749 +0.008657 +0.00868 +0.008721 +0.008777 +0.008797 +0.008629 +0.008545 +0.008575 +0.008648 +0.008683 +0.008726 +0.008554 +0.008465 +0.008521 +0.008583 +0.008626 +0.00868 +0.008543 +0.008467 +0.008531 +0.008594 +0.008681 +0.00874 +0.008589 +0.00853 +0.008568 +0.008645 +0.008715 +0.008777 +0.008623 +0.008552 +0.008608 +0.008686 +0.008765 +0.00882 +0.008681 +0.008601 +0.00868 +0.008738 +0.008812 +0.008849 +0.008715 +0.008657 +0.008689 +0.008777 +0.008854 +0.008905 +0.008748 +0.008679 +0.008738 +0.008813 +0.008892 +0.00896 +0.008805 +0.008723 +0.008783 +0.008865 +0.008957 +0.008996 +0.008839 +0.00878 +0.008822 +0.008899 +0.008982 +0.009047 +0.008912 +0.00884 +0.008875 +0.008944 +0.009029 +0.009092 +0.008924 +0.008875 +0.008901 +0.008989 +0.009067 +0.009136 +0.008977 +0.008908 +0.008956 +0.009029 +0.009128 +0.00918 +0.009023 +0.008951 +0.009003 +0.009079 +0.009176 +0.009254 +0.009066 +0.008987 +0.009042 +0.009122 +0.009247 +0.009274 +0.009104 +0.009041 +0.00909 +0.009175 +0.009275 +0.009329 +0.009154 +0.009083 +0.009128 +0.009217 +0.009309 +0.009369 +0.009202 +0.009126 +0.009196 +0.00928 +0.009375 +0.00944 +0.009233 +0.009162 +0.009227 +0.009302 +0.009395 +0.009459 +0.009312 +0.00927 +0.009312 +0.009356 +0.00944 +0.00949 +0.009334 +0.00926 +0.009306 +0.009395 +0.009501 +0.009553 +0.009399 +0.009322 +0.009374 +0.00944 +0.009547 +0.009584 +0.009439 +0.009361 +0.009424 +0.009516 +0.009599 +0.009658 +0.009485 +0.009403 +0.009472 +0.009555 +0.009643 +0.00968 +0.009537 +0.009488 +0.009513 +0.009601 +0.009695 +0.009732 +0.009583 +0.009504 +0.009546 +0.009641 +0.009748 +0.009779 +0.009626 +0.009542 +0.009607 +0.009695 +0.009788 +0.009844 +0.009685 +0.009616 +0.009659 +0.00974 +0.009832 +0.009912 +0.009739 +0.009635 +0.009694 +0.00978 +0.009887 +0.009978 +0.009776 +0.009701 +0.009758 +0.009824 +0.009924 +0.009994 +0.009829 +0.009732 +0.009808 +0.009873 +0.009978 +0.01005 +0.009871 +0.009791 +0.009854 +0.009943 +0.010037 +0.010108 +0.009948 +0.009872 +0.009873 +0.009973 +0.010076 +0.010147 +0.009973 +0.009877 +0.00995 +0.010054 +0.010149 +0.010206 +0.010034 +0.009922 +0.009987 +0.010088 +0.010188 +0.010267 +0.010071 +0.009972 +0.010038 +0.010144 +0.010234 +0.010298 +0.010123 +0.010055 +0.010128 +0.010175 +0.010294 +0.010351 +0.010163 +0.010073 +0.010135 +0.010249 +0.010351 +0.010401 +0.010223 +0.010147 +0.010208 +0.0103 +0.010391 +0.010439 +0.010269 +0.010191 +0.010242 +0.01034 +0.010464 +0.010504 +0.010314 +0.010221 +0.010308 +0.010298 +0.01039 +0.01037 +0.010177 +0.010058 +0.010095 +0.010133 +0.010173 +0.010194 +0.009971 +0.009838 +0.009852 +0.009886 +0.009932 +0.009955 +0.009725 +0.009596 +0.009596 +0.009644 +0.009673 +0.009684 +0.009469 +0.009337 +0.009368 +0.009423 +0.00945 +0.00948 +0.009232 +0.009122 +0.00913 +0.009156 +0.009205 +0.009197 +0.009011 +0.008913 +0.008921 +0.008965 +0.009006 +0.009035 +0.008834 +0.008735 +0.00876 +0.008814 +0.008856 +0.008889 +0.008712 +0.008601 +0.008626 +0.008674 +0.008724 +0.008745 +0.008564 +0.00848 +0.008511 +0.008585 +0.008636 +0.008647 +0.00848 +0.008405 +0.008453 +0.008525 +0.00856 +0.008611 +0.008466 +0.008399 +0.008458 +0.008542 +0.008611 +0.008657 +0.008518 +0.00846 +0.008486 +0.008563 +0.008642 +0.008712 +0.008559 +0.008484 +0.008555 +0.008629 +0.008696 +0.008743 +0.008599 +0.008526 +0.008601 +0.008669 +0.008726 +0.008774 +0.008643 +0.008584 +0.008642 +0.008725 +0.008813 +0.008823 +0.008673 +0.008605 +0.008656 +0.008742 +0.008812 +0.00888 +0.008727 +0.008654 +0.008714 +0.008779 +0.008875 +0.008927 +0.008776 +0.008706 +0.008756 +0.008819 +0.008923 +0.008991 +0.008833 +0.008764 +0.008788 +0.008871 +0.008949 +0.009005 +0.008867 +0.00879 +0.00884 +0.008925 +0.009034 +0.009066 +0.008902 +0.00883 +0.00888 +0.008958 +0.009043 +0.009115 +0.008948 +0.008878 +0.008948 +0.009011 +0.009105 +0.009163 +0.009005 +0.008907 +0.008971 +0.009057 +0.009149 +0.009226 +0.009064 +0.00895 +0.009005 +0.009088 +0.009184 +0.009246 +0.009083 +0.009012 +0.009067 +0.009141 +0.009238 +0.009299 +0.00914 +0.009051 +0.009112 +0.009185 +0.009278 +0.009341 +0.009189 +0.009098 +0.009167 +0.009247 +0.009339 +0.0094 +0.009235 +0.009144 +0.009226 +0.009276 +0.009363 +0.009421 +0.009278 +0.009198 +0.009251 +0.009344 +0.009437 +0.009497 +0.009312 +0.009228 +0.009292 +0.009367 +0.009461 +0.009539 +0.009362 +0.009288 +0.00935 +0.009429 +0.009518 +0.009586 +0.009412 +0.00933 +0.009383 +0.009477 +0.009594 +0.009653 +0.009445 +0.009372 +0.009442 +0.009517 +0.009628 +0.009666 +0.009501 +0.009429 +0.009491 +0.009575 +0.009675 +0.009723 +0.009543 +0.009476 +0.009534 +0.009607 +0.009708 +0.009774 +0.009616 +0.00953 +0.009594 +0.009667 +0.009753 +0.00982 +0.009655 +0.009591 +0.009613 +0.009713 +0.009807 +0.009855 +0.009698 +0.009613 +0.009687 +0.009783 +0.009839 +0.0099 +0.009752 +0.009675 +0.009731 +0.009823 +0.009922 +0.00995 +0.009796 +0.009705 +0.009771 +0.009859 +0.009963 +0.010008 +0.009843 +0.009765 +0.00985 +0.009922 +0.009991 +0.010059 +0.009893 +0.009815 +0.009865 +0.009958 +0.010063 +0.010109 +0.009951 +0.009865 +0.009917 +0.01 +0.010117 +0.010174 +0.009998 +0.009932 +0.009984 +0.010056 +0.010158 +0.010222 +0.010039 +0.009954 +0.01002 +0.010142 +0.010234 +0.01028 +0.010119 +0.009981 +0.010064 +0.010147 +0.010254 +0.010331 +0.010136 +0.010065 +0.010152 +0.010219 +0.010324 +0.010389 +0.010177 +0.010102 +0.010173 +0.010255 +0.01038 +0.010442 +0.010246 +0.010146 +0.010224 +0.010327 +0.010453 +0.010473 +0.010285 +0.010215 +0.010325 +0.010377 +0.010448 +0.010523 +0.010331 +0.010237 +0.010302 +0.010372 +0.010463 +0.010517 +0.01027 +0.010139 +0.010158 +0.010194 +0.010254 +0.01023 +0.01 +0.009876 +0.009914 +0.009921 +0.009968 +0.009953 +0.009744 +0.009574 +0.009582 +0.009617 +0.009662 +0.0097 +0.009421 +0.009295 +0.009315 +0.009339 +0.009376 +0.009384 +0.009165 +0.009039 +0.009066 +0.009116 +0.009153 +0.009152 +0.008959 +0.008834 +0.008857 +0.008903 +0.00895 +0.008959 +0.008778 +0.008673 +0.008713 +0.008785 +0.008805 +0.00881 +0.008619 +0.008524 +0.008563 +0.008612 +0.008662 +0.008706 +0.008514 +0.008422 +0.008459 +0.008515 +0.00857 +0.008593 +0.008423 +0.008329 +0.008369 +0.008433 +0.008501 +0.008552 +0.008402 +0.008307 +0.008359 +0.008443 +0.008516 +0.008571 +0.008438 +0.008364 +0.008423 +0.008483 +0.00855 +0.008625 +0.008466 +0.0084 +0.008445 +0.008511 +0.0086 +0.008661 +0.008515 +0.008451 +0.008477 +0.008566 +0.008653 +0.008709 +0.008565 +0.008493 +0.008533 +0.008598 +0.008684 +0.008757 +0.008608 +0.008518 +0.008579 +0.008637 +0.008724 +0.008788 +0.008648 +0.008585 +0.008631 +0.008691 +0.00878 +0.008824 +0.008677 +0.008612 +0.008662 +0.00873 +0.008818 +0.008874 +0.008726 +0.008655 +0.00872 +0.008774 +0.008855 +0.008924 +0.008777 +0.008695 +0.008753 +0.008842 +0.008903 +0.008964 +0.008813 +0.008741 +0.008791 +0.008861 +0.00896 +0.009025 +0.008889 +0.008793 +0.008833 +0.008914 +0.009014 +0.009046 +0.008893 +0.008819 +0.008878 +0.008956 +0.009045 +0.009099 +0.008947 +0.008869 +0.00893 +0.009001 +0.009092 +0.009145 +0.008998 +0.008929 +0.008971 +0.009048 +0.009149 +0.009204 +0.009034 +0.008958 +0.009019 +0.009106 +0.009207 +0.00923 +0.009073 +0.009001 +0.009053 +0.009147 +0.009259 +0.00928 +0.009129 +0.009042 +0.009106 +0.009188 +0.009279 +0.009337 +0.009175 +0.009102 +0.009156 +0.009237 +0.009333 +0.00939 +0.009211 +0.009134 +0.009202 +0.009282 +0.009368 +0.009434 +0.00929 +0.009218 +0.009242 +0.009319 +0.009412 +0.009454 +0.009315 +0.009228 +0.009295 +0.009402 +0.009468 +0.009508 +0.009353 +0.009275 +0.009327 +0.009414 +0.009516 +0.009573 +0.00941 +0.009342 +0.00941 +0.009474 +0.009564 +0.009607 +0.009454 +0.009367 +0.009431 +0.009542 +0.009638 +0.009655 +0.009505 +0.009426 +0.009488 +0.009568 +0.00964 +0.009706 +0.009548 +0.009464 +0.009524 +0.009621 +0.009696 +0.009767 +0.009602 +0.009512 +0.009575 +0.00966 +0.009771 +0.00982 +0.009647 +0.009576 +0.009634 +0.009699 +0.009807 +0.009889 +0.009703 +0.009603 +0.00968 +0.009737 +0.009844 +0.009932 +0.009755 +0.009674 +0.009719 +0.009812 +0.009891 +0.009962 +0.009787 +0.0097 +0.009778 +0.009861 +0.009943 +0.010026 +0.009839 +0.009753 +0.009829 +0.009904 +0.010003 +0.010073 +0.009914 +0.009851 +0.009865 +0.00993 +0.010032 +0.01011 +0.009959 +0.009849 +0.009901 +0.01001 +0.010098 +0.010183 +0.010008 +0.009897 +0.009958 +0.010059 +0.010147 +0.010213 +0.010053 +0.009946 +0.010024 +0.010127 +0.010218 +0.010268 +0.01009 +0.010011 +0.010093 +0.010141 +0.010233 +0.010304 +0.010139 +0.010048 +0.01013 +0.010217 +0.0103 +0.010358 +0.010195 +0.010106 +0.010167 +0.01028 +0.010355 +0.01039 +0.010213 +0.010127 +0.010161 +0.010224 +0.010294 +0.010301 +0.010116 +0.009997 +0.010023 +0.010043 +0.010098 +0.010124 +0.009861 +0.009728 +0.009749 +0.009797 +0.009835 +0.009869 +0.009613 +0.009491 +0.009483 +0.009512 +0.009562 +0.009561 +0.00936 +0.009261 +0.009249 +0.009281 +0.009315 +0.009328 +0.009124 +0.009005 +0.00902 +0.009055 +0.009084 +0.009109 +0.008916 +0.008845 +0.008824 +0.008851 +0.008887 +0.008915 +0.00875 +0.008647 +0.008667 +0.008715 +0.008769 +0.008807 +0.008623 +0.008525 +0.008555 +0.008579 +0.008633 +0.008674 +0.008501 +0.008422 +0.008447 +0.008506 +0.008554 +0.008599 +0.008451 +0.008378 +0.008418 +0.008472 +0.008535 +0.008595 +0.008477 +0.008392 +0.00845 +0.00849 +0.008573 +0.008645 +0.00851 +0.008432 +0.008478 +0.008556 +0.008625 +0.008675 +0.008535 +0.00847 +0.008508 +0.008588 +0.008666 +0.008727 +0.008598 +0.008516 +0.008579 +0.008634 +0.008722 +0.008761 +0.008625 +0.008564 +0.008601 +0.008677 +0.00875 +0.008839 +0.008675 +0.008588 +0.008635 +0.008726 +0.008793 +0.008862 +0.008701 +0.008639 +0.008694 +0.008769 +0.00886 +0.008916 +0.008754 +0.008674 +0.008728 +0.008811 +0.008886 +0.00896 +0.008796 +0.008734 +0.008786 +0.00886 +0.008947 +0.009002 +0.008839 +0.008762 +0.008821 +0.008908 +0.008999 +0.009029 +0.008878 +0.008801 +0.008862 +0.008959 +0.009038 +0.009091 +0.008933 +0.008846 +0.008902 +0.008977 +0.00908 +0.009132 +0.008973 +0.008913 +0.008962 +0.009047 +0.009127 +0.009188 +0.009022 +0.008932 +0.008997 +0.009065 +0.009159 +0.009225 +0.009098 +0.009007 +0.009047 +0.00911 +0.009193 +0.009262 +0.009102 +0.00903 +0.009089 +0.009172 +0.009283 +0.009316 +0.009169 +0.009087 +0.009126 +0.009206 +0.009301 +0.00937 +0.009218 +0.009121 +0.009177 +0.009258 +0.009367 +0.009427 +0.009261 +0.009158 +0.009219 +0.009307 +0.009417 +0.009494 +0.009277 +0.0092 +0.009264 +0.009357 +0.009463 +0.009512 +0.00935 +0.009257 +0.009304 +0.009397 +0.009492 +0.009546 +0.009389 +0.009311 +0.009388 +0.009466 +0.00955 +0.009614 +0.009447 +0.009337 +0.009402 +0.009481 +0.009581 +0.009657 +0.009501 +0.009428 +0.009458 +0.009537 +0.009635 +0.009683 +0.009527 +0.009442 +0.009501 +0.009594 +0.009685 +0.009761 +0.009569 +0.009525 +0.009547 +0.009638 +0.009735 +0.009797 +0.009624 +0.009552 +0.009603 +0.0097 +0.009809 +0.009842 +0.009662 +0.00959 +0.009649 +0.009776 +0.009827 +0.009876 +0.009731 +0.009654 +0.009731 +0.009785 +0.009875 +0.009937 +0.009758 +0.009684 +0.00975 +0.00983 +0.009926 +0.009997 +0.009832 +0.009747 +0.009802 +0.009895 +0.009975 +0.010049 +0.009879 +0.009796 +0.009866 +0.009928 +0.010051 +0.010111 +0.009907 +0.00983 +0.009888 +0.00999 +0.010074 +0.010187 +0.009988 +0.009888 +0.00993 +0.010032 +0.010128 +0.010193 +0.010024 +0.009942 +0.010011 +0.010102 +0.010203 +0.010248 +0.010076 +0.009974 +0.010037 +0.010133 +0.01024 +0.010351 +0.010117 +0.010025 +0.01009 +0.010178 +0.010291 +0.010337 +0.010172 +0.010098 +0.010171 +0.010247 +0.01036 +0.01038 +0.01021 +0.010143 +0.010197 +0.010286 +0.010397 +0.010467 +0.010281 +0.010194 +0.010234 +0.010315 +0.010395 +0.010442 +0.010239 +0.01011 +0.010096 +0.010172 +0.010232 +0.010226 +0.009995 +0.009856 +0.009891 +0.009926 +0.00998 +0.009955 +0.009752 +0.009624 +0.009618 +0.009659 +0.00968 +0.009668 +0.009443 +0.00932 +0.009326 +0.009363 +0.009398 +0.009415 +0.009193 +0.009068 +0.00909 +0.009129 +0.009181 +0.00919 +0.008957 +0.008845 +0.008868 +0.008924 +0.008945 +0.00896 +0.008777 +0.008674 +0.008694 +0.008748 +0.008804 +0.008824 +0.00865 +0.00856 +0.008589 +0.008638 +0.008681 +0.008697 +0.008528 +0.008432 +0.00846 +0.00853 +0.008576 +0.008609 +0.008461 +0.008371 +0.008417 +0.008485 +0.008522 +0.008564 +0.008404 +0.008325 +0.008381 +0.008448 +0.00853 +0.008606 +0.008464 +0.008368 +0.008419 +0.008496 +0.008579 +0.008651 +0.008483 +0.008414 +0.008461 +0.008538 +0.008621 +0.008691 +0.00854 +0.008466 +0.008502 +0.008581 +0.008663 +0.008724 +0.008582 +0.00851 +0.008572 +0.008624 +0.008708 +0.008785 +0.00863 +0.00855 +0.00863 +0.008648 +0.008735 +0.008808 +0.008651 +0.00858 +0.008641 +0.008712 +0.008812 +0.008866 +0.008716 +0.008632 +0.008684 +0.008744 +0.008839 +0.00889 +0.008744 +0.008678 +0.008731 +0.008795 +0.008883 +0.008949 +0.00881 +0.008733 +0.008758 +0.008838 +0.008911 +0.008989 +0.008849 +0.008764 +0.008825 +0.008898 +0.008963 +0.009033 +0.008881 +0.00881 +0.008863 +0.00893 +0.009015 +0.009087 +0.008934 +0.008869 +0.008915 +0.00898 +0.009047 +0.009122 +0.008962 +0.008889 +0.008936 +0.009031 +0.009129 +0.009199 +0.009002 +0.008928 +0.008984 +0.009072 +0.009154 +0.009214 +0.009063 +0.008991 +0.009047 +0.009128 +0.009187 +0.009264 +0.009107 +0.009031 +0.009088 +0.009164 +0.009242 +0.009315 +0.00917 +0.009089 +0.009131 +0.009213 +0.009272 +0.009353 +0.009202 +0.009118 +0.009203 +0.009253 +0.009351 +0.009396 +0.009247 +0.009175 +0.009215 +0.009298 +0.009385 +0.00943 +0.009292 +0.009205 +0.009277 +0.00935 +0.009442 +0.009506 +0.009325 +0.009249 +0.009307 +0.009396 +0.009482 +0.009541 +0.009391 +0.009303 +0.009374 +0.009461 +0.009559 +0.009602 +0.009416 +0.00933 +0.009396 +0.009512 +0.009571 +0.009626 +0.009486 +0.009418 +0.009462 +0.00954 +0.009631 +0.009677 +0.009519 +0.009435 +0.009496 +0.009582 +0.009671 +0.009749 +0.009568 +0.009492 +0.009552 +0.009632 +0.009723 +0.009792 +0.009628 +0.009573 +0.009602 +0.009688 +0.009757 +0.009828 +0.009669 +0.009576 +0.009632 +0.00973 +0.009826 +0.0099 +0.009741 +0.009648 +0.009703 +0.009766 +0.009877 +0.009936 +0.009755 +0.009681 +0.009728 +0.009845 +0.009935 +0.009993 +0.009819 +0.009748 +0.009807 +0.009886 +0.009952 +0.010021 +0.009867 +0.009786 +0.009827 +0.009918 +0.010022 +0.01009 +0.00991 +0.009821 +0.009891 +0.009974 +0.010102 +0.010155 +0.009954 +0.009874 +0.009959 +0.010013 +0.010118 +0.010193 +0.010004 +0.009939 +0.010012 +0.010113 +0.010204 +0.010246 +0.010034 +0.009955 +0.010032 +0.010111 +0.010221 +0.010299 +0.010117 +0.010028 +0.010104 +0.010198 +0.010262 +0.010338 +0.010158 +0.010071 +0.010138 +0.010234 +0.010327 +0.0104 +0.010215 +0.010128 +0.010189 +0.010287 +0.010427 +0.010424 +0.010244 +0.010186 +0.010181 +0.010259 +0.010324 +0.010349 +0.010132 +0.009991 +0.01002 +0.010074 +0.01012 +0.010143 +0.009914 +0.009769 +0.009787 +0.009804 +0.00984 +0.009855 +0.009634 +0.009521 +0.009526 +0.009587 +0.009604 +0.009609 +0.009406 +0.009291 +0.009301 +0.009311 +0.009343 +0.009351 +0.009154 +0.009061 +0.009055 +0.009083 +0.009132 +0.009139 +0.00894 +0.008841 +0.008865 +0.008914 +0.008936 +0.008972 +0.008779 +0.008679 +0.008691 +0.008749 +0.008786 +0.008809 +0.008641 +0.008543 +0.008572 +0.008622 +0.008692 +0.008716 +0.008556 +0.008439 +0.008477 +0.008511 +0.00857 +0.008616 +0.008443 +0.008367 +0.00841 +0.008478 +0.008534 +0.008581 +0.008445 +0.008374 +0.008428 +0.008499 +0.008587 +0.008611 +0.008472 +0.0084 +0.008457 +0.008521 +0.008603 +0.008671 +0.008536 +0.008454 +0.008509 +0.008583 +0.008663 +0.008725 +0.008552 +0.008483 +0.008527 +0.008612 +0.008701 +0.008765 +0.008596 +0.008548 +0.008581 +0.008655 +0.008734 +0.008794 +0.008648 +0.008577 +0.008625 +0.008707 +0.00879 +0.008858 +0.008679 +0.008618 +0.008661 +0.008738 +0.008825 +0.008887 +0.008728 +0.008661 +0.008732 +0.00881 +0.00891 +0.008919 +0.008779 +0.008687 +0.008754 +0.008832 +0.008913 +0.008968 +0.008821 +0.008745 +0.00882 +0.008882 +0.008976 +0.009022 +0.00887 +0.008781 +0.008842 +0.008923 +0.008997 +0.009068 +0.008914 +0.00883 +0.008888 +0.008967 +0.009046 +0.009117 +0.008965 +0.008886 +0.008965 +0.009006 +0.009096 +0.009154 +0.009 +0.00893 +0.008979 +0.009051 +0.009141 +0.009204 +0.009044 +0.008966 +0.009054 +0.009098 +0.009189 +0.009262 +0.009102 +0.009008 +0.009061 +0.00915 +0.009237 +0.009296 +0.009156 +0.00905 +0.009117 +0.009197 +0.009304 +0.009381 +0.009195 +0.009095 +0.009138 +0.009235 +0.00934 +0.009384 +0.009224 +0.009145 +0.009225 +0.009292 +0.009379 +0.009452 +0.009277 +0.009181 +0.009241 +0.009336 +0.009419 +0.009484 +0.009345 +0.009235 +0.009293 +0.009379 +0.009475 +0.009533 +0.009367 +0.009286 +0.009377 +0.009441 +0.009533 +0.009588 +0.009413 +0.009326 +0.009395 +0.009463 +0.00956 +0.009628 +0.009457 +0.009381 +0.009461 +0.009541 +0.009628 +0.009696 +0.009514 +0.009416 +0.009482 +0.009563 +0.009661 +0.009731 +0.009559 +0.009481 +0.009535 +0.009627 +0.009744 +0.009785 +0.009603 +0.009517 +0.009577 +0.009685 +0.009783 +0.009816 +0.009657 +0.009589 +0.009622 +0.009707 +0.009816 +0.009865 +0.009707 +0.009646 +0.009691 +0.00978 +0.009872 +0.009903 +0.009749 +0.009672 +0.009726 +0.009814 +0.009907 +0.010001 +0.009849 +0.009733 +0.00979 +0.009857 +0.009941 +0.010017 +0.00985 +0.009758 +0.009842 +0.009901 +0.010031 +0.010071 +0.009892 +0.009808 +0.009879 +0.009954 +0.010064 +0.010138 +0.00997 +0.009881 +0.009913 +0.010015 +0.010107 +0.010175 +0.010009 +0.00994 +0.009982 +0.010083 +0.0102 +0.010216 +0.010054 +0.009951 +0.010005 +0.010115 +0.010202 +0.010273 +0.010111 +0.010014 +0.010089 +0.010186 +0.010273 +0.010329 +0.01014 +0.010055 +0.010119 +0.010217 +0.010319 +0.010376 +0.010209 +0.010125 +0.010213 +0.010263 +0.010358 +0.010423 +0.010259 +0.010171 +0.010206 +0.010302 +0.010409 +0.010418 +0.010218 +0.010087 +0.010103 +0.01016 +0.010231 +0.010229 +0.010016 +0.009902 +0.009914 +0.009934 +0.009985 +0.009951 +0.009738 +0.009613 +0.009626 +0.009682 +0.009681 +0.009686 +0.009485 +0.009353 +0.009362 +0.009391 +0.009448 +0.009424 +0.009207 +0.009086 +0.009112 +0.009135 +0.009176 +0.009187 +0.008991 +0.008867 +0.008882 +0.008928 +0.008964 +0.008993 +0.008802 +0.008698 +0.008717 +0.008768 +0.00882 +0.008836 +0.008681 +0.008548 +0.008573 +0.008617 +0.008673 +0.008701 +0.008531 +0.00844 +0.00847 +0.008527 +0.008593 +0.008596 +0.008428 +0.008341 +0.008372 +0.00843 +0.008489 +0.008553 +0.008395 +0.008307 +0.008368 +0.008431 +0.008506 +0.008564 +0.00842 +0.008339 +0.008387 +0.008458 +0.008539 +0.008616 +0.008472 +0.008388 +0.008456 +0.008508 +0.008586 +0.00865 +0.008489 +0.008416 +0.00847 +0.008546 +0.008619 +0.008684 +0.00856 +0.00847 +0.008515 +0.008586 +0.008677 +0.00874 +0.008587 +0.00851 +0.008556 +0.008637 +0.00871 +0.008798 +0.008632 +0.008556 +0.008596 +0.008677 +0.008777 +0.008829 +0.008659 +0.008597 +0.008634 +0.008739 +0.008814 +0.008859 +0.008717 +0.008646 +0.008694 +0.008752 +0.008839 +0.008919 +0.00875 +0.008685 +0.008732 +0.008801 +0.008877 +0.008946 +0.008809 +0.008724 +0.008777 +0.008843 +0.008947 +0.009006 +0.008847 +0.008783 +0.008836 +0.008918 +0.008969 +0.009039 +0.008882 +0.008809 +0.008864 +0.008938 +0.00904 +0.009082 +0.00895 +0.008858 +0.00891 +0.008991 +0.009053 +0.009123 +0.008975 +0.0089 +0.008954 +0.009025 +0.009134 +0.00919 +0.009032 +0.008949 +0.009005 +0.009066 +0.009147 +0.009229 +0.009073 +0.009012 +0.009046 +0.009133 +0.009192 +0.009262 +0.009113 +0.00902 +0.009079 +0.009167 +0.009249 +0.009312 +0.00916 +0.009102 +0.009146 +0.009221 +0.009301 +0.009351 +0.009192 +0.009139 +0.009171 +0.009244 +0.009349 +0.009431 +0.009253 +0.009182 +0.009252 +0.009318 +0.009416 +0.00946 +0.00927 +0.009196 +0.009257 +0.009346 +0.009441 +0.009517 +0.009358 +0.009274 +0.009329 +0.009388 +0.009491 +0.009534 +0.009384 +0.009307 +0.009355 +0.009446 +0.009541 +0.009606 +0.009442 +0.009362 +0.009418 +0.009484 +0.009586 +0.009658 +0.009517 +0.00943 +0.009461 +0.009537 +0.009639 +0.00969 +0.00953 +0.009444 +0.0095 +0.009586 +0.009689 +0.009763 +0.009602 +0.009505 +0.009553 +0.009633 +0.009717 +0.009778 +0.009631 +0.009543 +0.009598 +0.009691 +0.009791 +0.009849 +0.009686 +0.009599 +0.00967 +0.009753 +0.009827 +0.009879 +0.009718 +0.009664 +0.009697 +0.009783 +0.009879 +0.009953 +0.009773 +0.009683 +0.009756 +0.009821 +0.009938 +0.010022 +0.009824 +0.00973 +0.009805 +0.009878 +0.009978 +0.010053 +0.009861 +0.009793 +0.009863 +0.009959 +0.010084 +0.010086 +0.009898 +0.009822 +0.009884 +0.009987 +0.010063 +0.010171 +0.009964 +0.009877 +0.009941 +0.010043 +0.010137 +0.010182 +0.01003 +0.009922 +0.009998 +0.010107 +0.010192 +0.010236 +0.010076 +0.009977 +0.010048 +0.010138 +0.010242 +0.010333 +0.010107 +0.010019 +0.01011 +0.010193 +0.010274 +0.010343 +0.010169 +0.010082 +0.010145 +0.010237 +0.010345 +0.010406 +0.010215 +0.010121 +0.010154 +0.010216 +0.010311 +0.010297 +0.010075 +0.009954 +0.010011 +0.010039 +0.010082 +0.010093 +0.009892 +0.00978 +0.00973 +0.009767 +0.009823 +0.009819 +0.009616 +0.00949 +0.0095 +0.009521 +0.009566 +0.009572 +0.009348 +0.009229 +0.00925 +0.009278 +0.009327 +0.009342 +0.009134 +0.009012 +0.009018 +0.009061 +0.009088 +0.0091 +0.008911 +0.008796 +0.008822 +0.008866 +0.008914 +0.008943 +0.008771 +0.008631 +0.008659 +0.008704 +0.008759 +0.008788 +0.008602 +0.008507 +0.008556 +0.008588 +0.008641 +0.008672 +0.008501 +0.008404 +0.008444 +0.008492 +0.008558 +0.008582 +0.008421 +0.008342 +0.008406 +0.008459 +0.008504 +0.008557 +0.008408 +0.008337 +0.008394 +0.008471 +0.008544 +0.008629 +0.008448 +0.008384 +0.008446 +0.008502 +0.008591 +0.008629 +0.008496 +0.008422 +0.008486 +0.008542 +0.008627 +0.008682 +0.008545 +0.008467 +0.008514 +0.008607 +0.008684 +0.008727 +0.00858 +0.00852 +0.008555 +0.008635 +0.008714 +0.008785 +0.008646 +0.008544 +0.008615 +0.008682 +0.008798 +0.008822 +0.008654 +0.008583 +0.00864 +0.008719 +0.0088 +0.008859 +0.008708 +0.008663 +0.008696 +0.00877 +0.008855 +0.008914 +0.008747 +0.008679 +0.008765 +0.008795 +0.008887 +0.008954 +0.008807 +0.008718 +0.008767 +0.008855 +0.008928 +0.008996 +0.008848 +0.008766 +0.008846 +0.008915 +0.008985 +0.009041 +0.008891 +0.008802 +0.008862 +0.008931 +0.009024 +0.009099 +0.008926 +0.00885 +0.008913 +0.009001 +0.009073 +0.009132 +0.00899 +0.008893 +0.008972 +0.00902 +0.009113 +0.009173 +0.009018 +0.008952 +0.008993 +0.009072 +0.009167 +0.009231 +0.009101 +0.008985 +0.00904 +0.009118 +0.009203 +0.009283 +0.00912 +0.009024 +0.009085 +0.009176 +0.009268 +0.009309 +0.009164 +0.009064 +0.009133 +0.00922 +0.009319 +0.00938 +0.009214 +0.009126 +0.009177 +0.00925 +0.009352 +0.009405 +0.009247 +0.009167 +0.009246 +0.00935 +0.009415 +0.009479 +0.009282 +0.009194 +0.009262 +0.009353 +0.009436 +0.009506 +0.009362 +0.009252 +0.009317 +0.009413 +0.009484 +0.009544 +0.009394 +0.009303 +0.009372 +0.009446 +0.009565 +0.009619 +0.009431 +0.009358 +0.009406 +0.009494 +0.009596 +0.009659 +0.009511 +0.0094 +0.009466 +0.009549 +0.009664 +0.009702 +0.009506 +0.00944 +0.009519 +0.009587 +0.009678 +0.009748 +0.009572 +0.009503 +0.00955 +0.009641 +0.009726 +0.009793 +0.00964 +0.009541 +0.009605 +0.009713 +0.009796 +0.009837 +0.009677 +0.009601 +0.009681 +0.009728 +0.009824 +0.009882 +0.009721 +0.009643 +0.009706 +0.009801 +0.009889 +0.009932 +0.00977 +0.009697 +0.009753 +0.009825 +0.009929 +0.01001 +0.009826 +0.009752 +0.009807 +0.009875 +0.009973 +0.010043 +0.009873 +0.009785 +0.009857 +0.009956 +0.010039 +0.010104 +0.009919 +0.009839 +0.009897 +0.009968 +0.010075 +0.010156 +0.00996 +0.009882 +0.009957 +0.010053 +0.010145 +0.010211 +0.010038 +0.009916 +0.010019 +0.010075 +0.010172 +0.010245 +0.010082 +0.00997 +0.010037 +0.010131 +0.010276 +0.010329 +0.010092 +0.01002 +0.010089 +0.010208 +0.010295 +0.01034 +0.010177 +0.010072 +0.010142 +0.010241 +0.010328 +0.010399 +0.010247 +0.01014 +0.01021 +0.010308 +0.010389 +0.010438 +0.010273 +0.010164 +0.010231 +0.01032 +0.010426 +0.010461 +0.010217 +0.010115 +0.010115 +0.010157 +0.010219 +0.010237 +0.010033 +0.009893 +0.009901 +0.009926 +0.009975 +0.009946 +0.009728 +0.009607 +0.009613 +0.009656 +0.009694 +0.009709 +0.00949 +0.009354 +0.00937 +0.009395 +0.00944 +0.009461 +0.00925 +0.00915 +0.009139 +0.009175 +0.009194 +0.009212 +0.009025 +0.008892 +0.008917 +0.008957 +0.009003 +0.009023 +0.008846 +0.008741 +0.008748 +0.00882 +0.008836 +0.008861 +0.008672 +0.008572 +0.008614 +0.008652 +0.008707 +0.008752 +0.008581 +0.008475 +0.008496 +0.00855 +0.008607 +0.00864 +0.00847 +0.008365 +0.008394 +0.008465 +0.008545 +0.00858 +0.008422 +0.008348 +0.008395 +0.008466 +0.008547 +0.008603 +0.008468 +0.008388 +0.008438 +0.008515 +0.008605 +0.008673 +0.008484 +0.008423 +0.008474 +0.008545 +0.008629 +0.008696 +0.008536 +0.008464 +0.008533 +0.008612 +0.008697 +0.008765 +0.008583 +0.008503 +0.008552 +0.008627 +0.008721 +0.00878 +0.008634 +0.008567 +0.008625 +0.008661 +0.00875 +0.008815 +0.008667 +0.008591 +0.008638 +0.00873 +0.0088 +0.008861 +0.008728 +0.008656 +0.008691 +0.008759 +0.008855 +0.008904 +0.008758 +0.008684 +0.008742 +0.00883 +0.008934 +0.008965 +0.008801 +0.008727 +0.008779 +0.008842 +0.008927 +0.008998 +0.008843 +0.008765 +0.008819 +0.008896 +0.008985 +0.00904 +0.008892 +0.008821 +0.008864 +0.008934 +0.009029 +0.009099 +0.008934 +0.00887 +0.008931 +0.008985 +0.00907 +0.009132 +0.008983 +0.008922 +0.008964 +0.009025 +0.009107 +0.009193 +0.009024 +0.008954 +0.009017 +0.009078 +0.009164 +0.009216 +0.009063 +0.008992 +0.009046 +0.00911 +0.00921 +0.009284 +0.009138 +0.009037 +0.009104 +0.009167 +0.009246 +0.009311 +0.00917 +0.009076 +0.009129 +0.009222 +0.009333 +0.009404 +0.009211 +0.009131 +0.009181 +0.009245 +0.009343 +0.009402 +0.009254 +0.00917 +0.009243 +0.009305 +0.009403 +0.009465 +0.009301 +0.009213 +0.009292 +0.00933 +0.009444 +0.009512 +0.009356 +0.009273 +0.009332 +0.009407 +0.009486 +0.009562 +0.009393 +0.009325 +0.009377 +0.009434 +0.009545 +0.009607 +0.009451 +0.009371 +0.009414 +0.009497 +0.009588 +0.009642 +0.009482 +0.009409 +0.009468 +0.00954 +0.009633 +0.009697 +0.009551 +0.009458 +0.009504 +0.009592 +0.009686 +0.009759 +0.009597 +0.009499 +0.009559 +0.009654 +0.009771 +0.009787 +0.009614 +0.009539 +0.009601 +0.009701 +0.009799 +0.009866 +0.009693 +0.009596 +0.009643 +0.00974 +0.009823 +0.009896 +0.009725 +0.00966 +0.009715 +0.009798 +0.009905 +0.009946 +0.009756 +0.009692 +0.009744 +0.009834 +0.009943 +0.010032 +0.009828 +0.009753 +0.009788 +0.009872 +0.009983 +0.010032 +0.009885 +0.009794 +0.009863 +0.009956 +0.010049 +0.010088 +0.009916 +0.009833 +0.009896 +0.009983 +0.010075 +0.010156 +0.009995 +0.009904 +0.00997 +0.010032 +0.010125 +0.010193 +0.010046 +0.009951 +0.009991 +0.010078 +0.010179 +0.010257 +0.010067 +0.009993 +0.010048 +0.010132 +0.010245 +0.010311 +0.010146 +0.010075 +0.010083 +0.010179 +0.010283 +0.010351 +0.01017 +0.010076 +0.010153 +0.010263 +0.01037 +0.010416 +0.010232 +0.010163 +0.010194 +0.010265 +0.010359 +0.010421 +0.010235 +0.010124 +0.010161 +0.010216 +0.010283 +0.010283 +0.010075 +0.009921 +0.00995 +0.00998 +0.010075 +0.010032 +0.009805 +0.009688 +0.009703 +0.009731 +0.009766 +0.009788 +0.009578 +0.00946 +0.009454 +0.00949 +0.009521 +0.009535 +0.009301 +0.009176 +0.00919 +0.009227 +0.009263 +0.00929 +0.009076 +0.008978 +0.008976 +0.009011 +0.009035 +0.00906 +0.008871 +0.008763 +0.008787 +0.008814 +0.008856 +0.0089 +0.008728 +0.008619 +0.008639 +0.008687 +0.008716 +0.008744 +0.008575 +0.008488 +0.008511 +0.008569 +0.008592 +0.008617 +0.008466 +0.008377 +0.008418 +0.008451 +0.008508 +0.008555 +0.008388 +0.0083 +0.008335 +0.008395 +0.008467 +0.008527 +0.008384 +0.008313 +0.008353 +0.008428 +0.008503 +0.00856 +0.008422 +0.008344 +0.008405 +0.008479 +0.008555 +0.008621 +0.008477 +0.008398 +0.008461 +0.008534 +0.008582 +0.008644 +0.008496 +0.008433 +0.008487 +0.008554 +0.008632 +0.008706 +0.00856 +0.008512 +0.008514 +0.008597 +0.008665 +0.008726 +0.008588 +0.008518 +0.008565 +0.008633 +0.008738 +0.00879 +0.008644 +0.008567 +0.008625 +0.008673 +0.008758 +0.008827 +0.008679 +0.008629 +0.008659 +0.008726 +0.008793 +0.008868 +0.008725 +0.008653 +0.0087 +0.008758 +0.008856 +0.008917 +0.008762 +0.008693 +0.008759 +0.008817 +0.008891 +0.008963 +0.008807 +0.008731 +0.008776 +0.008863 +0.008935 +0.009008 +0.008879 +0.008797 +0.008836 +0.008911 +0.008985 +0.009064 +0.008908 +0.008811 +0.008858 +0.008935 +0.00903 +0.009119 +0.008944 +0.008871 +0.008926 +0.009005 +0.009073 +0.009132 +0.008988 +0.008901 +0.008957 +0.009071 +0.009115 +0.009178 +0.009023 +0.008953 +0.009002 +0.009082 +0.009177 +0.009231 +0.009079 +0.009011 +0.009068 +0.009172 +0.009219 +0.009267 +0.009107 +0.009033 +0.009105 +0.009178 +0.009272 +0.009332 +0.009178 +0.009096 +0.009149 +0.009232 +0.009305 +0.009355 +0.009202 +0.009131 +0.009187 +0.009259 +0.009366 +0.009408 +0.009255 +0.009192 +0.009233 +0.009312 +0.009399 +0.009474 +0.009337 +0.009231 +0.00929 +0.009368 +0.009448 +0.009518 +0.009352 +0.009265 +0.009317 +0.009419 +0.009491 +0.009552 +0.009409 +0.009325 +0.009384 +0.009467 +0.009548 +0.009603 +0.009442 +0.00936 +0.009413 +0.009485 +0.009619 +0.009663 +0.009498 +0.009427 +0.00949 +0.009572 +0.009629 +0.009687 +0.009531 +0.009459 +0.009525 +0.009587 +0.009682 +0.009757 +0.009579 +0.009504 +0.009597 +0.009629 +0.009738 +0.009805 +0.009633 +0.009565 +0.009621 +0.009692 +0.009785 +0.009858 +0.009685 +0.009595 +0.009646 +0.009751 +0.009864 +0.009941 +0.009736 +0.009643 +0.0097 +0.009796 +0.009887 +0.009944 +0.009768 +0.009696 +0.009754 +0.009854 +0.009961 +0.010015 +0.009842 +0.009739 +0.009787 +0.009881 +0.00999 +0.010043 +0.009889 +0.009787 +0.009858 +0.009937 +0.010036 +0.010104 +0.009956 +0.009837 +0.009894 +0.010003 +0.010101 +0.010173 +0.009973 +0.009874 +0.00995 +0.010028 +0.010133 +0.010202 +0.01003 +0.009957 +0.010016 +0.010096 +0.010188 +0.010246 +0.010064 +0.009982 +0.010041 +0.010138 +0.01024 +0.01032 +0.010136 +0.010077 +0.010096 +0.010173 +0.010284 +0.010368 +0.010178 +0.010105 +0.010186 +0.010218 +0.010309 +0.010376 +0.010195 +0.010086 +0.010118 +0.010184 +0.010244 +0.010269 +0.010056 +0.009912 +0.009922 +0.009969 +0.010015 +0.010011 +0.009803 +0.009672 +0.0097 +0.009715 +0.009754 +0.009746 +0.009549 +0.009399 +0.009412 +0.009439 +0.009484 +0.009492 +0.009299 +0.009179 +0.009179 +0.009212 +0.00925 +0.00926 +0.009053 +0.008937 +0.008963 +0.008988 +0.009028 +0.009063 +0.008871 +0.008752 +0.008773 +0.008825 +0.008877 +0.008913 +0.008709 +0.008608 +0.008646 +0.00867 +0.008728 +0.008758 +0.008592 +0.008476 +0.008513 +0.008547 +0.008602 +0.008643 +0.008476 +0.008375 +0.008414 +0.008469 +0.008517 +0.008576 +0.008416 +0.008336 +0.008358 +0.008424 +0.008496 +0.008554 +0.008415 +0.008344 +0.008391 +0.008465 +0.008576 +0.008605 +0.00846 +0.00838 +0.008433 +0.008508 +0.008579 +0.008628 +0.008487 +0.008427 +0.008472 +0.008538 +0.008623 +0.008686 +0.008527 +0.008463 +0.008525 +0.008589 +0.008664 +0.008731 +0.008585 +0.008521 +0.008575 +0.008635 +0.008709 +0.008759 +0.008622 +0.008544 +0.008597 +0.008697 +0.008761 +0.008815 +0.008663 +0.008602 +0.00866 +0.008723 +0.008798 +0.00886 +0.008707 +0.008627 +0.008683 +0.008756 +0.008845 +0.008912 +0.008762 +0.008693 +0.008736 +0.008812 +0.008891 +0.008939 +0.008782 +0.008717 +0.008775 +0.008841 +0.008944 +0.008986 +0.008844 +0.00878 +0.008833 +0.00888 +0.008973 +0.009025 +0.008885 +0.00882 +0.00886 +0.008947 +0.009037 +0.009107 +0.00891 +0.008845 +0.008901 +0.00897 +0.009063 +0.009123 +0.008978 +0.008909 +0.00896 +0.009049 +0.009115 +0.009172 +0.00901 +0.008933 +0.008987 +0.009065 +0.009152 +0.009243 +0.009078 +0.008998 +0.00903 +0.009111 +0.0092 +0.009267 +0.009099 +0.009027 +0.009084 +0.009158 +0.009268 +0.009325 +0.009148 +0.009074 +0.009126 +0.009218 +0.009294 +0.009357 +0.009211 +0.009124 +0.009181 +0.009266 +0.009357 +0.009401 +0.009239 +0.009167 +0.009243 +0.00933 +0.009381 +0.009449 +0.009292 +0.009214 +0.009274 +0.009345 +0.009446 +0.009514 +0.009321 +0.00925 +0.00932 +0.009393 +0.009488 +0.009547 +0.00938 +0.009298 +0.009354 +0.009451 +0.00953 +0.009597 +0.00944 +0.009365 +0.009411 +0.009495 +0.009604 +0.009668 +0.009474 +0.009384 +0.009446 +0.009522 +0.009644 +0.009706 +0.009516 +0.009456 +0.00951 +0.009596 +0.009685 +0.009729 +0.009566 +0.009484 +0.009556 +0.009625 +0.009718 +0.009811 +0.009632 +0.009549 +0.009611 +0.00969 +0.009768 +0.009829 +0.009675 +0.009616 +0.009652 +0.00974 +0.009806 +0.009881 +0.009724 +0.009626 +0.009691 +0.009807 +0.009864 +0.009928 +0.00978 +0.009701 +0.009752 +0.009817 +0.009922 +0.009984 +0.00982 +0.009732 +0.009777 +0.009873 +0.010004 +0.010054 +0.009876 +0.009807 +0.009857 +0.009967 +0.01 +0.010074 +0.009894 +0.009841 +0.009892 +0.009966 +0.01007 +0.010136 +0.009971 +0.009876 +0.009942 +0.010012 +0.010132 +0.010222 +0.010027 +0.009938 +0.009991 +0.010074 +0.010173 +0.010246 +0.010057 +0.009971 +0.01007 +0.010169 +0.010267 +0.010284 +0.010134 +0.009998 +0.010078 +0.010176 +0.010263 +0.010352 +0.010162 +0.010069 +0.010143 +0.010232 +0.01033 +0.010396 +0.010215 +0.010118 +0.010189 +0.010282 +0.010363 +0.010377 +0.010173 +0.01005 +0.010089 +0.01012 +0.010186 +0.010232 +0.010011 +0.009842 +0.009871 +0.009931 +0.009945 +0.009954 +0.009731 +0.009597 +0.009617 +0.009652 +0.009699 +0.009713 +0.009486 +0.009351 +0.009374 +0.009399 +0.009445 +0.009456 +0.009245 +0.009115 +0.009131 +0.00919 +0.00922 +0.009214 +0.009027 +0.008904 +0.008943 +0.008957 +0.008992 +0.009015 +0.008822 +0.00873 +0.008751 +0.008785 +0.008831 +0.008842 +0.008664 +0.008553 +0.008583 +0.008631 +0.008683 +0.008704 +0.008557 +0.008432 +0.008466 +0.008517 +0.008578 +0.008593 +0.008414 +0.008332 +0.008362 +0.008417 +0.008484 +0.008541 +0.008378 +0.008303 +0.00832 +0.008387 +0.00847 +0.008529 +0.008383 +0.008313 +0.008353 +0.008442 +0.008527 +0.008573 +0.008428 +0.008362 +0.008415 +0.008474 +0.008545 +0.008609 +0.008467 +0.00839 +0.008448 +0.008517 +0.008591 +0.008656 +0.008511 +0.008454 +0.00849 +0.008556 +0.008634 +0.008704 +0.008582 +0.0085 +0.008543 +0.008603 +0.008668 +0.008731 +0.008597 +0.008514 +0.00857 +0.008642 +0.008728 +0.008798 +0.008636 +0.008579 +0.008621 +0.008696 +0.008776 +0.008827 +0.008683 +0.008603 +0.008662 +0.008725 +0.008809 +0.008878 +0.008745 +0.008657 +0.008709 +0.008782 +0.008867 +0.00894 +0.008759 +0.008696 +0.008741 +0.008808 +0.008905 +0.008971 +0.008801 +0.008732 +0.008792 +0.008858 +0.008949 +0.00901 +0.008861 +0.008779 +0.008827 +0.008928 +0.009002 +0.009062 +0.008892 +0.008823 +0.008877 +0.008954 +0.009046 +0.009102 +0.008933 +0.008872 +0.008937 +0.009029 +0.009107 +0.009141 +0.008974 +0.008907 +0.00898 +0.009033 +0.009117 +0.009182 +0.00903 +0.008951 +0.009019 +0.009099 +0.009182 +0.009237 +0.009073 +0.009006 +0.009048 +0.009129 +0.009225 +0.009278 +0.009126 +0.009043 +0.0091 +0.009191 +0.009264 +0.009336 +0.009173 +0.009122 +0.009139 +0.009214 +0.009302 +0.009366 +0.009221 +0.009149 +0.009183 +0.009274 +0.009359 +0.009425 +0.009257 +0.009191 +0.009233 +0.009309 +0.009431 +0.00948 +0.009295 +0.009232 +0.00928 +0.009373 +0.009455 +0.009521 +0.009351 +0.009268 +0.00935 +0.009434 +0.009541 +0.009557 +0.009374 +0.009316 +0.009371 +0.009459 +0.009544 +0.009607 +0.009473 +0.009348 +0.009414 +0.00951 +0.009588 +0.00966 +0.009495 +0.009419 +0.009472 +0.009551 +0.009673 +0.009711 +0.009532 +0.00946 +0.009518 +0.00959 +0.009703 +0.00976 +0.009623 +0.009526 +0.009552 +0.009646 +0.009748 +0.009797 +0.009626 +0.009552 +0.009614 +0.009691 +0.00979 +0.009859 +0.009682 +0.009616 +0.009676 +0.009758 +0.00983 +0.009894 +0.009738 +0.009647 +0.009704 +0.009798 +0.009896 +0.009966 +0.009796 +0.009724 +0.009787 +0.009839 +0.009912 +0.009992 +0.009823 +0.009747 +0.009814 +0.009879 +0.009984 +0.010046 +0.009879 +0.009793 +0.009869 +0.009936 +0.010044 +0.010109 +0.009943 +0.00987 +0.009889 +0.009985 +0.010094 +0.010159 +0.00997 +0.009888 +0.009963 +0.010094 +0.010147 +0.010198 +0.010032 +0.009924 +0.010001 +0.010072 +0.010187 +0.010263 +0.010076 +0.009986 +0.010055 +0.010139 +0.010244 +0.010317 +0.010121 +0.01004 +0.010116 +0.0102 +0.010316 +0.010357 +0.010175 +0.01009 +0.010162 +0.010257 +0.010331 +0.010372 +0.010201 +0.010095 +0.010136 +0.010168 +0.010231 +0.010224 +0.010025 +0.009905 +0.009907 +0.009945 +0.010017 +0.010006 +0.00979 +0.009644 +0.009662 +0.009668 +0.009717 +0.009728 +0.009512 +0.0094 +0.009399 +0.009424 +0.009476 +0.009503 +0.009294 +0.009145 +0.009148 +0.009183 +0.009222 +0.00926 +0.009066 +0.008942 +0.008959 +0.008983 +0.009017 +0.009041 +0.008861 +0.008754 +0.00876 +0.008807 +0.008865 +0.008898 +0.008715 +0.008616 +0.008631 +0.008654 +0.008717 +0.00874 +0.008576 +0.008478 +0.008511 +0.008558 +0.008622 +0.008625 +0.008467 +0.008368 +0.008396 +0.008447 +0.008502 +0.008547 +0.008393 +0.008314 +0.008346 +0.008412 +0.008481 +0.008523 +0.008377 +0.008309 +0.008359 +0.008425 +0.00851 +0.008556 +0.008426 +0.00836 +0.008411 +0.008476 +0.008562 +0.008588 +0.008454 +0.008391 +0.008442 +0.008534 +0.008598 +0.008638 +0.008497 +0.008446 +0.008489 +0.008566 +0.008629 +0.008686 +0.008544 +0.008475 +0.00854 +0.008588 +0.00868 +0.008724 +0.008587 +0.008515 +0.008571 +0.008652 +0.008726 +0.00879 +0.008641 +0.008567 +0.008601 +0.008681 +0.008767 +0.008813 +0.00867 +0.008613 +0.008682 +0.008729 +0.008802 +0.00886 +0.008713 +0.008642 +0.008718 +0.008761 +0.008849 +0.008917 +0.008769 +0.008699 +0.008742 +0.008808 +0.008887 +0.00895 +0.008811 +0.008727 +0.008782 +0.008853 +0.008946 +0.00903 +0.00886 +0.008788 +0.008836 +0.008893 +0.008982 +0.009045 +0.008902 +0.008834 +0.008859 +0.008957 +0.00902 +0.009088 +0.008942 +0.008857 +0.00891 +0.009005 +0.00907 +0.00914 +0.008981 +0.008917 +0.008962 +0.009039 +0.009129 +0.009179 +0.009032 +0.008951 +0.009006 +0.009082 +0.009163 +0.009244 +0.009085 +0.009006 +0.009054 +0.009135 +0.009234 +0.009281 +0.009106 +0.009034 +0.009088 +0.009173 +0.009266 +0.009328 +0.009172 +0.009098 +0.009155 +0.009212 +0.009303 +0.009356 +0.009204 +0.009136 +0.009199 +0.00927 +0.00935 +0.009418 +0.009253 +0.009166 +0.009228 +0.009309 +0.009398 +0.009466 +0.009321 +0.009266 +0.009304 +0.009357 +0.009445 +0.009494 +0.009333 +0.009256 +0.009318 +0.0094 +0.009502 +0.009551 +0.009409 +0.009364 +0.009382 +0.009436 +0.009541 +0.009594 +0.009433 +0.009355 +0.009417 +0.009495 +0.009612 +0.009681 +0.0095 +0.009423 +0.009467 +0.009545 +0.009663 +0.009683 +0.009529 +0.00944 +0.009526 +0.009596 +0.009692 +0.009743 +0.00959 +0.009494 +0.009547 +0.009646 +0.009737 +0.009807 +0.009652 +0.009563 +0.009599 +0.009688 +0.009787 +0.009846 +0.009675 +0.009602 +0.009645 +0.009756 +0.009865 +0.009924 +0.00976 +0.009647 +0.009687 +0.009773 +0.009874 +0.009939 +0.009799 +0.00967 +0.009766 +0.009852 +0.009942 +0.010012 +0.009823 +0.009733 +0.009796 +0.009881 +0.009976 +0.010053 +0.009869 +0.00979 +0.009856 +0.009937 +0.010034 +0.010102 +0.009927 +0.009879 +0.009893 +0.009999 +0.010097 +0.010132 +0.009974 +0.009881 +0.009943 +0.01007 +0.01012 +0.010191 +0.010032 +0.009963 +0.010007 +0.010104 +0.010178 +0.010233 +0.010078 +0.009976 +0.010039 +0.010136 +0.010252 +0.010318 +0.010135 +0.010055 +0.010142 +0.010159 +0.010281 +0.010336 +0.010155 +0.010076 +0.010117 +0.010204 +0.010282 +0.010305 +0.010117 +0.00995 +0.00998 +0.010035 +0.010104 +0.010118 +0.009882 +0.009761 +0.009793 +0.00983 +0.009857 +0.009845 +0.00963 +0.009507 +0.009521 +0.009594 +0.009584 +0.009606 +0.009408 +0.009252 +0.009275 +0.009303 +0.009334 +0.009355 +0.00914 +0.009024 +0.009042 +0.009103 +0.009121 +0.009137 +0.008949 +0.008829 +0.008844 +0.008894 +0.008938 +0.008964 +0.008769 +0.008668 +0.008699 +0.008748 +0.00878 +0.008811 +0.008644 +0.008539 +0.008549 +0.008607 +0.00865 +0.008682 +0.008527 +0.008431 +0.008453 +0.008505 +0.008571 +0.008576 +0.008418 +0.008333 +0.008365 +0.008436 +0.008505 +0.008554 +0.008407 +0.008323 +0.008373 +0.008457 +0.008524 +0.008572 +0.00843 +0.008356 +0.008403 +0.008474 +0.008564 +0.008631 +0.008493 +0.008398 +0.008444 +0.00852 +0.008598 +0.008662 +0.008512 +0.008433 +0.008505 +0.008572 +0.008652 +0.008713 +0.008563 +0.008484 +0.008552 +0.008602 +0.008686 +0.00874 +0.008597 +0.008532 +0.008583 +0.008657 +0.008739 +0.008799 +0.00865 +0.008563 +0.008613 +0.008694 +0.00878 +0.008859 +0.008684 +0.00861 +0.008657 +0.008733 +0.008821 +0.008878 +0.008728 +0.008654 +0.008706 +0.008788 +0.008877 +0.008925 +0.008779 +0.008699 +0.008745 +0.008829 +0.008904 +0.008964 +0.008823 +0.008748 +0.008792 +0.008868 +0.008961 +0.009025 +0.008873 +0.008793 +0.008851 +0.008931 +0.009005 +0.009052 +0.008907 +0.008824 +0.008879 +0.008957 +0.009056 +0.00911 +0.008961 +0.008872 +0.00893 +0.008988 +0.009114 +0.009145 +0.008985 +0.008925 +0.00898 +0.009038 +0.009137 +0.009195 +0.009038 +0.008958 +0.009024 +0.009081 +0.009181 +0.009252 +0.009104 +0.009054 +0.009074 +0.009135 +0.009217 +0.009282 +0.009141 +0.009047 +0.009093 +0.009173 +0.009278 +0.009333 +0.009194 +0.009122 +0.009171 +0.009231 +0.009313 +0.009373 +0.009218 +0.009135 +0.009197 +0.009271 +0.009375 +0.009455 +0.009278 +0.009206 +0.009264 +0.00934 +0.009429 +0.009465 +0.009304 +0.009223 +0.009287 +0.00938 +0.009452 +0.009547 +0.00936 +0.00929 +0.009346 +0.009407 +0.009519 +0.009563 +0.009418 +0.009344 +0.009393 +0.009458 +0.009559 +0.009627 +0.009454 +0.009376 +0.009446 +0.009505 +0.009625 +0.009689 +0.009534 +0.009447 +0.009477 +0.009551 +0.009644 +0.009715 +0.00955 +0.009467 +0.00952 +0.009647 +0.009715 +0.009773 +0.009613 +0.009529 +0.009556 +0.009658 +0.009733 +0.009805 +0.009662 +0.009573 +0.009617 +0.00971 +0.009811 +0.009873 +0.009697 +0.009621 +0.009694 +0.009778 +0.009852 +0.009926 +0.00975 +0.009654 +0.009714 +0.009805 +0.009902 +0.009977 +0.009783 +0.009733 +0.009777 +0.009873 +0.009963 +0.010022 +0.00983 +0.00975 +0.009828 +0.009901 +0.010003 +0.010065 +0.009907 +0.009824 +0.009883 +0.009972 +0.010099 +0.010084 +0.009933 +0.009874 +0.0099 +0.010006 +0.010096 +0.010161 +0.010004 +0.009901 +0.009965 +0.010063 +0.01016 +0.010221 +0.010054 +0.009964 +0.010033 +0.010108 +0.010209 +0.010262 +0.010099 +0.010004 +0.01006 +0.010163 +0.010286 +0.010372 +0.010144 +0.010062 +0.010093 +0.010204 +0.010328 +0.010358 +0.010188 +0.010105 +0.010161 +0.010259 +0.010362 +0.010427 +0.010249 +0.010151 +0.010207 +0.010294 +0.010403 +0.010439 +0.010214 +0.010101 +0.010117 +0.010162 +0.010234 +0.010269 +0.010026 +0.009878 +0.009912 +0.009925 +0.009978 +0.00995 +0.009758 +0.009625 +0.009609 +0.009654 +0.009702 +0.009725 +0.009508 +0.009375 +0.009389 +0.009411 +0.009453 +0.009475 +0.00926 +0.009137 +0.009157 +0.009201 +0.009232 +0.009251 +0.009061 +0.008958 +0.00897 +0.008986 +0.009034 +0.009059 +0.008876 +0.008772 +0.008792 +0.008842 +0.008884 +0.008902 +0.008739 +0.008613 +0.008643 +0.008684 +0.00874 +0.008776 +0.008607 +0.008507 +0.00853 +0.008589 +0.008638 +0.008654 +0.008493 +0.008406 +0.008432 +0.00849 +0.008564 +0.008614 +0.008471 +0.008367 +0.008404 +0.008474 +0.008548 +0.008611 +0.008474 +0.008397 +0.008446 +0.008512 +0.0086 +0.008667 +0.008508 +0.008459 +0.008481 +0.008554 +0.008637 +0.008701 +0.00854 +0.008473 +0.008529 +0.008613 +0.008695 +0.00875 +0.008615 +0.008525 +0.008578 +0.008634 +0.008722 +0.008812 +0.008657 +0.00856 +0.008624 +0.008673 +0.008793 +0.008822 +0.008674 +0.008598 +0.008657 +0.008718 +0.00881 +0.008873 +0.008731 +0.008662 +0.008713 +0.008778 +0.008851 +0.008913 +0.008764 +0.008696 +0.008743 +0.008815 +0.008902 +0.008978 +0.008826 +0.008749 +0.008803 +0.008879 +0.008964 +0.008993 +0.00885 +0.008773 +0.008822 +0.008908 +0.00899 +0.00907 +0.008893 +0.008823 +0.008876 +0.008948 +0.009037 +0.009092 +0.008947 +0.008873 +0.008934 +0.009008 +0.009086 +0.009148 +0.008982 +0.008911 +0.008964 +0.009042 +0.00913 +0.009184 +0.009046 +0.008987 +0.009046 +0.009096 +0.009185 +0.009219 +0.009069 +0.009001 +0.009047 +0.00912 +0.009225 +0.009308 +0.009115 +0.00904 +0.009104 +0.009187 +0.009269 +0.009329 +0.009171 +0.009089 +0.009146 +0.009237 +0.009322 +0.009369 +0.00922 +0.009133 +0.009196 +0.009283 +0.009374 +0.009448 +0.009253 +0.009191 +0.009242 +0.009321 +0.009426 +0.009452 +0.0093 +0.009229 +0.009291 +0.009368 +0.00948 +0.009517 +0.009362 +0.009279 +0.009341 +0.009422 +0.009488 +0.009554 +0.009395 +0.009322 +0.009378 +0.009466 +0.009555 +0.009612 +0.009453 +0.009389 +0.009439 +0.009502 +0.009594 +0.009679 +0.009488 +0.009422 +0.00948 +0.009567 +0.009643 +0.009699 +0.009541 +0.009468 +0.009519 +0.009595 +0.009702 +0.009769 +0.009604 +0.009525 +0.00958 +0.009649 +0.009736 +0.009809 +0.00964 +0.009551 +0.009626 +0.009719 +0.009816 +0.009849 +0.009679 +0.009598 +0.009664 +0.009775 +0.009836 +0.009899 +0.009746 +0.00966 +0.009704 +0.009806 +0.009888 +0.009957 +0.0098 +0.009691 +0.009755 +0.009847 +0.009952 +0.010021 +0.009856 +0.009761 +0.009802 +0.009903 +0.009997 +0.010093 +0.00988 +0.009789 +0.009852 +0.009957 +0.010064 +0.010119 +0.009939 +0.00985 +0.009898 +0.009992 +0.0101 +0.010149 +0.009989 +0.009924 +0.009965 +0.010058 +0.010157 +0.010193 +0.010023 +0.009948 +0.010009 +0.010095 +0.010219 +0.010259 +0.010122 +0.009994 +0.010053 +0.01014 +0.010243 +0.010319 +0.010137 +0.010047 +0.010116 +0.010209 +0.010291 +0.01037 +0.01018 +0.010096 +0.010171 +0.010239 +0.010359 +0.010431 +0.010274 +0.010152 +0.010207 +0.010293 +0.010401 +0.010481 +0.010316 +0.010202 +0.010262 +0.010381 +0.010469 +0.010529 +0.010326 +0.01021 +0.010291 +0.010362 +0.010447 +0.010516 +0.010245 +0.01012 +0.010164 +0.010226 +0.01029 +0.010302 +0.010086 +0.009941 +0.009984 +0.010028 +0.010078 +0.010072 +0.009855 +0.009762 +0.009746 +0.009799 +0.009839 +0.009882 +0.009642 +0.009535 +0.009542 +0.0096 +0.009616 +0.00963 +0.009434 +0.009304 +0.009334 +0.009379 +0.009433 +0.009451 +0.009239 +0.00913 +0.00914 +0.009188 +0.009233 +0.009235 +0.009048 +0.008932 +0.008972 +0.009047 +0.009093 +0.00908 +0.008882 +0.008782 +0.008808 +0.008875 +0.008935 +0.008946 +0.008766 +0.008664 +0.008697 +0.008761 +0.008824 +0.008835 +0.008651 +0.008567 +0.008605 +0.008669 +0.008734 +0.008783 +0.008603 +0.008534 +0.0086 +0.008677 +0.008753 +0.008807 +0.008654 +0.008565 +0.008643 +0.008694 +0.008783 +0.008837 +0.008687 +0.008616 +0.008671 +0.008742 +0.00883 +0.008908 +0.008734 +0.008653 +0.008708 +0.00879 +0.008877 +0.008939 +0.008799 +0.008697 +0.008751 +0.008834 +0.008918 +0.008993 +0.008812 +0.008743 +0.008792 +0.008868 +0.008978 +0.009039 +0.008896 +0.008814 +0.008847 +0.008921 +0.008989 +0.009053 +0.008911 +0.008835 +0.00889 +0.008968 +0.009038 +0.009108 +0.008986 +0.008876 +0.008923 +0.008998 +0.009105 +0.009161 +0.009005 +0.008938 +0.008985 +0.009049 +0.009133 +0.009203 +0.009038 +0.008963 +0.009024 +0.009108 +0.009219 +0.009266 +0.009106 +0.009024 +0.009083 +0.009138 +0.009213 +0.009277 +0.009123 +0.009058 +0.009105 +0.009191 +0.009285 +0.009345 +0.009182 +0.009104 +0.009163 +0.009227 +0.009329 +0.009399 +0.009235 +0.009151 +0.009214 +0.009289 +0.009362 +0.009435 +0.009279 +0.009221 +0.009267 +0.009325 +0.009415 +0.00948 +0.009331 +0.009273 +0.009291 +0.009377 +0.009481 +0.009508 +0.009358 +0.009293 +0.009336 +0.009427 +0.009526 +0.009568 +0.009408 +0.009325 +0.009396 +0.009469 +0.009561 +0.009644 +0.009462 +0.009392 +0.009451 +0.009533 +0.009643 +0.009657 +0.009507 +0.009417 +0.00948 +0.009576 +0.009647 +0.009753 +0.009581 +0.009471 +0.009529 +0.009611 +0.009693 +0.009761 +0.009603 +0.009525 +0.00958 +0.00967 +0.009762 +0.009821 +0.009646 +0.009578 +0.009606 +0.009704 +0.009819 +0.00989 +0.009748 +0.009631 +0.009684 +0.009743 +0.009845 +0.009915 +0.009741 +0.009659 +0.009722 +0.009833 +0.009909 +0.009991 +0.0098 +0.009711 +0.009767 +0.009848 +0.00995 +0.010021 +0.00985 +0.009753 +0.009839 +0.009926 +0.010016 +0.010074 +0.009906 +0.009835 +0.009865 +0.009958 +0.01006 +0.010113 +0.009935 +0.009846 +0.009906 +0.010008 +0.0101 +0.010161 +0.010009 +0.009904 +0.009969 +0.010068 +0.010173 +0.010218 +0.010043 +0.009955 +0.010015 +0.010112 +0.01021 +0.010259 +0.01009 +0.01003 +0.010125 +0.010177 +0.01025 +0.010311 +0.010158 +0.010051 +0.010118 +0.010192 +0.010314 +0.010368 +0.010208 +0.010128 +0.010164 +0.010253 +0.010363 +0.01042 +0.010241 +0.010167 +0.010204 +0.010323 +0.010447 +0.010486 +0.010306 +0.010204 +0.010294 +0.010374 +0.010455 +0.010524 +0.010336 +0.010259 +0.010321 +0.010412 +0.010509 +0.01056 +0.010363 +0.010276 +0.010299 +0.010366 +0.010429 +0.010479 +0.010224 +0.010086 +0.010109 +0.010155 +0.0102 +0.010199 +0.009987 +0.009861 +0.009877 +0.009909 +0.009964 +0.009956 +0.009701 +0.009584 +0.009603 +0.009647 +0.009689 +0.009703 +0.009488 +0.009372 +0.009386 +0.009414 +0.009463 +0.00946 +0.009277 +0.009167 +0.009183 +0.009239 +0.009254 +0.009257 +0.009074 +0.00897 +0.009 +0.009029 +0.009083 +0.009104 +0.008938 +0.008867 +0.008864 +0.008896 +0.008939 +0.008954 +0.008791 +0.008696 +0.008722 +0.008765 +0.008824 +0.00887 +0.008672 +0.008583 +0.008618 +0.008665 +0.008713 +0.008756 +0.008591 +0.008509 +0.00855 +0.008618 +0.00868 +0.008731 +0.00857 +0.008502 +0.008552 +0.008617 +0.008715 +0.008777 +0.008638 +0.008548 +0.008606 +0.008673 +0.008751 +0.008814 +0.008652 +0.008575 +0.008631 +0.008714 +0.008787 +0.008847 +0.008706 +0.00863 +0.008691 +0.008764 +0.008853 +0.008901 +0.008748 +0.008663 +0.008721 +0.008812 +0.008874 +0.008937 +0.0088 +0.008705 +0.008761 +0.008842 +0.008942 +0.009005 +0.008821 +0.008739 +0.008813 +0.008906 +0.008972 +0.009033 +0.008881 +0.008809 +0.008839 +0.008917 +0.009013 +0.009077 +0.008925 +0.00884 +0.008902 +0.008989 +0.009073 +0.009134 +0.00898 +0.008898 +0.008926 +0.009015 +0.009103 +0.009151 +0.008999 +0.008936 +0.009013 +0.0091 +0.009146 +0.009203 +0.009058 +0.008975 +0.009036 +0.009118 +0.009191 +0.009265 +0.009109 +0.00903 +0.009086 +0.009149 +0.009241 +0.009308 +0.009157 +0.009068 +0.009117 +0.009204 +0.009303 +0.009363 +0.009203 +0.009133 +0.009168 +0.009239 +0.009341 +0.009414 +0.009263 +0.009148 +0.009201 +0.009287 +0.009381 +0.009452 +0.009281 +0.009201 +0.009279 +0.009336 +0.009437 +0.009504 +0.009345 +0.009268 +0.009299 +0.009391 +0.00947 +0.009551 +0.009395 +0.009282 +0.009347 +0.009455 +0.009549 +0.009595 +0.009429 +0.009358 +0.009414 +0.009502 +0.009562 +0.009623 +0.009464 +0.009396 +0.009453 +0.009535 +0.009625 +0.009694 +0.009532 +0.009443 +0.009488 +0.009568 +0.009668 +0.009749 +0.009583 +0.009496 +0.009548 +0.009625 +0.009721 +0.00979 +0.009619 +0.009529 +0.009587 +0.009688 +0.009814 +0.009872 +0.009663 +0.009601 +0.009615 +0.009716 +0.009807 +0.009868 +0.009705 +0.009635 +0.009703 +0.009789 +0.009891 +0.009937 +0.00975 +0.009677 +0.009724 +0.009817 +0.009927 +0.009996 +0.009808 +0.009735 +0.00979 +0.009853 +0.009973 +0.010038 +0.009899 +0.00978 +0.00983 +0.009938 +0.010035 +0.010094 +0.009901 +0.009812 +0.009884 +0.009959 +0.01006 +0.010147 +0.009973 +0.009893 +0.009953 +0.01003 +0.010108 +0.010187 +0.009999 +0.009921 +0.009984 +0.010085 +0.010167 +0.010238 +0.010057 +0.010008 +0.010035 +0.010114 +0.010213 +0.010285 +0.01012 +0.010044 +0.010107 +0.010166 +0.010267 +0.010329 +0.010159 +0.010067 +0.010124 +0.010235 +0.010338 +0.010403 +0.010229 +0.010127 +0.010169 +0.010277 +0.01038 +0.010434 +0.010259 +0.010208 +0.010248 +0.010318 +0.010415 +0.010491 +0.010318 +0.010216 +0.010283 +0.010392 +0.010501 +0.010524 +0.010363 +0.010237 +0.010283 +0.010358 +0.010403 +0.010433 +0.010206 +0.010087 +0.010122 +0.010159 +0.010199 +0.010214 +0.009994 +0.009855 +0.00988 +0.009956 +0.00997 +0.009935 +0.009726 +0.009601 +0.009622 +0.00965 +0.009689 +0.009702 +0.009487 +0.009372 +0.009388 +0.009436 +0.009479 +0.009475 +0.009277 +0.009139 +0.00917 +0.009208 +0.009247 +0.009256 +0.009056 +0.008953 +0.008991 +0.009026 +0.00906 +0.009094 +0.00892 +0.008777 +0.008824 +0.00885 +0.00889 +0.008922 +0.008747 +0.008645 +0.008681 +0.008737 +0.00877 +0.008803 +0.008636 +0.008542 +0.008573 +0.008627 +0.008684 +0.008726 +0.008562 +0.008465 +0.008506 +0.008552 +0.008623 +0.008675 +0.008518 +0.008448 +0.008498 +0.008584 +0.008672 +0.008746 +0.008565 +0.008489 +0.008529 +0.008603 +0.008707 +0.008742 +0.008596 +0.008525 +0.008587 +0.008648 +0.008741 +0.008796 +0.008648 +0.008567 +0.008622 +0.008703 +0.008782 +0.008847 +0.008693 +0.008625 +0.008691 +0.008747 +0.008821 +0.008882 +0.00876 +0.008652 +0.008712 +0.008789 +0.008884 +0.008913 +0.00878 +0.008711 +0.00876 +0.008842 +0.008925 +0.008966 +0.008811 +0.008743 +0.008795 +0.008868 +0.008965 +0.009013 +0.008864 +0.008794 +0.008846 +0.008918 +0.009004 +0.009067 +0.008919 +0.008834 +0.008885 +0.008974 +0.009065 +0.009104 +0.008959 +0.00889 +0.008955 +0.009001 +0.009082 +0.009144 +0.008999 +0.00893 +0.008983 +0.009059 +0.009156 +0.009217 +0.00903 +0.00896 +0.009021 +0.009094 +0.009183 +0.009249 +0.009104 +0.009023 +0.009075 +0.009159 +0.009246 +0.009278 +0.009133 +0.00904 +0.009104 +0.009193 +0.009296 +0.009376 +0.009196 +0.009091 +0.009147 +0.009226 +0.009327 +0.009379 +0.009216 +0.009146 +0.009207 +0.009298 +0.009391 +0.009452 +0.009264 +0.009191 +0.00925 +0.009327 +0.009406 +0.009483 +0.009327 +0.009253 +0.009309 +0.009394 +0.009478 +0.009529 +0.009356 +0.009294 +0.00936 +0.009412 +0.009512 +0.009599 +0.009398 +0.009329 +0.009384 +0.00947 +0.009565 +0.009618 +0.009454 +0.009371 +0.009441 +0.009532 +0.009624 +0.009677 +0.009509 +0.009421 +0.009495 +0.009559 +0.009656 +0.00972 +0.009568 +0.009482 +0.009538 +0.009632 +0.009742 +0.009764 +0.009583 +0.009506 +0.009569 +0.009665 +0.00978 +0.009806 +0.009646 +0.00956 +0.009624 +0.009713 +0.009811 +0.009869 +0.009708 +0.00963 +0.009687 +0.009752 +0.009854 +0.009907 +0.009745 +0.009668 +0.009725 +0.009803 +0.00991 +0.009999 +0.009837 +0.009716 +0.009767 +0.009836 +0.009945 +0.010018 +0.009839 +0.009781 +0.00981 +0.009925 +0.010013 +0.01008 +0.00989 +0.00981 +0.009862 +0.009957 +0.010052 +0.010114 +0.009945 +0.009841 +0.009912 +0.010006 +0.010106 +0.010171 +0.010009 +0.009954 +0.009965 +0.010053 +0.01015 +0.010227 +0.010032 +0.009945 +0.010012 +0.01011 +0.010223 +0.010255 +0.010105 +0.010031 +0.010075 +0.010176 +0.010266 +0.010297 +0.010142 +0.010051 +0.010113 +0.010211 +0.010306 +0.01037 +0.010197 +0.010127 +0.010198 +0.010249 +0.010372 +0.01041 +0.010243 +0.010151 +0.010216 +0.010304 +0.010411 +0.010485 +0.010303 +0.010212 +0.010272 +0.010366 +0.010483 +0.010538 +0.010368 +0.010274 +0.010303 +0.010406 +0.010494 +0.010548 +0.010348 +0.01024 +0.010285 +0.010303 +0.010369 +0.010374 +0.010179 +0.009997 +0.010033 +0.010056 +0.010109 +0.010118 +0.009895 +0.009769 +0.009773 +0.009808 +0.009852 +0.00986 +0.009635 +0.009516 +0.009514 +0.009546 +0.009601 +0.009607 +0.009378 +0.009254 +0.009273 +0.009317 +0.009355 +0.009345 +0.009142 +0.00903 +0.009056 +0.009091 +0.009155 +0.009129 +0.008952 +0.008829 +0.008853 +0.008897 +0.008952 +0.008972 +0.008807 +0.008706 +0.008728 +0.008762 +0.008814 +0.008849 +0.008655 +0.008553 +0.008592 +0.008636 +0.008691 +0.008737 +0.00856 +0.008471 +0.00851 +0.008536 +0.008595 +0.008638 +0.008482 +0.008394 +0.008435 +0.008503 +0.008589 +0.008674 +0.008492 +0.008417 +0.008454 +0.008539 +0.008607 +0.00867 +0.008527 +0.008461 +0.008498 +0.008591 +0.00868 +0.008719 +0.008579 +0.008506 +0.008552 +0.008611 +0.008695 +0.008765 +0.008629 +0.008564 +0.008624 +0.008658 +0.008727 +0.008797 +0.008655 +0.008584 +0.008647 +0.008701 +0.008786 +0.008841 +0.008706 +0.008648 +0.008686 +0.008757 +0.008829 +0.008894 +0.008745 +0.008666 +0.008717 +0.008798 +0.008877 +0.008957 +0.00881 +0.008718 +0.008766 +0.008847 +0.008937 +0.008996 +0.008839 +0.008747 +0.008806 +0.008899 +0.008973 +0.009019 +0.008859 +0.00879 +0.008855 +0.008927 +0.009015 +0.00908 +0.008927 +0.008844 +0.008904 +0.008992 +0.009072 +0.009113 +0.008964 +0.008894 +0.008936 +0.009017 +0.009114 +0.009156 +0.009015 +0.008947 +0.009003 +0.009107 +0.009169 +0.009199 +0.009036 +0.00897 +0.00903 +0.009136 +0.009191 +0.009246 +0.009107 +0.009036 +0.009085 +0.00917 +0.009253 +0.009288 +0.009142 +0.009064 +0.009117 +0.009202 +0.009295 +0.009355 +0.009196 +0.009117 +0.009169 +0.009238 +0.009335 +0.009406 +0.009267 +0.009176 +0.00921 +0.009306 +0.009399 +0.009435 +0.009284 +0.009192 +0.009274 +0.009336 +0.009432 +0.009483 +0.009334 +0.00926 +0.009326 +0.009407 +0.009493 +0.009528 +0.009371 +0.009298 +0.009339 +0.00943 +0.009527 +0.0096 +0.009443 +0.009359 +0.009426 +0.009537 +0.009567 +0.009614 +0.009449 +0.009379 +0.009443 +0.00954 +0.009605 +0.00969 +0.009522 +0.009446 +0.009493 +0.009582 +0.00967 +0.009734 +0.009573 +0.009496 +0.009552 +0.009632 +0.009728 +0.009772 +0.009612 +0.009532 +0.009583 +0.009667 +0.009788 +0.009886 +0.00967 +0.0096 +0.009635 +0.009697 +0.009814 +0.009862 +0.0097 +0.009624 +0.009686 +0.00977 +0.00987 +0.009941 +0.009761 +0.009671 +0.009737 +0.009817 +0.009913 +0.009991 +0.009818 +0.009737 +0.00979 +0.009856 +0.00996 +0.010034 +0.009875 +0.0098 +0.009815 +0.009921 +0.010026 +0.010116 +0.009903 +0.009804 +0.009868 +0.009965 +0.010063 +0.010122 +0.009968 +0.009874 +0.009931 +0.010026 +0.010104 +0.010186 +0.010007 +0.009927 +0.00998 +0.010075 +0.010188 +0.010245 +0.010056 +0.009989 +0.010057 +0.010107 +0.010204 +0.010269 +0.010108 +0.010034 +0.010119 +0.010178 +0.010264 +0.010313 +0.010153 +0.010066 +0.010128 +0.010217 +0.010339 +0.010407 +0.010219 +0.010135 +0.010185 +0.010257 +0.010376 +0.010435 +0.010252 +0.010181 +0.010276 +0.010324 +0.010409 +0.010481 +0.010288 +0.010205 +0.01025 +0.010336 +0.01046 +0.010428 +0.010206 +0.010073 +0.010105 +0.010147 +0.010207 +0.010225 +0.009992 +0.009875 +0.009906 +0.009933 +0.009988 +0.009975 +0.009733 +0.009607 +0.009636 +0.009708 +0.009751 +0.009695 +0.009478 +0.009358 +0.009383 +0.009415 +0.009462 +0.009478 +0.00925 +0.009127 +0.009156 +0.009205 +0.009252 +0.00926 +0.009062 +0.00892 +0.008948 +0.009 +0.00905 +0.009065 +0.008878 +0.00877 +0.008806 +0.008857 +0.008913 +0.008938 +0.008767 +0.008637 +0.008653 +0.008709 +0.008781 +0.008781 +0.008613 +0.008513 +0.008538 +0.008606 +0.008656 +0.008689 +0.008509 +0.008427 +0.008455 +0.008517 +0.008589 +0.008642 +0.008476 +0.008393 +0.008444 +0.008499 +0.008585 +0.008644 +0.008502 +0.008425 +0.008467 +0.008534 +0.00864 +0.00872 +0.008564 +0.008466 +0.008512 +0.008575 +0.008661 +0.008747 +0.008576 +0.008499 +0.008547 +0.008633 +0.008711 +0.008769 +0.008628 +0.008545 +0.008599 +0.008677 +0.008763 +0.008836 +0.008661 +0.008589 +0.008652 +0.008724 +0.00881 +0.008873 +0.008707 +0.008624 +0.008682 +0.008766 +0.008871 +0.008904 +0.008745 +0.008671 +0.008723 +0.008804 +0.008887 +0.008952 +0.008824 +0.008724 +0.008781 +0.00884 +0.008932 +0.008998 +0.008848 +0.008768 +0.008816 +0.008889 +0.008979 +0.009037 +0.008892 +0.008803 +0.008862 +0.008946 +0.009029 +0.009099 +0.008939 +0.00887 +0.008929 +0.008995 +0.009059 +0.009111 +0.00897 +0.008894 +0.008954 +0.009046 +0.009124 +0.009193 +0.009036 +0.008938 +0.008979 +0.009065 +0.009162 +0.009214 +0.009068 +0.008997 +0.009044 +0.009117 +0.009214 +0.009266 +0.009112 +0.009034 +0.009078 +0.009155 +0.00926 +0.009346 +0.009194 +0.009081 +0.009163 +0.009192 +0.00929 +0.009359 +0.009197 +0.009114 +0.009168 +0.009263 +0.009362 +0.009408 +0.009266 +0.00917 +0.009225 +0.009309 +0.009398 +0.009447 +0.009292 +0.009218 +0.009259 +0.009347 +0.009449 +0.009498 +0.009347 +0.009268 +0.009333 +0.009433 +0.009479 +0.009552 +0.009389 +0.009333 +0.009343 +0.009433 +0.009528 +0.009605 +0.00943 +0.009348 +0.009414 +0.009507 +0.009602 +0.009664 +0.00949 +0.009392 +0.009449 +0.009538 +0.00964 +0.009693 +0.009534 +0.009454 +0.0095 +0.009595 +0.009693 +0.009779 +0.009579 +0.009479 +0.009547 +0.009634 +0.009745 +0.009823 +0.009638 +0.00954 +0.0096 +0.00968 +0.009781 +0.009843 +0.009669 +0.00959 +0.009662 +0.009754 +0.00984 +0.009911 +0.009702 +0.009635 +0.009705 +0.009791 +0.009877 +0.009945 +0.009797 +0.009737 +0.009748 +0.009832 +0.009931 +0.009969 +0.009821 +0.009734 +0.009792 +0.00992 +0.00998 +0.010029 +0.009865 +0.009769 +0.009841 +0.009938 +0.01002 +0.010103 +0.009941 +0.009854 +0.009912 +0.009995 +0.010072 +0.010146 +0.009971 +0.009894 +0.009979 +0.010049 +0.01013 +0.010199 +0.010029 +0.009948 +0.009993 +0.010066 +0.010177 +0.010239 +0.010077 +0.009989 +0.01004 +0.010136 +0.010248 +0.010298 +0.010121 +0.010037 +0.010095 +0.010189 +0.010301 +0.010367 +0.010184 +0.010084 +0.010164 +0.010272 +0.010326 +0.010383 +0.010234 +0.010135 +0.010205 +0.010304 +0.010408 +0.010449 +0.01026 +0.010179 +0.010237 +0.01034 +0.010452 +0.010517 +0.010326 +0.010229 +0.010283 +0.010359 +0.010409 +0.010443 +0.010216 +0.010092 +0.010163 +0.010226 +0.010249 +0.010238 +0.010032 +0.009889 +0.009955 +0.009955 +0.009998 +0.010016 +0.009798 +0.009669 +0.009684 +0.009716 +0.009762 +0.00978 +0.009548 +0.009427 +0.009445 +0.009508 +0.009545 +0.009556 +0.009348 +0.00921 +0.009221 +0.009283 +0.00933 +0.009355 +0.009116 +0.009008 +0.009028 +0.009075 +0.009129 +0.00914 +0.008945 +0.008852 +0.008857 +0.008916 +0.008956 +0.008981 +0.008795 +0.008699 +0.008714 +0.008776 +0.00882 +0.008849 +0.008671 +0.008561 +0.008596 +0.008656 +0.008719 +0.008751 +0.008575 +0.008488 +0.008533 +0.008605 +0.008648 +0.008682 +0.00852 +0.008452 +0.008509 +0.008591 +0.008675 +0.008732 +0.008588 +0.008502 +0.008569 +0.008611 +0.008707 +0.008763 +0.00861 +0.008542 +0.008599 +0.008669 +0.008745 +0.008816 +0.00866 +0.008582 +0.008634 +0.008716 +0.008787 +0.008859 +0.008714 +0.008649 +0.008721 +0.008765 +0.008846 +0.008896 +0.00874 +0.008666 +0.008715 +0.008791 +0.00887 +0.008949 +0.008786 +0.008715 +0.008786 +0.008863 +0.008917 +0.008983 +0.008838 +0.008763 +0.008811 +0.008883 +0.008973 +0.009025 +0.008891 +0.008826 +0.00886 +0.008936 +0.009026 +0.009076 +0.008947 +0.008849 +0.008894 +0.008964 +0.009058 +0.00914 +0.008961 +0.00889 +0.008939 +0.009026 +0.009105 +0.009164 +0.009019 +0.008938 +0.008987 +0.00908 +0.00917 +0.009203 +0.009052 +0.008982 +0.009042 +0.009115 +0.009198 +0.009262 +0.009106 +0.009034 +0.009097 +0.009185 +0.009281 +0.009284 +0.00914 +0.00906 +0.009122 +0.009201 +0.009304 +0.009359 +0.009202 +0.009126 +0.009174 +0.009255 +0.009348 +0.009381 +0.009228 +0.009151 +0.009213 +0.009298 +0.009401 +0.009445 +0.009285 +0.009205 +0.009269 +0.009345 +0.009431 +0.0095 +0.009342 +0.009283 +0.009317 +0.0094 +0.009476 +0.009528 +0.009382 +0.009282 +0.009349 +0.009452 +0.009523 +0.009593 +0.009442 +0.009357 +0.009413 +0.009496 +0.009574 +0.009626 +0.009468 +0.009397 +0.009434 +0.009527 +0.009622 +0.009686 +0.009526 +0.009438 +0.009513 +0.009609 +0.009704 +0.009721 +0.009549 +0.009484 +0.009553 +0.009618 +0.009711 +0.009786 +0.009618 +0.009534 +0.009593 +0.00967 +0.009766 +0.009846 +0.009671 +0.009585 +0.009638 +0.009724 +0.009814 +0.009879 +0.009719 +0.009627 +0.009678 +0.009796 +0.009891 +0.009979 +0.00976 +0.009676 +0.009754 +0.009796 +0.009908 +0.009959 +0.009807 +0.00974 +0.009767 +0.009865 +0.009972 +0.01003 +0.009857 +0.009772 +0.009824 +0.009919 +0.010036 +0.010097 +0.00991 +0.009808 +0.009889 +0.009964 +0.01007 +0.010137 +0.00999 +0.009888 +0.009935 +0.010022 +0.010119 +0.01021 +0.009992 +0.0099 +0.009974 +0.010047 +0.010171 +0.010241 +0.010047 +0.009964 +0.010037 +0.010106 +0.010221 +0.010289 +0.010112 +0.010019 +0.010103 +0.010188 +0.010263 +0.010333 +0.010164 +0.010112 +0.010124 +0.010212 +0.010302 +0.010399 +0.010222 +0.010125 +0.010216 +0.010279 +0.010353 +0.010422 +0.010254 +0.010156 +0.010245 +0.010319 +0.010425 +0.010487 +0.010295 +0.010192 +0.010242 +0.010342 +0.010398 +0.010434 +0.010233 +0.01009 +0.01011 +0.01018 +0.010239 +0.010207 +0.009984 +0.009866 +0.009883 +0.009924 +0.010005 +0.009996 +0.009752 +0.009626 +0.009622 +0.009674 +0.009705 +0.009707 +0.009495 +0.009366 +0.009395 +0.009446 +0.009474 +0.009483 +0.00927 +0.009151 +0.009165 +0.009207 +0.009256 +0.009273 +0.009091 +0.008939 +0.008952 +0.008994 +0.009041 +0.009067 +0.008863 +0.00876 +0.008791 +0.008848 +0.008903 +0.008904 +0.008732 +0.008623 +0.008648 +0.008685 +0.00874 +0.008757 +0.008583 +0.008491 +0.008519 +0.008572 +0.008626 +0.008664 +0.008492 +0.008401 +0.008441 +0.008508 +0.008587 +0.008623 +0.008464 +0.008371 +0.008428 +0.008505 +0.008588 +0.008635 +0.008501 +0.008422 +0.00847 +0.00854 +0.008622 +0.008696 +0.008536 +0.008464 +0.008516 +0.008606 +0.008678 +0.008723 +0.008578 +0.008507 +0.008551 +0.00863 +0.008724 +0.00877 +0.008633 +0.008551 +0.00862 +0.008699 +0.008791 +0.008825 +0.00865 +0.008578 +0.008667 +0.008707 +0.008786 +0.00885 +0.0087 +0.008631 +0.00869 +0.008769 +0.008864 +0.008904 +0.008761 +0.008668 +0.008729 +0.008797 +0.008883 +0.008954 +0.008798 +0.008717 +0.008773 +0.00886 +0.008936 +0.008991 +0.008848 +0.008771 +0.008841 +0.008892 +0.00897 +0.009036 +0.008895 +0.008815 +0.008868 +0.008937 +0.009018 +0.009084 +0.008925 +0.008848 +0.008908 +0.008981 +0.009081 +0.009138 +0.008994 +0.008894 +0.00895 +0.009026 +0.00911 +0.009174 +0.009023 +0.008949 +0.009002 +0.009065 +0.00918 +0.009239 +0.009099 +0.008985 +0.009043 +0.009109 +0.009196 +0.009266 +0.0091 +0.009027 +0.009107 +0.00916 +0.009243 +0.009307 +0.00916 +0.009075 +0.009128 +0.009219 +0.009298 +0.009362 +0.009217 +0.009136 +0.009183 +0.009252 +0.00935 +0.009408 +0.009249 +0.009171 +0.009224 +0.009332 +0.009416 +0.00945 +0.009292 +0.009213 +0.009278 +0.009334 +0.00943 +0.009501 +0.009336 +0.009262 +0.00932 +0.009401 +0.009485 +0.00955 +0.009394 +0.009301 +0.009355 +0.009438 +0.009537 +0.009604 +0.009444 +0.009362 +0.009421 +0.009487 +0.009594 +0.009665 +0.009499 +0.009392 +0.009462 +0.009536 +0.009626 +0.009688 +0.009527 +0.009444 +0.009524 +0.009579 +0.009675 +0.009753 +0.009575 +0.009489 +0.00955 +0.00963 +0.009733 +0.009799 +0.009646 +0.00956 +0.009591 +0.009692 +0.009779 +0.009832 +0.009677 +0.009599 +0.009666 +0.009737 +0.00983 +0.009896 +0.009743 +0.009644 +0.009679 +0.009769 +0.009876 +0.00993 +0.009768 +0.009679 +0.00974 +0.009834 +0.009935 +0.009988 +0.009811 +0.009745 +0.009785 +0.009872 +0.010003 +0.010068 +0.009867 +0.009782 +0.009842 +0.009969 +0.01002 +0.010078 +0.009902 +0.009831 +0.0099 +0.009981 +0.010124 +0.010127 +0.009964 +0.009875 +0.009946 +0.010023 +0.010118 +0.010201 +0.010017 +0.009947 +0.010012 +0.010094 +0.010165 +0.010245 +0.010067 +0.009986 +0.010039 +0.010131 +0.010266 +0.010293 +0.010122 +0.010036 +0.010095 +0.010166 +0.010273 +0.010341 +0.010181 +0.010083 +0.010133 +0.010258 +0.010351 +0.010402 +0.010227 +0.010116 +0.010181 +0.010284 +0.010375 +0.010437 +0.010297 +0.010184 +0.01024 +0.010339 +0.01045 +0.010484 +0.010297 +0.010167 +0.010217 +0.010302 +0.010338 +0.01035 +0.010146 +0.010009 +0.010027 +0.010078 +0.010116 +0.010116 +0.009906 +0.009769 +0.00978 +0.009821 +0.009856 +0.009864 +0.009652 +0.009529 +0.009538 +0.009566 +0.009625 +0.009638 +0.009417 +0.009277 +0.009287 +0.009345 +0.009367 +0.00937 +0.009176 +0.009063 +0.009102 +0.009116 +0.009158 +0.009173 +0.008968 +0.008871 +0.008887 +0.008932 +0.008963 +0.008989 +0.008834 +0.008724 +0.008743 +0.008781 +0.008821 +0.00885 +0.008658 +0.008572 +0.008594 +0.008665 +0.008692 +0.00872 +0.008539 +0.008441 +0.00848 +0.008532 +0.008598 +0.008611 +0.008464 +0.008372 +0.008415 +0.008475 +0.008547 +0.008591 +0.008462 +0.008386 +0.008429 +0.008491 +0.008576 +0.008646 +0.008487 +0.00842 +0.008458 +0.008545 +0.008624 +0.008696 +0.008532 +0.008471 +0.008532 +0.008588 +0.008653 +0.008704 +0.008567 +0.008497 +0.008555 +0.008615 +0.00872 +0.008786 +0.00863 +0.008567 +0.008593 +0.008668 +0.008734 +0.008795 +0.008654 +0.008587 +0.008634 +0.008719 +0.008785 +0.008853 +0.008703 +0.008627 +0.008689 +0.008743 +0.008837 +0.008904 +0.008756 +0.008711 +0.008776 +0.008796 +0.008859 +0.008926 +0.008787 +0.008708 +0.008764 +0.008846 +0.008916 +0.008988 +0.008855 +0.008778 +0.008811 +0.008887 +0.008978 +0.009016 +0.008874 +0.008804 +0.008858 +0.008938 +0.00902 +0.009077 +0.008925 +0.008845 +0.008903 +0.008978 +0.009054 +0.00912 +0.008975 +0.008901 +0.008983 +0.009042 +0.009108 +0.009149 +0.009008 +0.008929 +0.008983 +0.009062 +0.009159 +0.009207 +0.009074 +0.008998 +0.009043 +0.009119 +0.009209 +0.009257 +0.009092 +0.009016 +0.009082 +0.009161 +0.009243 +0.009312 +0.009157 +0.009076 +0.009141 +0.009215 +0.009286 +0.00934 +0.009194 +0.009116 +0.009205 +0.009252 +0.009339 +0.00939 +0.009238 +0.009168 +0.00921 +0.00929 +0.009383 +0.009443 +0.009287 +0.009209 +0.009286 +0.009361 +0.009435 +0.009497 +0.009325 +0.009249 +0.00931 +0.00941 +0.009464 +0.009541 +0.009399 +0.009302 +0.009372 +0.009461 +0.009543 +0.009609 +0.009411 +0.009362 +0.009381 +0.009472 +0.009568 +0.009627 +0.009473 +0.009397 +0.009449 +0.009539 +0.009632 +0.0097 +0.009518 +0.009443 +0.009507 +0.009572 +0.009671 +0.009744 +0.009561 +0.009491 +0.009559 +0.009617 +0.009726 +0.009805 +0.009643 +0.009572 +0.009593 +0.009657 +0.009757 +0.009858 +0.009649 +0.009587 +0.009623 +0.009747 +0.009829 +0.009895 +0.009728 +0.009639 +0.009677 +0.00977 +0.00986 +0.009928 +0.009766 +0.009691 +0.009727 +0.009818 +0.009926 +0.009984 +0.00981 +0.009727 +0.009808 +0.009893 +0.00998 +0.010032 +0.009855 +0.009772 +0.009833 +0.009935 +0.010015 +0.010087 +0.009904 +0.00983 +0.009904 +0.009986 +0.010078 +0.010143 +0.009943 +0.009862 +0.009935 +0.01001 +0.010109 +0.0102 +0.010006 +0.009926 +0.010001 +0.010097 +0.010197 +0.01022 +0.010049 +0.009968 +0.010053 +0.010135 +0.010232 +0.01028 +0.010105 +0.010024 +0.010085 +0.010185 +0.010265 +0.010347 +0.010176 +0.010087 +0.010171 +0.010226 +0.010307 +0.010381 +0.010205 +0.010119 +0.010185 +0.010281 +0.010422 +0.010469 +0.010234 +0.010143 +0.010205 +0.010304 +0.010378 +0.010418 +0.010217 +0.010077 +0.010111 +0.010149 +0.010211 +0.010233 +0.010008 +0.009857 +0.009876 +0.00994 +0.00998 +0.009991 +0.009744 +0.009611 +0.009616 +0.009667 +0.009708 +0.009713 +0.009502 +0.009392 +0.009409 +0.009407 +0.009442 +0.009458 +0.009251 +0.00912 +0.009145 +0.009184 +0.009237 +0.009258 +0.009044 +0.008919 +0.008926 +0.008979 +0.009028 +0.009055 +0.008865 +0.008737 +0.008776 +0.008832 +0.008888 +0.008895 +0.008724 +0.008604 +0.008622 +0.008685 +0.008733 +0.008785 +0.008586 +0.008477 +0.00851 +0.008563 +0.00862 +0.008657 +0.008484 +0.008387 +0.008434 +0.008495 +0.008556 +0.008602 +0.00845 +0.008385 +0.008419 +0.008495 +0.008558 +0.008625 +0.008475 +0.008401 +0.008447 +0.008509 +0.008595 +0.008686 +0.008528 +0.008453 +0.008505 +0.00858 +0.008677 +0.008713 +0.008548 +0.008476 +0.008529 +0.008604 +0.008699 +0.008751 +0.0086 +0.008532 +0.008582 +0.008671 +0.008735 +0.00879 +0.008642 +0.008573 +0.008636 +0.008718 +0.008782 +0.008837 +0.008682 +0.008608 +0.008663 +0.008736 +0.00883 +0.008879 +0.00873 +0.008661 +0.008718 +0.008821 +0.008887 +0.008928 +0.00876 +0.008685 +0.008748 +0.008831 +0.008907 +0.008992 +0.008812 +0.008759 +0.0088 +0.008876 +0.008967 +0.009011 +0.00886 +0.008786 +0.008836 +0.008917 +0.008996 +0.009067 +0.008909 +0.008829 +0.008882 +0.008953 +0.009044 +0.009102 +0.008959 +0.008902 +0.008946 +0.009 +0.0091 +0.00916 +0.009008 +0.008932 +0.00897 +0.009036 +0.009138 +0.009212 +0.009029 +0.008959 +0.009024 +0.00909 +0.009184 +0.009254 +0.00909 +0.009003 +0.009059 +0.00914 +0.009227 +0.009291 +0.009144 +0.009064 +0.009117 +0.009193 +0.009285 +0.009369 +0.00917 +0.009094 +0.009143 +0.009228 +0.009331 +0.009399 +0.009229 +0.009155 +0.009203 +0.009269 +0.009381 +0.009442 +0.009255 +0.009184 +0.00924 +0.009339 +0.009428 +0.009491 +0.009322 +0.009245 +0.009299 +0.009372 +0.009454 +0.009518 +0.009366 +0.009289 +0.009361 +0.009428 +0.009496 +0.009562 +0.009412 +0.009335 +0.009391 +0.009454 +0.009564 +0.009615 +0.00946 +0.00939 +0.00944 +0.009538 +0.009595 +0.00967 +0.009501 +0.009422 +0.009486 +0.009547 +0.009664 +0.00974 +0.009559 +0.009475 +0.009533 +0.009607 +0.009742 +0.009752 +0.009594 +0.009511 +0.009568 +0.00966 +0.009779 +0.009803 +0.009644 +0.009567 +0.009609 +0.009714 +0.009804 +0.009866 +0.009716 +0.009627 +0.009662 +0.009749 +0.009855 +0.009914 +0.009734 +0.009657 +0.009717 +0.009814 +0.009932 +0.009993 +0.009824 +0.009687 +0.009755 +0.009854 +0.009946 +0.010018 +0.009831 +0.009776 +0.009844 +0.009906 +0.010002 +0.010071 +0.00988 +0.009799 +0.009868 +0.009946 +0.010062 +0.010122 +0.009931 +0.009848 +0.009919 +0.01 +0.010098 +0.010168 +0.010003 +0.00995 +0.009976 +0.010059 +0.010146 +0.010205 +0.010041 +0.009954 +0.010007 +0.010109 +0.010197 +0.010304 +0.010107 +0.010007 +0.01007 +0.010148 +0.01026 +0.01031 +0.010142 +0.010052 +0.01011 +0.010212 +0.010292 +0.010367 +0.010208 +0.010125 +0.010188 +0.010251 +0.010356 +0.010423 +0.010268 +0.010154 +0.010208 +0.010312 +0.010382 +0.010448 +0.010261 +0.010176 +0.010179 +0.010253 +0.010292 +0.010287 +0.010073 +0.009932 +0.009949 +0.009986 +0.010036 +0.010026 +0.009822 +0.00968 +0.009704 +0.00974 +0.009783 +0.009798 +0.009554 +0.009463 +0.009433 +0.009467 +0.009515 +0.009504 +0.009304 +0.009189 +0.00919 +0.009222 +0.009261 +0.009266 +0.009085 +0.008961 +0.008993 +0.009017 +0.009047 +0.009064 +0.008868 +0.008766 +0.008788 +0.008823 +0.008856 +0.008894 +0.008738 +0.00864 +0.008681 +0.008695 +0.008736 +0.008762 +0.008579 +0.00852 +0.008518 +0.008566 +0.008614 +0.008666 +0.008484 +0.008391 +0.00842 +0.008479 +0.00852 +0.008572 +0.008409 +0.008321 +0.008361 +0.008412 +0.008493 +0.008541 +0.008393 +0.008306 +0.008364 +0.008416 +0.008504 +0.008564 +0.00843 +0.008363 +0.008408 +0.008476 +0.008552 +0.008608 +0.008464 +0.008395 +0.008434 +0.008528 +0.008586 +0.008644 +0.008496 +0.00843 +0.008485 +0.008551 +0.008641 +0.008693 +0.008554 +0.008477 +0.008529 +0.008594 +0.008683 +0.008759 +0.008586 +0.008531 +0.008579 +0.008636 +0.008712 +0.008787 +0.008647 +0.00858 +0.008602 +0.008684 +0.008754 +0.008822 +0.008695 +0.00861 +0.008664 +0.008734 +0.008843 +0.008845 +0.008714 +0.008647 +0.008692 +0.008779 +0.008851 +0.008919 +0.008762 +0.008688 +0.008746 +0.00881 +0.008892 +0.008955 +0.008819 +0.00873 +0.008786 +0.008881 +0.008965 +0.009033 +0.008864 +0.008779 +0.008813 +0.008894 +0.008984 +0.009043 +0.00889 +0.008819 +0.008896 +0.008958 +0.009048 +0.009097 +0.008934 +0.008867 +0.008903 +0.009 +0.009073 +0.009136 +0.009002 +0.008921 +0.008977 +0.009052 +0.009138 +0.009186 +0.009016 +0.008955 +0.009001 +0.009111 +0.00917 +0.009238 +0.009075 +0.009009 +0.00905 +0.009121 +0.00921 +0.009276 +0.009117 +0.009038 +0.009098 +0.009184 +0.009269 +0.00935 +0.009162 +0.009087 +0.009137 +0.009221 +0.009325 +0.009364 +0.009215 +0.009144 +0.009196 +0.009289 +0.009374 +0.009436 +0.009278 +0.009176 +0.009225 +0.009297 +0.009404 +0.009468 +0.009336 +0.009234 +0.009289 +0.009374 +0.009458 +0.009499 +0.009351 +0.009264 +0.009331 +0.009418 +0.009499 +0.009557 +0.009403 +0.009321 +0.009371 +0.009455 +0.009556 +0.009597 +0.009451 +0.009372 +0.009433 +0.00956 +0.00961 +0.009645 +0.009484 +0.009404 +0.009467 +0.009543 +0.009657 +0.009715 +0.009546 +0.009471 +0.009537 +0.009611 +0.009708 +0.009739 +0.009582 +0.009494 +0.009558 +0.009649 +0.009737 +0.009802 +0.009645 +0.009561 +0.009613 +0.009706 +0.009803 +0.009898 +0.009673 +0.009588 +0.009668 +0.009755 +0.009828 +0.009898 +0.009725 +0.009658 +0.009716 +0.009785 +0.009893 +0.009964 +0.009808 +0.009714 +0.009759 +0.009828 +0.009935 +0.010001 +0.009826 +0.009743 +0.009804 +0.009902 +0.010008 +0.010074 +0.009914 +0.009807 +0.009844 +0.009943 +0.010021 +0.010096 +0.009914 +0.009843 +0.009905 +0.009982 +0.010089 +0.010165 +0.009975 +0.009894 +0.009957 +0.010049 +0.010158 +0.010219 +0.010032 +0.009927 +0.010002 +0.010091 +0.010189 +0.010254 +0.010077 +0.010033 +0.01007 +0.010149 +0.010242 +0.010303 +0.010128 +0.010024 +0.010087 +0.010173 +0.010258 +0.010306 +0.010118 +0.009985 +0.010028 +0.010056 +0.010138 +0.010172 +0.00994 +0.009819 +0.009834 +0.009868 +0.009932 +0.009938 +0.009707 +0.009579 +0.009581 +0.009656 +0.009671 +0.009681 +0.009468 +0.009353 +0.00937 +0.00941 +0.009411 +0.009445 +0.009226 +0.009108 +0.009121 +0.009161 +0.0092 +0.009229 +0.009046 +0.00891 +0.008925 +0.008963 +0.008994 +0.009026 +0.008834 +0.008727 +0.008743 +0.008782 +0.008828 +0.008869 +0.008703 +0.008584 +0.008595 +0.008632 +0.008686 +0.00873 +0.008559 +0.00845 +0.008484 +0.008561 +0.008582 +0.008606 +0.008445 +0.008355 +0.008385 +0.008432 +0.008495 +0.008542 +0.008384 +0.008298 +0.008354 +0.008409 +0.00848 +0.008532 +0.00839 +0.008316 +0.008364 +0.008429 +0.008514 +0.008572 +0.00845 +0.008372 +0.008411 +0.00847 +0.008559 +0.00862 +0.008465 +0.008387 +0.008444 +0.008518 +0.008589 +0.008654 +0.008522 +0.00844 +0.008492 +0.008559 +0.008644 +0.008703 +0.00857 +0.008471 +0.008536 +0.008605 +0.008689 +0.008753 +0.008602 +0.008519 +0.008562 +0.008646 +0.008731 +0.008811 +0.008641 +0.008558 +0.00863 +0.008685 +0.008781 +0.008834 +0.008685 +0.008613 +0.008661 +0.008722 +0.008809 +0.008874 +0.008727 +0.008653 +0.008703 +0.008779 +0.008856 +0.008921 +0.008765 +0.0087 +0.008744 +0.008814 +0.008916 +0.008973 +0.008811 +0.008755 +0.008811 +0.008887 +0.008973 +0.009 +0.008862 +0.008768 +0.008829 +0.008925 +0.008982 +0.009056 +0.008911 +0.008821 +0.008869 +0.008948 +0.009047 +0.009107 +0.008947 +0.008878 +0.008918 +0.008999 +0.009103 +0.009164 +0.009 +0.00891 +0.008965 +0.009044 +0.009129 +0.009192 +0.009037 +0.00898 +0.009034 +0.009102 +0.00919 +0.009236 +0.009084 +0.009005 +0.009039 +0.00914 +0.009227 +0.009272 +0.00913 +0.009063 +0.009106 +0.009183 +0.009266 +0.009336 +0.009171 +0.009086 +0.009148 +0.009222 +0.00932 +0.009399 +0.009235 +0.009138 +0.009198 +0.009282 +0.009385 +0.00943 +0.009254 +0.009172 +0.009229 +0.009335 +0.009429 +0.009488 +0.009317 +0.009231 +0.009304 +0.009359 +0.009458 +0.009517 +0.009354 +0.009264 +0.009333 +0.00941 +0.009506 +0.009587 +0.009401 +0.009323 +0.009383 +0.009464 +0.009557 +0.009639 +0.009468 +0.009389 +0.009426 +0.009527 +0.009585 +0.009654 +0.009496 +0.009415 +0.009477 +0.009578 +0.009673 +0.00972 +0.009532 +0.009463 +0.00952 +0.0096 +0.009706 +0.009748 +0.0096 +0.00954 +0.009586 +0.009666 +0.009759 +0.009808 +0.009628 +0.009555 +0.009617 +0.009727 +0.009806 +0.009853 +0.009678 +0.009627 +0.009663 +0.009747 +0.009837 +0.009914 +0.009738 +0.009652 +0.009718 +0.009821 +0.009909 +0.009949 +0.009789 +0.009701 +0.009767 +0.00986 +0.009944 +0.010009 +0.009847 +0.009768 +0.009824 +0.009901 +0.010003 +0.010083 +0.00987 +0.009789 +0.009854 +0.009935 +0.010083 +0.010124 +0.009943 +0.009861 +0.009898 +0.009988 +0.010099 +0.010159 +0.00998 +0.00991 +0.009959 +0.010038 +0.010154 +0.010203 +0.010033 +0.00995 +0.010007 +0.010099 +0.01021 +0.010301 +0.010111 +0.009982 +0.010042 +0.010144 +0.010251 +0.010314 +0.010127 +0.010085 +0.010112 +0.010208 +0.010313 +0.010375 +0.010161 +0.010075 +0.010137 +0.010228 +0.010306 +0.010349 +0.010133 +0.009997 +0.01004 +0.010091 +0.010124 +0.010137 +0.009918 +0.00982 +0.009811 +0.009847 +0.009871 +0.00989 +0.009678 +0.009536 +0.009563 +0.009612 +0.00965 +0.009666 +0.009461 +0.009337 +0.009348 +0.009378 +0.009437 +0.009434 +0.009228 +0.009118 +0.009159 +0.009164 +0.009224 +0.009253 +0.009044 +0.008932 +0.008961 +0.009007 +0.009064 +0.00905 +0.008878 +0.008759 +0.008793 +0.008856 +0.0089 +0.008924 +0.008748 +0.008646 +0.008673 +0.008725 +0.008801 +0.008811 +0.008635 +0.008536 +0.008589 +0.008628 +0.008678 +0.008717 +0.008548 +0.008451 +0.008495 +0.008561 +0.008623 +0.008668 +0.008515 +0.008444 +0.008509 +0.008573 +0.008636 +0.008677 +0.008542 +0.008487 +0.008518 +0.00858 +0.008666 +0.008737 +0.0086 +0.008518 +0.008573 +0.008655 +0.008727 +0.00877 +0.00862 +0.008558 +0.008597 +0.008676 +0.008776 +0.008824 +0.00867 +0.008595 +0.00865 +0.008744 +0.008809 +0.008865 +0.008715 +0.008664 +0.008708 +0.008771 +0.008843 +0.008909 +0.008752 +0.008683 +0.008761 +0.008803 +0.008895 +0.008951 +0.008794 +0.00873 +0.008808 +0.00886 +0.008952 +0.009011 +0.008848 +0.008761 +0.00882 +0.008902 +0.008981 +0.009042 +0.0089 +0.008812 +0.008867 +0.008943 +0.009041 +0.009114 +0.008946 +0.008854 +0.00891 +0.008984 +0.009073 +0.009153 +0.008984 +0.008899 +0.008982 +0.009026 +0.009116 +0.009174 +0.009039 +0.008939 +0.00901 +0.009098 +0.009174 +0.009237 +0.009078 +0.008986 +0.009036 +0.00912 +0.009223 +0.009268 +0.009118 +0.009047 +0.009112 +0.009216 +0.009291 +0.009313 +0.00914 +0.009067 +0.009127 +0.009218 +0.0093 +0.009373 +0.009225 +0.009129 +0.009182 +0.009271 +0.009346 +0.009412 +0.009257 +0.009168 +0.00923 +0.009315 +0.009424 +0.009483 +0.009308 +0.009226 +0.009265 +0.009345 +0.009443 +0.00951 +0.009384 +0.009257 +0.009336 +0.00944 +0.009499 +0.009565 +0.009392 +0.009292 +0.00936 +0.009443 +0.009531 +0.009606 +0.009469 +0.009349 +0.009416 +0.009515 +0.009584 +0.009653 +0.009488 +0.009398 +0.009452 +0.009551 +0.009663 +0.009708 +0.009527 +0.009459 +0.009545 +0.009608 +0.009682 +0.009735 +0.009565 +0.009534 +0.009562 +0.009652 +0.009741 +0.0098 +0.009613 +0.009543 +0.009622 +0.009692 +0.009776 +0.009854 +0.00968 +0.009594 +0.009658 +0.009737 +0.009832 +0.009901 +0.009729 +0.009644 +0.009711 +0.009814 +0.009922 +0.009943 +0.009767 +0.009686 +0.009747 +0.009845 +0.009923 +0.010025 +0.009832 +0.009745 +0.009806 +0.009896 +0.009979 +0.010039 +0.009878 +0.009799 +0.009857 +0.009927 +0.010055 +0.010107 +0.009929 +0.00985 +0.009887 +0.009977 +0.010091 +0.010183 +0.009983 +0.009896 +0.009934 +0.010024 +0.010131 +0.010182 +0.010025 +0.009948 +0.009987 +0.01009 +0.010202 +0.010261 +0.010095 +0.009997 +0.01004 +0.010138 +0.010242 +0.010338 +0.010116 +0.010027 +0.010101 +0.01019 +0.010311 +0.01037 +0.010207 +0.010078 +0.010165 +0.010223 +0.010332 +0.010405 +0.010227 +0.010151 +0.01022 +0.010299 +0.010406 +0.010462 +0.010267 +0.01018 +0.010254 +0.010342 +0.010446 +0.010501 +0.010302 +0.010217 +0.010273 +0.010336 +0.010395 +0.010429 +0.010233 +0.010093 +0.010102 +0.01015 +0.01021 +0.010181 +0.009971 +0.009836 +0.009873 +0.009897 +0.009967 +0.009972 +0.009746 +0.009623 +0.009633 +0.009659 +0.009701 +0.009705 +0.009504 +0.009384 +0.009406 +0.009451 +0.009479 +0.00949 +0.009285 +0.00917 +0.00922 +0.00925 +0.009268 +0.009291 +0.009102 +0.009003 +0.009029 +0.009059 +0.009098 +0.009125 +0.008929 +0.008834 +0.008871 +0.008921 +0.008973 +0.008991 +0.008812 +0.008705 +0.008736 +0.008789 +0.00883 +0.008859 +0.008682 +0.008595 +0.008628 +0.008678 +0.008738 +0.008769 +0.008615 +0.008504 +0.008547 +0.008605 +0.008662 +0.008704 +0.008551 +0.008468 +0.008521 +0.008611 +0.008681 +0.008728 +0.008591 +0.00852 +0.00856 +0.008631 +0.008716 +0.008777 +0.008625 +0.008557 +0.008636 +0.008684 +0.008771 +0.008806 +0.008665 +0.008589 +0.008642 +0.008731 +0.008805 +0.008888 +0.008737 +0.00864 +0.008692 +0.00877 +0.008854 +0.008909 +0.008763 +0.00867 +0.008729 +0.0088 +0.008904 +0.008942 +0.008796 +0.008724 +0.008775 +0.008858 +0.008923 +0.008999 +0.008841 +0.008773 +0.008827 +0.008893 +0.009002 +0.009052 +0.008881 +0.008814 +0.008868 +0.008964 +0.009041 +0.009078 +0.008935 +0.008858 +0.008906 +0.008986 +0.009073 +0.009141 +0.008984 +0.008909 +0.008936 +0.009026 +0.009134 +0.009177 +0.00902 +0.00895 +0.008989 +0.009092 +0.009179 +0.009239 +0.009074 +0.008995 +0.009042 +0.00911 +0.009203 +0.009273 +0.009117 +0.009075 +0.009078 +0.009166 +0.009248 +0.009314 +0.009184 +0.009071 +0.009121 +0.009211 +0.009306 +0.009373 +0.009216 +0.009151 +0.009183 +0.009252 +0.009357 +0.009402 +0.009248 +0.009168 +0.009218 +0.009308 +0.009409 +0.009479 +0.009302 +0.009223 +0.009274 +0.009358 +0.009473 +0.009494 +0.00933 +0.009246 +0.009306 +0.009414 +0.009486 +0.009548 +0.009402 +0.009311 +0.009359 +0.009443 +0.009553 +0.00961 +0.009446 +0.009372 +0.009423 +0.009508 +0.009583 +0.009637 +0.009479 +0.009403 +0.009455 +0.009543 +0.009643 +0.009708 +0.009571 +0.009462 +0.009501 +0.009574 +0.009679 +0.009748 +0.009574 +0.009503 +0.009544 +0.009639 +0.009737 +0.009786 +0.009632 +0.009534 +0.009599 +0.009709 +0.009778 +0.009866 +0.009694 +0.009594 +0.009632 +0.009729 +0.009824 +0.009888 +0.009728 +0.009645 +0.009753 +0.009808 +0.009896 +0.009933 +0.009759 +0.009679 +0.009731 +0.009825 +0.009931 +0.009996 +0.009813 +0.009772 +0.00979 +0.009878 +0.009981 +0.010041 +0.009871 +0.009783 +0.009849 +0.009935 +0.010048 +0.010095 +0.009926 +0.00983 +0.009902 +0.010003 +0.010101 +0.010138 +0.009952 +0.009919 +0.009953 +0.010033 +0.010114 +0.010192 +0.010019 +0.009927 +0.009992 +0.010082 +0.010203 +0.010258 +0.01008 +0.009987 +0.010048 +0.010122 +0.010223 +0.010291 +0.010121 +0.010037 +0.010086 +0.010189 +0.01032 +0.010359 +0.010152 +0.010065 +0.01015 +0.010229 +0.010346 +0.010435 +0.010235 +0.010115 +0.010186 +0.010276 +0.010382 +0.010463 +0.010263 +0.010214 +0.010262 +0.010344 +0.010432 +0.010476 +0.010268 +0.010175 +0.010209 +0.010263 +0.010332 +0.010397 +0.010173 +0.010011 +0.010048 +0.010077 +0.010135 +0.010139 +0.009952 +0.009813 +0.009831 +0.009853 +0.00989 +0.009868 +0.009664 +0.009542 +0.009555 +0.009579 +0.009603 +0.00963 +0.00942 +0.009296 +0.009317 +0.009331 +0.009351 +0.009371 +0.009178 +0.009094 +0.009086 +0.009109 +0.009155 +0.009161 +0.008981 +0.008874 +0.008897 +0.008933 +0.008985 +0.009018 +0.008819 +0.008722 +0.008752 +0.008801 +0.008845 +0.008863 +0.008684 +0.00859 +0.008622 +0.008669 +0.008709 +0.008738 +0.008583 +0.008489 +0.008524 +0.008566 +0.008624 +0.008658 +0.008495 +0.00839 +0.00843 +0.008484 +0.008544 +0.00861 +0.008456 +0.008375 +0.008426 +0.008497 +0.008572 +0.008637 +0.008502 +0.008421 +0.008455 +0.008528 +0.008624 +0.00868 +0.00854 +0.008446 +0.008511 +0.008577 +0.008658 +0.008731 +0.008573 +0.008491 +0.008552 +0.008625 +0.008722 +0.008801 +0.008613 +0.008547 +0.00858 +0.008661 +0.008755 +0.008803 +0.008669 +0.008578 +0.008629 +0.008721 +0.008795 +0.008859 +0.008701 +0.008624 +0.008704 +0.008744 +0.008818 +0.008888 +0.008743 +0.008674 +0.00873 +0.008789 +0.008879 +0.008933 +0.00879 +0.008709 +0.008771 +0.008858 +0.00894 +0.008979 +0.008835 +0.008769 +0.008819 +0.008892 +0.008983 +0.009011 +0.00887 +0.008792 +0.008843 +0.008936 +0.009015 +0.009095 +0.008927 +0.008858 +0.008904 +0.008976 +0.009047 +0.009117 +0.008959 +0.008888 +0.008948 +0.009026 +0.009103 +0.009166 +0.009021 +0.008952 +0.008993 +0.009059 +0.009137 +0.009212 +0.009057 +0.008992 +0.009051 +0.009118 +0.009222 +0.009239 +0.009094 +0.009018 +0.009068 +0.009154 +0.009228 +0.00932 +0.009175 +0.009079 +0.009146 +0.009203 +0.00928 +0.009344 +0.00919 +0.009106 +0.009164 +0.009252 +0.009359 +0.009428 +0.009235 +0.009155 +0.009219 +0.009289 +0.009378 +0.009445 +0.009288 +0.009211 +0.00927 +0.009372 +0.009438 +0.009489 +0.009328 +0.009248 +0.009307 +0.009373 +0.009472 +0.009555 +0.009394 +0.009312 +0.009372 +0.009448 +0.009523 +0.009583 +0.009425 +0.009366 +0.009397 +0.009467 +0.00959 +0.009639 +0.009488 +0.009405 +0.00946 +0.009523 +0.009616 +0.009677 +0.009516 +0.00944 +0.00952 +0.009558 +0.009673 +0.009718 +0.009562 +0.009498 +0.00955 +0.009624 +0.009724 +0.0098 +0.009633 +0.009537 +0.009588 +0.00969 +0.009787 +0.009824 +0.009658 +0.009574 +0.009637 +0.009762 +0.009824 +0.009878 +0.009714 +0.009613 +0.009693 +0.009774 +0.009859 +0.009927 +0.009772 +0.009669 +0.009731 +0.00983 +0.009912 +0.009981 +0.009813 +0.009724 +0.009788 +0.00988 +0.009983 +0.010085 +0.00985 +0.009758 +0.009823 +0.009917 +0.010024 +0.010081 +0.009931 +0.00982 +0.009887 +0.009979 +0.010089 +0.01011 +0.009947 +0.009875 +0.009919 +0.010028 +0.010119 +0.010194 +0.010022 +0.009933 +0.009986 +0.010066 +0.010154 +0.010233 +0.01009 +0.009962 +0.010037 +0.010115 +0.01021 +0.010282 +0.010104 +0.01001 +0.010085 +0.010187 +0.010264 +0.010342 +0.010163 +0.010083 +0.010123 +0.01021 +0.010307 +0.010401 +0.010204 +0.010109 +0.010196 +0.010277 +0.010364 +0.010431 +0.01024 +0.010109 +0.010126 +0.010182 +0.010211 +0.010226 +0.01004 +0.009917 +0.009912 +0.009963 +0.010005 +0.010027 +0.009807 +0.009666 +0.009686 +0.00972 +0.009749 +0.009789 +0.009583 +0.009435 +0.00944 +0.009472 +0.009518 +0.009521 +0.009319 +0.009198 +0.009208 +0.009284 +0.009284 +0.009289 +0.009097 +0.008967 +0.008976 +0.009004 +0.009052 +0.009074 +0.008894 +0.008788 +0.008812 +0.00885 +0.008885 +0.008911 +0.008733 +0.008623 +0.008644 +0.008685 +0.008741 +0.008769 +0.008611 +0.008501 +0.008536 +0.008569 +0.008623 +0.008661 +0.008498 +0.008421 +0.008443 +0.008475 +0.008532 +0.008584 +0.008428 +0.008359 +0.008378 +0.008425 +0.008495 +0.008556 +0.008412 +0.008348 +0.008389 +0.008459 +0.008552 +0.008603 +0.008468 +0.008376 +0.008429 +0.008497 +0.008584 +0.008631 +0.008491 +0.008426 +0.008475 +0.008543 +0.008626 +0.008694 +0.008551 +0.008487 +0.008512 +0.008587 +0.008662 +0.008724 +0.008584 +0.008512 +0.008575 +0.008656 +0.008727 +0.008751 +0.008615 +0.008547 +0.008602 +0.008668 +0.008745 +0.008817 +0.008668 +0.008596 +0.008648 +0.008735 +0.008809 +0.008859 +0.008709 +0.008633 +0.008682 +0.008756 +0.008849 +0.008921 +0.008776 +0.008676 +0.008739 +0.008812 +0.008893 +0.008955 +0.008784 +0.008715 +0.008767 +0.008844 +0.00893 +0.008996 +0.008845 +0.008761 +0.008817 +0.00889 +0.008984 +0.009028 +0.008876 +0.008814 +0.008876 +0.008938 +0.00903 +0.009103 +0.008944 +0.008861 +0.008913 +0.00899 +0.00909 +0.009118 +0.00899 +0.008882 +0.008934 +0.009039 +0.009117 +0.009178 +0.009027 +0.00895 +0.008997 +0.009068 +0.009163 +0.009211 +0.009059 +0.008992 +0.009035 +0.009105 +0.009201 +0.009271 +0.009108 +0.009033 +0.009095 +0.009173 +0.00925 +0.009317 +0.009174 +0.009109 +0.009128 +0.009194 +0.009285 +0.009352 +0.00923 +0.009112 +0.009169 +0.00926 +0.009355 +0.009413 +0.009256 +0.009167 +0.009201 +0.009299 +0.009393 +0.009445 +0.009291 +0.009215 +0.009265 +0.009346 +0.009443 +0.009497 +0.009339 +0.009254 +0.009318 +0.009405 +0.009489 +0.009558 +0.009421 +0.00934 +0.009341 +0.009424 +0.009519 +0.009611 +0.009427 +0.009335 +0.009415 +0.009498 +0.009598 +0.009648 +0.009497 +0.009387 +0.009446 +0.009528 +0.009626 +0.009685 +0.009523 +0.009449 +0.009519 +0.009605 +0.009704 +0.009752 +0.009567 +0.009482 +0.009539 +0.009658 +0.009728 +0.009775 +0.009615 +0.009534 +0.009596 +0.009678 +0.00978 +0.009847 +0.009657 +0.009583 +0.009653 +0.009743 +0.009841 +0.009901 +0.009736 +0.009616 +0.009685 +0.009778 +0.009865 +0.009936 +0.009778 +0.009685 +0.009743 +0.009839 +0.009933 +0.010011 +0.0098 +0.009725 +0.009783 +0.009879 +0.009985 +0.010049 +0.009875 +0.009792 +0.009828 +0.009924 +0.010024 +0.010074 +0.009914 +0.009839 +0.009904 +0.00999 +0.010097 +0.010117 +0.009962 +0.009888 +0.00994 +0.010019 +0.010128 +0.010219 +0.010064 +0.009944 +0.009977 +0.010074 +0.010159 +0.010233 +0.010053 +0.009982 +0.010044 +0.010122 +0.010228 +0.010293 +0.01011 +0.010021 +0.010086 +0.010177 +0.010285 +0.010364 +0.010183 +0.010097 +0.010135 +0.01023 +0.010328 +0.0104 +0.010245 +0.010133 +0.010176 +0.010304 +0.010355 +0.010411 +0.010187 +0.010065 +0.010092 +0.010139 +0.010218 +0.010243 +0.010016 +0.009885 +0.009906 +0.009927 +0.009984 +0.009984 +0.009773 +0.009651 +0.009637 +0.00969 +0.009741 +0.009727 +0.009502 +0.009376 +0.00938 +0.009445 +0.009455 +0.009456 +0.00925 +0.009131 +0.009155 +0.009197 +0.009236 +0.009237 +0.009047 +0.008924 +0.008941 +0.008987 +0.009038 +0.009044 +0.008859 +0.008754 +0.008787 +0.008826 +0.008889 +0.008884 +0.008713 +0.008605 +0.008637 +0.008687 +0.008721 +0.00876 +0.008601 +0.00852 +0.00852 +0.008562 +0.008609 +0.008642 +0.008468 +0.008376 +0.008411 +0.008457 +0.008527 +0.008569 +0.008406 +0.00832 +0.008366 +0.00842 +0.0085 +0.00856 +0.008436 +0.008336 +0.008386 +0.008459 +0.008541 +0.008608 +0.008461 +0.008384 +0.008422 +0.008497 +0.008583 +0.008642 +0.008517 +0.008428 +0.008464 +0.008544 +0.008631 +0.008688 +0.008547 +0.008475 +0.008501 +0.008606 +0.008663 +0.008735 +0.008571 +0.008508 +0.008554 +0.008617 +0.008696 +0.008776 +0.00862 +0.008553 +0.0086 +0.008671 +0.008765 +0.008814 +0.008675 +0.008599 +0.00865 +0.008709 +0.0088 +0.008883 +0.008739 +0.008628 +0.008679 +0.008748 +0.008842 +0.008911 +0.008769 +0.008685 +0.008736 +0.008804 +0.008884 +0.008938 +0.0088 +0.008715 +0.008774 +0.00885 +0.008939 +0.009015 +0.00885 +0.00878 +0.008822 +0.008892 +0.008969 +0.009036 +0.008882 +0.008805 +0.008866 +0.008963 +0.009044 +0.009074 +0.008939 +0.008844 +0.008899 +0.008985 +0.009062 +0.009125 +0.008978 +0.008905 +0.008961 +0.00904 +0.009121 +0.009163 +0.009015 +0.008934 +0.008992 +0.009081 +0.009151 +0.00924 +0.009082 +0.008994 +0.009058 +0.009111 +0.0092 +0.009266 +0.009111 +0.009059 +0.009101 +0.009156 +0.009237 +0.009309 +0.009156 +0.009073 +0.009134 +0.009209 +0.009297 +0.009362 +0.009204 +0.009136 +0.009188 +0.009264 +0.009358 +0.009399 +0.009237 +0.009163 +0.00923 +0.009302 +0.009389 +0.009466 +0.009308 +0.009228 +0.009273 +0.009357 +0.009461 +0.0095 +0.009328 +0.009257 +0.009312 +0.009403 +0.009498 +0.00959 +0.00938 +0.009307 +0.009354 +0.009431 +0.009534 +0.009583 +0.009431 +0.009356 +0.009437 +0.00949 +0.009581 +0.00965 +0.009476 +0.009396 +0.009453 +0.009531 +0.009632 +0.009711 +0.009567 +0.00949 +0.009511 +0.009565 +0.009669 +0.009734 +0.009572 +0.0095 +0.009539 +0.009661 +0.009751 +0.0098 +0.009639 +0.009545 +0.009586 +0.009684 +0.009773 +0.009839 +0.009672 +0.009602 +0.009645 +0.009731 +0.009836 +0.00989 +0.009723 +0.00964 +0.00971 +0.009816 +0.009904 +0.009937 +0.009772 +0.009673 +0.009743 +0.00983 +0.00992 +0.009996 +0.009812 +0.00974 +0.00981 +0.0099 +0.009997 +0.010061 +0.009866 +0.009769 +0.009839 +0.009923 +0.010028 +0.010098 +0.009921 +0.009834 +0.009923 +0.010008 +0.010133 +0.010138 +0.009957 +0.009862 +0.009933 +0.010034 +0.010133 +0.010207 +0.010012 +0.009926 +0.009989 +0.010089 +0.010221 +0.010224 +0.010072 +0.009981 +0.010055 +0.010159 +0.010242 +0.01028 +0.01013 +0.010028 +0.010092 +0.010192 +0.010298 +0.010388 +0.010179 +0.010083 +0.010153 +0.010238 +0.010335 +0.010373 +0.010192 +0.010101 +0.010158 +0.010228 +0.010288 +0.010318 +0.010103 +0.009989 +0.010001 +0.010071 +0.010088 +0.010126 +0.009901 +0.009771 +0.009814 +0.009842 +0.009858 +0.009866 +0.009648 +0.009559 +0.009539 +0.009576 +0.009597 +0.009632 +0.009419 +0.009288 +0.0093 +0.00934 +0.009354 +0.009351 +0.009153 +0.00905 +0.00906 +0.009111 +0.009146 +0.009165 +0.008947 +0.008843 +0.008871 +0.008918 +0.008942 +0.008969 +0.00878 +0.008683 +0.008724 +0.008777 +0.008825 +0.008819 +0.008632 +0.008534 +0.008571 +0.008627 +0.008673 +0.008677 +0.008521 +0.008454 +0.00847 +0.008514 +0.008563 +0.008585 +0.008415 +0.00833 +0.008371 +0.008438 +0.008479 +0.008526 +0.008383 +0.008315 +0.008363 +0.008431 +0.008511 +0.008571 +0.008421 +0.008349 +0.008402 +0.008468 +0.008586 +0.008631 +0.008463 +0.008376 +0.008431 +0.00852 +0.008584 +0.008649 +0.008501 +0.00843 +0.008478 +0.00856 +0.008652 +0.008705 +0.008555 +0.008476 +0.008524 +0.008604 +0.008691 +0.00873 +0.008594 +0.00851 +0.008573 +0.008652 +0.008731 +0.008786 +0.008636 +0.00857 +0.008616 +0.008703 +0.008765 +0.008809 +0.008666 +0.008606 +0.008669 +0.008714 +0.008805 +0.008867 +0.008725 +0.008643 +0.008699 +0.00877 +0.00888 +0.008917 +0.008769 +0.008703 +0.00874 +0.008806 +0.008881 +0.008957 +0.008808 +0.008728 +0.0088 +0.008855 +0.008945 +0.009025 +0.008866 +0.008807 +0.008846 +0.008899 +0.008975 +0.009033 +0.008896 +0.008829 +0.00886 +0.008941 +0.009035 +0.009091 +0.008945 +0.008861 +0.008919 +0.00899 +0.009077 +0.009149 +0.008982 +0.008907 +0.008973 +0.009052 +0.009115 +0.009178 +0.009039 +0.008947 +0.009006 +0.009086 +0.009169 +0.009265 +0.009087 +0.009004 +0.00905 +0.009128 +0.009219 +0.009273 +0.009122 +0.009047 +0.009095 +0.009161 +0.009263 +0.009341 +0.009165 +0.00909 +0.009149 +0.009222 +0.009302 +0.009358 +0.009207 +0.00913 +0.009189 +0.009275 +0.009345 +0.009416 +0.009261 +0.009179 +0.009269 +0.009321 +0.009402 +0.009456 +0.009308 +0.009247 +0.009279 +0.009345 +0.009445 +0.00951 +0.009357 +0.009265 +0.009331 +0.009404 +0.009503 +0.009583 +0.009407 +0.00932 +0.009375 +0.009447 +0.009534 +0.009602 +0.009446 +0.00936 +0.009426 +0.009508 +0.009606 +0.00969 +0.009483 +0.0094 +0.009446 +0.009554 +0.009648 +0.009706 +0.009544 +0.009479 +0.009516 +0.00959 +0.009729 +0.009749 +0.00958 +0.009498 +0.009551 +0.009646 +0.009741 +0.009825 +0.009642 +0.009551 +0.009612 +0.009688 +0.009784 +0.00986 +0.009692 +0.009631 +0.009669 +0.009749 +0.009844 +0.00991 +0.009735 +0.009625 +0.009695 +0.009804 +0.009882 +0.009961 +0.009783 +0.009701 +0.009779 +0.009839 +0.009939 +0.009991 +0.009825 +0.009744 +0.009805 +0.009909 +0.010005 +0.01005 +0.009872 +0.0098 +0.009891 +0.009949 +0.010033 +0.010084 +0.009942 +0.009869 +0.009935 +0.009988 +0.010086 +0.010132 +0.009967 +0.009895 +0.00994 +0.01004 +0.010142 +0.010213 +0.010027 +0.009944 +0.010008 +0.01008 +0.010191 +0.010267 +0.010084 +0.009994 +0.01007 +0.010186 +0.010244 +0.010297 +0.010118 +0.010045 +0.010112 +0.010169 +0.010272 +0.01037 +0.010155 +0.010049 +0.010075 +0.010123 +0.010184 +0.010212 +0.01002 +0.009899 +0.009928 +0.009995 +0.010047 +0.010039 +0.009831 +0.009683 +0.009702 +0.009745 +0.009783 +0.009838 +0.009582 +0.009455 +0.009475 +0.009503 +0.009545 +0.009555 +0.009349 +0.009243 +0.009265 +0.009276 +0.009316 +0.009347 +0.009133 +0.009021 +0.009045 +0.009068 +0.00911 +0.009141 +0.008951 +0.008843 +0.008861 +0.00893 +0.008966 +0.008985 +0.008807 +0.008691 +0.008747 +0.008768 +0.008803 +0.008826 +0.00867 +0.008565 +0.008604 +0.008647 +0.008683 +0.008722 +0.008549 +0.008439 +0.008471 +0.008538 +0.008563 +0.008606 +0.008457 +0.008376 +0.008401 +0.008458 +0.00853 +0.008573 +0.008435 +0.008372 +0.008409 +0.00847 +0.008561 +0.008625 +0.008489 +0.008427 +0.008466 +0.008512 +0.008586 +0.008659 +0.008528 +0.008437 +0.008485 +0.008553 +0.00865 +0.008713 +0.008559 +0.008492 +0.008544 +0.008601 +0.008683 +0.008741 +0.008603 +0.00854 +0.008576 +0.008653 +0.008723 +0.008781 +0.008633 +0.008567 +0.008612 +0.0087 +0.00877 +0.008842 +0.00871 +0.008623 +0.008667 +0.008746 +0.008816 +0.008872 +0.008734 +0.00867 +0.008695 +0.008775 +0.008859 +0.008915 +0.00877 +0.008716 +0.008754 +0.008831 +0.008913 +0.008963 +0.008811 +0.008734 +0.008795 +0.008858 +0.008947 +0.009017 +0.008873 +0.008796 +0.00884 +0.008933 +0.00902 +0.009063 +0.008889 +0.008814 +0.00887 +0.00895 +0.00906 +0.009095 +0.008938 +0.008873 +0.008939 +0.009 +0.009083 +0.009146 +0.008987 +0.008915 +0.008973 +0.009056 +0.009141 +0.009185 +0.009037 +0.008952 +0.009005 +0.009101 +0.009176 +0.009237 +0.009089 +0.009026 +0.009094 +0.009157 +0.009228 +0.009267 +0.009119 +0.009047 +0.009102 +0.009181 +0.009268 +0.009337 +0.009175 +0.009106 +0.009145 +0.009216 +0.009316 +0.009374 +0.009226 +0.009138 +0.009194 +0.009298 +0.009381 +0.009425 +0.009267 +0.009185 +0.009234 +0.009318 +0.009414 +0.009494 +0.009331 +0.009234 +0.009291 +0.009387 +0.009471 +0.009509 +0.009345 +0.009271 +0.009327 +0.009406 +0.0095 +0.009585 +0.009452 +0.009327 +0.009395 +0.009472 +0.009537 +0.009605 +0.009444 +0.009361 +0.009416 +0.009526 +0.009602 +0.009657 +0.009506 +0.009428 +0.009513 +0.009544 +0.009645 +0.009699 +0.009554 +0.009502 +0.00953 +0.009612 +0.009698 +0.009747 +0.009585 +0.009518 +0.009562 +0.009648 +0.009747 +0.009833 +0.009654 +0.009561 +0.009637 +0.009703 +0.009788 +0.009858 +0.009684 +0.009605 +0.009681 +0.009759 +0.009875 +0.009894 +0.009725 +0.009662 +0.009714 +0.009805 +0.009895 +0.009984 +0.009801 +0.009707 +0.009756 +0.00985 +0.009933 +0.010004 +0.00985 +0.009748 +0.009817 +0.009913 +0.010018 +0.010071 +0.009895 +0.009798 +0.009851 +0.009943 +0.010048 +0.010151 +0.009937 +0.009868 +0.009901 +0.009997 +0.010101 +0.01016 +0.009984 +0.009899 +0.009983 +0.010036 +0.010169 +0.010223 +0.010039 +0.009947 +0.010005 +0.010094 +0.010209 +0.010268 +0.010079 +0.010015 +0.010085 +0.010165 +0.010264 +0.010326 +0.010158 +0.010037 +0.010099 +0.010202 +0.010294 +0.010368 +0.010187 +0.010097 +0.010164 +0.010249 +0.010336 +0.010431 +0.010201 +0.010112 +0.010178 +0.010211 +0.010262 +0.010295 +0.010064 +0.009936 +0.009966 +0.010001 +0.010055 +0.010065 +0.009865 +0.009765 +0.009746 +0.009753 +0.009801 +0.009823 +0.009583 +0.009466 +0.009483 +0.00952 +0.009564 +0.009583 +0.009378 +0.009245 +0.009269 +0.009284 +0.00932 +0.00933 +0.009132 +0.009025 +0.00903 +0.00907 +0.009121 +0.009121 +0.008938 +0.008825 +0.008854 +0.008902 +0.008944 +0.008944 +0.008759 +0.008677 +0.008694 +0.008725 +0.008796 +0.008784 +0.008619 +0.008523 +0.008557 +0.008595 +0.00866 +0.008677 +0.008521 +0.00843 +0.00847 +0.008503 +0.008543 +0.008586 +0.008418 +0.008336 +0.008378 +0.008434 +0.008481 +0.008537 +0.008392 +0.008321 +0.008386 +0.008455 +0.008502 +0.008559 +0.008423 +0.008347 +0.008404 +0.00847 +0.008547 +0.008605 +0.008497 +0.008375 +0.008436 +0.008515 +0.008594 +0.008649 +0.008505 +0.008432 +0.008476 +0.008553 +0.008641 +0.008713 +0.008552 +0.008477 +0.008521 +0.008601 +0.008698 +0.008729 +0.008595 +0.008523 +0.008585 +0.008645 +0.008723 +0.008782 +0.008642 +0.008562 +0.008604 +0.008675 +0.008756 +0.008829 +0.008677 +0.008618 +0.00866 +0.008715 +0.008803 +0.008864 +0.008723 +0.008642 +0.008693 +0.008765 +0.00886 +0.008917 +0.008764 +0.008706 +0.008741 +0.00881 +0.008896 +0.00895 +0.008828 +0.008754 +0.008777 +0.008858 +0.00896 +0.008988 +0.008865 +0.00878 +0.008828 +0.008912 +0.008989 +0.009034 +0.008886 +0.008819 +0.008879 +0.008941 +0.009046 +0.009093 +0.008935 +0.008861 +0.008915 +0.008981 +0.009075 +0.009141 +0.008986 +0.00891 +0.008967 +0.009062 +0.009165 +0.009197 +0.009017 +0.008947 +0.008999 +0.009071 +0.00919 +0.00921 +0.00907 +0.009014 +0.009056 +0.009144 +0.009226 +0.009278 +0.009102 +0.009035 +0.009102 +0.009173 +0.009257 +0.00933 +0.009156 +0.009086 +0.009142 +0.009231 +0.009308 +0.009359 +0.009217 +0.009148 +0.009216 +0.009264 +0.009371 +0.00943 +0.009242 +0.00918 +0.009227 +0.009336 +0.009409 +0.009454 +0.009297 +0.009226 +0.009292 +0.009365 +0.009458 +0.009525 +0.009332 +0.009272 +0.009334 +0.009416 +0.009498 +0.009561 +0.009394 +0.009313 +0.009365 +0.009462 +0.009581 +0.00961 +0.009437 +0.009361 +0.009431 +0.009513 +0.009611 +0.009648 +0.009485 +0.009413 +0.009484 +0.009542 +0.009648 +0.00969 +0.009541 +0.00948 +0.00953 +0.009615 +0.009703 +0.009746 +0.009583 +0.009499 +0.009567 +0.009635 +0.009731 +0.009832 +0.009671 +0.009586 +0.00962 +0.009719 +0.009766 +0.009839 +0.009675 +0.009593 +0.009656 +0.009758 +0.009825 +0.009902 +0.009737 +0.009644 +0.009708 +0.009798 +0.00989 +0.009952 +0.009808 +0.009707 +0.009765 +0.009838 +0.009932 +0.010001 +0.009827 +0.009757 +0.009835 +0.009906 +0.010011 +0.010058 +0.009913 +0.009796 +0.009827 +0.009929 +0.010039 +0.010087 +0.009915 +0.009845 +0.009906 +0.010005 +0.010102 +0.010154 +0.00998 +0.009892 +0.009954 +0.010042 +0.010151 +0.010231 +0.010022 +0.009943 +0.01 +0.010125 +0.010197 +0.01025 +0.010079 +0.009991 +0.010071 +0.010185 +0.010247 +0.010315 +0.010104 +0.010032 +0.010105 +0.010184 +0.010292 +0.010364 +0.010181 +0.010091 +0.010163 +0.010239 +0.010342 +0.010423 +0.010227 +0.010133 +0.010217 +0.010306 +0.010379 +0.010371 +0.010165 +0.010059 +0.010077 +0.010127 +0.0102 +0.010252 +0.010008 +0.009865 +0.009886 +0.009911 +0.009955 +0.009946 +0.009731 +0.009604 +0.00961 +0.009672 +0.009688 +0.009697 +0.009483 +0.009348 +0.009368 +0.009401 +0.009443 +0.009462 +0.009267 +0.009162 +0.00914 +0.009172 +0.00921 +0.009229 +0.009038 +0.008904 +0.008928 +0.008973 +0.009043 +0.009044 +0.008848 +0.008742 +0.008749 +0.008807 +0.008846 +0.008875 +0.008694 +0.008589 +0.00863 +0.008665 +0.008722 +0.008756 +0.008587 +0.008471 +0.0085 +0.008562 +0.008614 +0.008666 +0.008475 +0.008381 +0.008394 +0.008457 +0.008523 +0.008558 +0.008394 +0.008308 +0.008355 +0.008417 +0.008498 +0.008547 +0.008407 +0.008321 +0.008374 +0.008452 +0.008537 +0.008587 +0.008437 +0.008374 +0.00841 +0.008482 +0.008566 +0.008634 +0.008477 +0.00841 +0.008459 +0.00854 +0.008638 +0.008694 +0.008523 +0.008452 +0.008495 +0.00858 +0.008651 +0.008712 +0.008568 +0.008486 +0.008544 +0.008608 +0.008692 +0.008762 +0.008603 +0.008548 +0.008575 +0.00866 +0.008747 +0.008794 +0.008654 +0.008595 +0.00863 +0.008697 +0.008788 +0.00885 +0.008694 +0.008619 +0.008674 +0.008753 +0.00885 +0.008882 +0.008732 +0.00867 +0.008723 +0.008792 +0.008885 +0.008923 +0.008778 +0.008702 +0.008753 +0.008821 +0.008924 +0.008975 +0.008837 +0.008762 +0.008816 +0.00887 +0.00896 +0.009028 +0.008871 +0.00879 +0.008848 +0.008921 +0.009012 +0.009077 +0.008931 +0.008849 +0.008924 +0.008965 +0.009044 +0.009094 +0.008945 +0.008883 +0.008921 +0.00902 +0.009116 +0.009144 +0.008995 +0.008921 +0.008989 +0.009051 +0.009135 +0.009212 +0.009046 +0.008978 +0.00903 +0.009116 +0.009176 +0.009251 +0.009095 +0.009021 +0.009065 +0.009146 +0.009232 +0.009326 +0.009172 +0.009058 +0.009105 +0.009192 +0.009266 +0.009336 +0.009177 +0.0091 +0.009161 +0.009238 +0.009329 +0.009392 +0.009224 +0.009142 +0.009202 +0.009289 +0.009369 +0.009444 +0.009287 +0.009196 +0.009271 +0.009336 +0.009421 +0.009477 +0.009318 +0.009239 +0.009317 +0.009386 +0.009471 +0.009551 +0.009374 +0.009297 +0.009342 +0.009406 +0.009505 +0.009582 +0.009409 +0.009332 +0.009409 +0.00947 +0.009581 +0.009647 +0.009465 +0.009383 +0.009435 +0.009505 +0.009607 +0.009668 +0.009521 +0.009436 +0.009494 +0.00957 +0.009673 +0.009745 +0.009545 +0.009473 +0.009509 +0.009641 +0.009701 +0.009778 +0.009616 +0.009539 +0.009565 +0.009654 +0.009761 +0.009811 +0.009648 +0.009567 +0.009626 +0.00973 +0.009829 +0.009885 +0.009705 +0.009607 +0.009674 +0.009763 +0.009854 +0.009922 +0.009771 +0.009679 +0.009723 +0.009799 +0.0099 +0.00997 +0.009797 +0.009732 +0.009764 +0.009859 +0.00997 +0.010038 +0.009859 +0.009749 +0.009816 +0.009906 +0.009998 +0.01007 +0.009896 +0.009818 +0.009894 +0.009984 +0.010079 +0.010112 +0.009941 +0.009866 +0.009944 +0.009997 +0.010086 +0.010161 +0.009999 +0.009911 +0.009968 +0.01006 +0.010197 +0.010212 +0.01004 +0.009961 +0.010027 +0.010125 +0.010208 +0.010266 +0.010093 +0.010022 +0.010062 +0.010148 +0.010273 +0.010334 +0.010168 +0.01008 +0.010148 +0.010225 +0.010312 +0.010316 +0.010159 +0.010081 +0.01011 +0.01019 +0.01025 +0.010291 +0.010075 +0.009956 +0.009973 +0.010018 +0.010056 +0.010062 +0.009865 +0.009733 +0.009755 +0.009786 +0.009819 +0.009825 +0.009602 +0.00948 +0.009509 +0.009544 +0.0096 +0.009566 +0.009351 +0.009244 +0.009237 +0.009276 +0.009304 +0.009312 +0.009125 +0.008992 +0.009023 +0.00906 +0.009105 +0.009116 +0.008918 +0.008805 +0.008832 +0.008864 +0.008918 +0.00893 +0.008753 +0.008653 +0.008711 +0.008747 +0.008791 +0.008818 +0.008648 +0.00857 +0.008587 +0.008626 +0.008665 +0.008694 +0.008537 +0.008467 +0.008496 +0.00853 +0.008586 +0.008622 +0.008463 +0.008379 +0.008428 +0.008479 +0.008543 +0.008591 +0.008464 +0.00838 +0.008438 +0.008491 +0.008567 +0.008636 +0.00848 +0.00841 +0.008453 +0.008529 +0.008615 +0.008696 +0.008545 +0.008479 +0.008507 +0.008588 +0.008644 +0.008703 +0.008566 +0.00849 +0.008539 +0.008639 +0.008702 +0.008752 +0.008608 +0.008537 +0.008595 +0.00868 +0.008732 +0.00881 +0.008648 +0.008588 +0.008632 +0.008722 +0.008795 +0.008831 +0.008701 +0.008626 +0.008674 +0.008748 +0.008838 +0.008902 +0.008782 +0.008664 +0.008719 +0.008785 +0.00887 +0.008938 +0.008771 +0.00871 +0.008761 +0.008839 +0.008936 +0.008976 +0.008835 +0.008751 +0.008801 +0.008877 +0.008967 +0.009034 +0.008863 +0.008808 +0.008857 +0.008922 +0.009015 +0.00909 +0.008917 +0.008838 +0.008892 +0.008983 +0.009079 +0.009106 +0.008971 +0.008877 +0.008934 +0.009029 +0.009105 +0.009165 +0.009005 +0.008914 +0.008973 +0.009053 +0.009159 +0.009199 +0.009049 +0.008985 +0.009041 +0.00912 +0.009198 +0.009265 +0.009095 +0.009003 +0.009072 +0.00914 +0.009233 +0.009302 +0.009161 +0.009084 +0.00912 +0.009198 +0.009276 +0.009334 +0.0092 +0.009094 +0.009157 +0.009241 +0.009353 +0.009405 +0.009231 +0.009155 +0.009199 +0.009283 +0.00938 +0.009446 +0.009269 +0.009198 +0.00928 +0.009355 +0.009437 +0.009503 +0.009316 +0.009241 +0.009294 +0.009382 +0.009505 +0.009531 +0.009375 +0.009279 +0.009345 +0.009441 +0.009527 +0.009604 +0.00941 +0.00933 +0.009388 +0.00947 +0.009591 +0.009633 +0.009459 +0.0094 +0.00945 +0.009524 +0.009619 +0.009682 +0.009513 +0.00943 +0.009504 +0.009583 +0.009674 +0.009727 +0.009578 +0.009496 +0.009539 +0.00961 +0.00971 +0.009771 +0.009607 +0.009523 +0.009584 +0.009702 +0.009764 +0.00982 +0.009646 +0.009574 +0.00963 +0.009751 +0.009823 +0.009881 +0.009709 +0.009625 +0.009676 +0.009766 +0.009864 +0.009918 +0.009759 +0.009692 +0.009776 +0.009829 +0.009925 +0.009948 +0.009793 +0.009726 +0.009763 +0.009861 +0.009964 +0.010023 +0.009871 +0.009805 +0.009837 +0.009919 +0.010004 +0.010074 +0.0099 +0.009814 +0.009876 +0.009951 +0.010065 +0.010135 +0.009947 +0.009864 +0.009937 +0.010032 +0.010135 +0.010182 +0.009997 +0.009951 +0.009971 +0.010052 +0.010158 +0.010234 +0.010047 +0.009966 +0.010027 +0.010117 +0.010239 +0.010304 +0.010124 +0.010004 +0.01007 +0.010166 +0.010263 +0.010331 +0.010158 +0.010079 +0.010142 +0.010238 +0.010374 +0.010383 +0.010188 +0.010096 +0.010166 +0.01029 +0.010385 +0.010407 +0.010234 +0.010131 +0.010193 +0.010273 +0.010323 +0.010363 +0.010147 +0.010008 +0.010056 +0.010119 +0.010129 +0.010148 +0.009924 +0.009796 +0.009817 +0.009861 +0.009887 +0.009914 +0.009693 +0.009596 +0.009585 +0.009605 +0.009633 +0.009625 +0.009447 +0.00931 +0.009321 +0.00937 +0.009412 +0.009407 +0.009208 +0.009091 +0.009114 +0.009149 +0.009198 +0.009187 +0.009 +0.00889 +0.008926 +0.008981 +0.009009 +0.009017 +0.008821 +0.008723 +0.008756 +0.008811 +0.008876 +0.008881 +0.008681 +0.008597 +0.008638 +0.008688 +0.00874 +0.008749 +0.00857 +0.008492 +0.008514 +0.008566 +0.008616 +0.008653 +0.008479 +0.008394 +0.008438 +0.008503 +0.008555 +0.008603 +0.008444 +0.008365 +0.008427 +0.0085 +0.00857 +0.008648 +0.008501 +0.008399 +0.00846 +0.008545 +0.008645 +0.008665 +0.008522 +0.008439 +0.008488 +0.008579 +0.008668 +0.008728 +0.008578 +0.008504 +0.008545 +0.008629 +0.008691 +0.008757 +0.008609 +0.008535 +0.008595 +0.008677 +0.008747 +0.008797 +0.00866 +0.008597 +0.008623 +0.008708 +0.008776 +0.008843 +0.0087 +0.008625 +0.008711 +0.00878 +0.008833 +0.008897 +0.008719 +0.008658 +0.008716 +0.008784 +0.008872 +0.008933 +0.00879 +0.008721 +0.008769 +0.008857 +0.00893 +0.00898 +0.008816 +0.008751 +0.008798 +0.008877 +0.00897 +0.009032 +0.008869 +0.008801 +0.008851 +0.008924 +0.009009 +0.00908 +0.008927 +0.008867 +0.008888 +0.008969 +0.009068 +0.009127 +0.00896 +0.008874 +0.00893 +0.009016 +0.009107 +0.009161 +0.009 +0.008935 +0.008997 +0.009074 +0.009153 +0.009216 +0.00904 +0.008966 +0.009034 +0.009099 +0.009194 +0.009258 +0.009101 +0.009016 +0.009065 +0.009155 +0.009263 +0.009311 +0.009134 +0.00905 +0.009118 +0.009198 +0.009297 +0.009392 +0.009175 +0.009107 +0.009147 +0.009232 +0.009334 +0.009389 +0.009226 +0.009157 +0.009219 +0.009313 +0.00939 +0.009459 +0.009266 +0.009183 +0.009252 +0.009329 +0.009424 +0.009489 +0.009327 +0.009291 +0.009332 +0.009391 +0.009471 +0.009523 +0.009356 +0.009283 +0.009333 +0.009426 +0.009553 +0.009588 +0.00941 +0.00934 +0.009392 +0.009468 +0.009574 +0.009621 +0.009467 +0.009388 +0.009443 +0.009544 +0.009627 +0.00968 +0.00951 +0.00943 +0.0095 +0.009587 +0.009695 +0.009733 +0.00955 +0.009481 +0.009538 +0.009632 +0.009711 +0.00976 +0.009611 +0.009527 +0.00958 +0.009673 +0.009763 +0.009822 +0.009655 +0.00957 +0.00963 +0.009713 +0.009816 +0.009876 +0.009709 +0.009632 +0.009694 +0.009767 +0.009866 +0.009949 +0.009773 +0.009658 +0.009712 +0.009821 +0.009901 +0.00997 +0.009809 +0.009737 +0.009794 +0.009854 +0.009955 +0.010025 +0.009851 +0.009763 +0.009821 +0.009929 +0.01002 +0.010091 +0.009901 +0.009817 +0.00987 +0.00996 +0.010053 +0.010131 +0.009972 +0.009889 +0.009908 +0.009999 +0.010104 +0.010172 +0.010024 +0.009899 +0.009967 +0.010073 +0.010178 +0.010238 +0.010042 +0.009955 +0.010021 +0.010129 +0.010208 +0.010264 +0.010111 +0.010034 +0.010087 +0.010182 +0.010268 +0.01031 +0.01015 +0.010083 +0.010146 +0.010202 +0.010307 +0.010368 +0.010199 +0.010102 +0.01019 +0.010284 +0.01037 +0.010426 +0.010252 +0.010173 +0.01024 +0.010316 +0.010411 +0.010471 +0.010285 +0.010185 +0.010235 +0.010315 +0.010396 +0.010416 +0.010187 +0.010057 +0.010096 +0.01013 +0.010197 +0.010158 +0.009946 +0.009821 +0.009865 +0.009884 +0.009903 +0.009951 +0.009685 +0.009573 +0.009596 +0.009633 +0.009667 +0.009688 +0.009484 +0.009348 +0.009368 +0.009395 +0.009421 +0.009431 +0.009231 +0.009121 +0.009138 +0.009189 +0.00923 +0.009244 +0.009073 +0.008921 +0.008947 +0.008979 +0.009026 +0.009048 +0.008875 +0.008777 +0.008823 +0.00886 +0.008905 +0.008914 +0.00875 +0.008645 +0.008686 +0.008731 +0.008781 +0.008807 +0.008645 +0.008562 +0.008585 +0.008639 +0.008673 +0.008714 +0.008544 +0.008463 +0.008521 +0.008571 +0.008654 +0.008698 +0.008528 +0.008478 +0.008514 +0.008591 +0.008669 +0.0087 +0.008563 +0.008501 +0.008539 +0.008621 +0.008718 +0.008769 +0.00861 +0.008533 +0.008595 +0.00867 +0.00875 +0.008806 +0.008667 +0.008582 +0.008636 +0.008724 +0.008814 +0.008852 +0.008697 +0.008639 +0.008681 +0.008783 +0.008855 +0.008881 +0.008731 +0.008672 +0.00876 +0.008795 +0.008872 +0.008929 +0.008786 +0.008704 +0.008763 +0.008839 +0.008928 +0.008981 +0.008836 +0.008765 +0.008806 +0.008886 +0.008969 +0.009037 +0.008902 +0.0088 +0.008857 +0.008922 +0.009014 +0.009099 +0.00893 +0.00886 +0.008901 +0.008961 +0.009057 +0.009115 +0.00896 +0.00889 +0.008949 +0.009027 +0.009127 +0.009167 +0.009015 +0.008927 +0.008981 +0.00906 +0.009146 +0.009219 +0.009066 +0.008987 +0.009047 +0.009113 +0.009205 +0.009265 +0.009108 +0.009009 +0.009069 +0.009162 +0.009254 +0.009328 +0.009151 +0.009053 +0.009114 +0.009192 +0.009296 +0.009357 +0.009175 +0.009103 +0.009186 +0.009243 +0.009352 +0.009417 +0.009241 +0.009153 +0.009209 +0.009286 +0.009377 +0.00945 +0.009293 +0.009201 +0.009272 +0.009361 +0.009434 +0.009503 +0.009345 +0.009249 +0.009321 +0.009391 +0.009472 +0.009523 +0.009385 +0.009296 +0.009343 +0.009429 +0.009532 +0.009591 +0.009422 +0.009333 +0.009403 +0.009481 +0.009595 +0.00966 +0.00947 +0.009386 +0.009448 +0.009521 +0.009623 +0.009688 +0.00951 +0.009434 +0.009525 +0.009604 +0.00972 +0.00974 +0.009555 +0.009467 +0.009535 +0.009615 +0.009717 +0.009776 +0.009606 +0.009529 +0.009588 +0.009681 +0.009777 +0.009861 +0.009657 +0.009574 +0.009634 +0.00973 +0.009844 +0.00987 +0.009705 +0.009637 +0.009678 +0.009768 +0.009876 +0.009927 +0.009796 +0.009686 +0.009747 +0.009832 +0.009905 +0.009986 +0.009818 +0.009707 +0.009778 +0.009851 +0.009966 +0.010054 +0.009865 +0.009786 +0.009854 +0.009925 +0.010003 +0.010072 +0.009899 +0.009807 +0.009889 +0.009965 +0.010065 +0.010146 +0.009966 +0.009906 +0.009928 +0.010016 +0.01011 +0.01019 +0.010025 +0.00992 +0.009968 +0.0101 +0.010145 +0.010221 +0.010065 +0.009955 +0.010037 +0.010149 +0.010239 +0.010301 +0.010115 +0.01002 +0.010072 +0.010171 +0.010268 +0.010334 +0.010166 +0.010076 +0.010184 +0.010218 +0.010312 +0.010371 +0.010209 +0.010122 +0.010181 +0.010294 +0.010393 +0.01044 +0.010255 +0.010175 +0.010233 +0.010324 +0.010428 +0.010496 +0.010331 +0.010245 +0.010298 +0.010371 +0.010477 +0.010526 +0.010345 +0.010245 +0.010322 +0.010377 +0.010428 +0.010443 +0.010203 +0.010081 +0.010124 +0.010138 +0.010188 +0.010203 +0.009982 +0.009856 +0.009895 +0.009894 +0.009933 +0.009949 +0.009719 +0.009595 +0.009617 +0.009643 +0.009678 +0.009695 +0.009483 +0.009359 +0.00935 +0.009392 +0.009447 +0.00943 +0.009212 +0.009097 +0.009135 +0.009163 +0.009199 +0.009207 +0.009014 +0.008913 +0.008911 +0.008958 +0.009003 +0.009015 +0.008838 +0.008732 +0.008766 +0.008801 +0.008842 +0.008877 +0.008693 +0.008598 +0.008622 +0.008685 +0.008715 +0.008753 +0.008578 +0.008504 +0.00854 +0.008582 +0.008608 +0.008641 +0.008483 +0.008404 +0.008456 +0.008506 +0.008571 +0.008624 +0.008487 +0.00841 +0.00846 +0.008529 +0.008593 +0.00867 +0.008519 +0.008455 +0.008503 +0.008564 +0.008648 +0.008724 +0.008569 +0.00849 +0.00855 +0.008615 +0.00869 +0.008746 +0.008604 +0.008541 +0.008596 +0.008653 +0.008746 +0.00878 +0.008641 +0.008574 +0.008621 +0.008708 +0.008785 +0.008851 +0.008677 +0.00861 +0.008671 +0.008733 +0.008832 +0.008877 +0.008736 +0.008654 +0.008709 +0.008793 +0.008869 +0.008919 +0.008782 +0.00871 +0.008753 +0.008851 +0.008929 +0.008983 +0.008829 +0.008767 +0.008789 +0.008857 +0.008948 +0.009013 +0.008862 +0.008799 +0.008849 +0.008931 +0.009004 +0.00907 +0.008903 +0.008832 +0.008875 +0.008956 +0.009054 +0.009102 +0.008955 +0.008883 +0.008941 +0.009003 +0.009081 +0.009166 +0.008996 +0.008929 +0.008979 +0.009052 +0.009171 +0.009201 +0.009043 +0.008994 +0.009013 +0.009088 +0.009175 +0.009239 +0.009085 +0.009016 +0.009065 +0.009136 +0.009251 +0.009306 +0.009141 +0.00906 +0.009113 +0.009175 +0.009272 +0.009343 +0.009176 +0.009102 +0.009164 +0.009233 +0.00933 +0.009392 +0.009233 +0.009167 +0.009207 +0.009274 +0.00936 +0.009432 +0.009296 +0.009216 +0.009244 +0.00932 +0.009424 +0.009467 +0.009318 +0.009238 +0.009281 +0.009363 +0.009484 +0.009551 +0.009381 +0.009293 +0.009361 +0.009409 +0.009507 +0.009581 +0.009403 +0.009334 +0.009386 +0.00947 +0.009599 +0.009624 +0.009449 +0.009369 +0.009427 +0.009524 +0.009605 +0.0097 +0.009509 +0.009432 +0.009469 +0.00956 +0.009664 +0.00972 +0.009557 +0.00948 +0.009533 +0.009613 +0.009726 +0.00978 +0.009609 +0.009521 +0.009562 +0.009666 +0.009749 +0.009822 +0.009684 +0.009562 +0.009629 +0.009721 +0.009813 +0.009881 +0.009698 +0.009596 +0.009671 +0.009775 +0.00984 +0.009921 +0.009743 +0.009642 +0.009722 +0.009813 +0.009909 +0.009969 +0.009802 +0.009718 +0.009767 +0.009882 +0.009969 +0.010023 +0.00984 +0.009776 +0.009847 +0.009908 +0.009989 +0.01005 +0.009905 +0.009825 +0.009876 +0.009964 +0.010063 +0.010098 +0.009941 +0.009855 +0.009906 +0.010005 +0.01011 +0.010172 +0.009999 +0.009919 +0.009964 +0.010049 +0.01017 +0.010234 +0.010049 +0.009977 +0.010048 +0.010135 +0.010193 +0.010255 +0.010107 +0.010004 +0.010052 +0.010148 +0.010274 +0.010343 +0.010157 +0.010067 +0.010108 +0.01022 +0.010306 +0.010358 +0.010194 +0.010108 +0.010197 +0.010278 +0.010379 +0.01045 +0.010238 +0.010146 +0.010221 +0.010348 +0.010421 +0.010475 +0.010276 +0.010206 +0.010273 +0.010375 +0.010432 +0.010493 +0.010293 +0.010182 +0.010239 +0.010276 +0.010323 +0.010345 +0.010125 +0.009983 +0.010005 +0.010051 +0.010106 +0.010104 +0.009891 +0.00979 +0.009783 +0.009818 +0.009867 +0.00988 +0.009618 +0.009494 +0.009512 +0.009554 +0.0096 +0.009605 +0.009418 +0.009276 +0.009279 +0.009316 +0.009366 +0.009364 +0.009177 +0.009056 +0.009073 +0.009106 +0.009155 +0.009163 +0.008965 +0.008862 +0.00888 +0.008918 +0.008969 +0.00898 +0.008816 +0.008729 +0.00876 +0.008771 +0.008818 +0.008837 +0.008661 +0.008571 +0.008587 +0.008636 +0.008686 +0.00873 +0.00856 +0.008453 +0.008489 +0.008531 +0.008589 +0.00863 +0.008476 +0.008383 +0.008423 +0.008488 +0.008563 +0.008625 +0.008469 +0.008395 +0.008438 +0.008522 +0.008586 +0.008656 +0.008509 +0.008449 +0.008481 +0.008565 +0.008641 +0.00869 +0.008556 +0.008477 +0.008516 +0.008591 +0.008679 +0.008728 +0.008585 +0.008525 +0.008574 +0.008651 +0.008731 +0.008792 +0.008638 +0.008567 +0.008605 +0.008687 +0.008768 +0.008809 +0.008688 +0.008609 +0.00866 +0.008731 +0.008811 +0.008873 +0.008733 +0.008634 +0.008691 +0.008759 +0.008875 +0.008908 +0.008763 +0.008699 +0.008748 +0.008821 +0.008912 +0.008944 +0.008806 +0.008726 +0.008784 +0.008856 +0.008951 +0.009008 +0.008847 +0.008784 +0.008821 +0.008899 +0.008989 +0.009052 +0.008908 +0.008818 +0.008875 +0.00897 +0.00908 +0.009099 +0.008932 +0.008853 +0.00891 +0.008995 +0.00908 +0.009148 +0.008975 +0.008913 +0.008973 +0.00905 +0.009147 +0.009194 +0.009017 +0.008948 +0.009001 +0.009072 +0.009166 +0.009252 +0.009073 +0.009 +0.00906 +0.009136 +0.009231 +0.009273 +0.009129 +0.009057 +0.009113 +0.009174 +0.009265 +0.009335 +0.009159 +0.009093 +0.009143 +0.009212 +0.009345 +0.00936 +0.009204 +0.009134 +0.009195 +0.009286 +0.00937 +0.009438 +0.009256 +0.009176 +0.009242 +0.009302 +0.009398 +0.009462 +0.009316 +0.009245 +0.009291 +0.009377 +0.009485 +0.009545 +0.009339 +0.009252 +0.00931 +0.009399 +0.009494 +0.009556 +0.009394 +0.009322 +0.009373 +0.009463 +0.009561 +0.0096 +0.00945 +0.009367 +0.009426 +0.009506 +0.009615 +0.009668 +0.009503 +0.009401 +0.009471 +0.009546 +0.00964 +0.009718 +0.009549 +0.009485 +0.009501 +0.009584 +0.009717 +0.009741 +0.009588 +0.009507 +0.009568 +0.009655 +0.009754 +0.009809 +0.009634 +0.009551 +0.009605 +0.009694 +0.009789 +0.009847 +0.00969 +0.009619 +0.009678 +0.009756 +0.009862 +0.009893 +0.009723 +0.009654 +0.009718 +0.009823 +0.009878 +0.009946 +0.009789 +0.009708 +0.009784 +0.009842 +0.009916 +0.009994 +0.009823 +0.009741 +0.009817 +0.009899 +0.009983 +0.010063 +0.009876 +0.009797 +0.009858 +0.009935 +0.010037 +0.010117 +0.009952 +0.009866 +0.00992 +0.010003 +0.010118 +0.010136 +0.009961 +0.009884 +0.009948 +0.010042 +0.010159 +0.010247 +0.010037 +0.009944 +0.009992 +0.010086 +0.010184 +0.010252 +0.010086 +0.010003 +0.010047 +0.010149 +0.010247 +0.010301 +0.010137 +0.010038 +0.010105 +0.010204 +0.010328 +0.010411 +0.010172 +0.010078 +0.010139 +0.010243 +0.010342 +0.010405 +0.010233 +0.010175 +0.010218 +0.010311 +0.010408 +0.010446 +0.010277 +0.010176 +0.010242 +0.010322 +0.010418 +0.01042 +0.010204 +0.010087 +0.010119 +0.010153 +0.010228 +0.010269 +0.009998 +0.009878 +0.009871 +0.009915 +0.009947 +0.009955 +0.009724 +0.009599 +0.009597 +0.00964 +0.009702 +0.00969 +0.009476 +0.009357 +0.009376 +0.009394 +0.009435 +0.009456 +0.009244 +0.009113 +0.009143 +0.009166 +0.009224 +0.009254 +0.009052 +0.008951 +0.008958 +0.00898 +0.009014 +0.009048 +0.008877 +0.008749 +0.008774 +0.008825 +0.008886 +0.008908 +0.00873 +0.008624 +0.008658 +0.008699 +0.008758 +0.008796 +0.008611 +0.008507 +0.008545 +0.008608 +0.008643 +0.008672 +0.008503 +0.008405 +0.008455 +0.008503 +0.008572 +0.008621 +0.008483 +0.008386 +0.008432 +0.008491 +0.008576 +0.008627 +0.008491 +0.008432 +0.008453 +0.008536 +0.008607 +0.008666 +0.00854 +0.00847 +0.00851 +0.008584 +0.008669 +0.008731 +0.008558 +0.008493 +0.008545 +0.008616 +0.008699 +0.008774 +0.008616 +0.008542 +0.008587 +0.008669 +0.008754 +0.008832 +0.008673 +0.008576 +0.008622 +0.008701 +0.008798 +0.008859 +0.008695 +0.008618 +0.008706 +0.008743 +0.008831 +0.008887 +0.008747 +0.008661 +0.008723 +0.008818 +0.008892 +0.008942 +0.008791 +0.008706 +0.008754 +0.008837 +0.008926 +0.008982 +0.008829 +0.008758 +0.008813 +0.008901 +0.008992 +0.009029 +0.008863 +0.008792 +0.008845 +0.008933 +0.009003 +0.009079 +0.008938 +0.008854 +0.008905 +0.00898 +0.009055 +0.009106 +0.008961 +0.008895 +0.008944 +0.009018 +0.009098 +0.009172 +0.009018 +0.00893 +0.008987 +0.009059 +0.009142 +0.009218 +0.009064 +0.009005 +0.009059 +0.009121 +0.009199 +0.009259 +0.009092 +0.009015 +0.009062 +0.009152 +0.009239 +0.009307 +0.009151 +0.009088 +0.009142 +0.009216 +0.00929 +0.00935 +0.009192 +0.009106 +0.009165 +0.009241 +0.009328 +0.009418 +0.009266 +0.009171 +0.009226 +0.009304 +0.009393 +0.009457 +0.009268 +0.00919 +0.00925 +0.009349 +0.00945 +0.009483 +0.009326 +0.009256 +0.009307 +0.009389 +0.009485 +0.009543 +0.009384 +0.009314 +0.009366 +0.009435 +0.009519 +0.009592 +0.00942 +0.009343 +0.009412 +0.009481 +0.009584 +0.009661 +0.00949 +0.009428 +0.009459 +0.009525 +0.009599 +0.009675 +0.009522 +0.009433 +0.009519 +0.009597 +0.00966 +0.009729 +0.009565 +0.009485 +0.009543 +0.009618 +0.009727 +0.009796 +0.009631 +0.009558 +0.0096 +0.009665 +0.009774 +0.009833 +0.009667 +0.009579 +0.009631 +0.009757 +0.009842 +0.009893 +0.009724 +0.009634 +0.009703 +0.009755 +0.009862 +0.009938 +0.009769 +0.009675 +0.009748 +0.009817 +0.009915 +0.009983 +0.009818 +0.009728 +0.009779 +0.009884 +0.009968 +0.01005 +0.009879 +0.009781 +0.00982 +0.009929 +0.01004 +0.010096 +0.009915 +0.00982 +0.009881 +0.009993 +0.010083 +0.010134 +0.009961 +0.009855 +0.009924 +0.010017 +0.01013 +0.010184 +0.010005 +0.009937 +0.009985 +0.010067 +0.010176 +0.010228 +0.010069 +0.009978 +0.010032 +0.010129 +0.010258 +0.010312 +0.010116 +0.010011 +0.010068 +0.010193 +0.010279 +0.010316 +0.010164 +0.010096 +0.010151 +0.010232 +0.010335 +0.010372 +0.010207 +0.010121 +0.010187 +0.010269 +0.010374 +0.010441 +0.010264 +0.01018 +0.010236 +0.010314 +0.010408 +0.010481 +0.010298 +0.010156 +0.010176 +0.010214 +0.010283 +0.010307 +0.010115 +0.009968 +0.009986 +0.010041 +0.010116 +0.010099 +0.009886 +0.009743 +0.009744 +0.009782 +0.009813 +0.009832 +0.009618 +0.009493 +0.009512 +0.009542 +0.009585 +0.009577 +0.009371 +0.009238 +0.00925 +0.009315 +0.009322 +0.009344 +0.009143 +0.009025 +0.009042 +0.00907 +0.009115 +0.009143 +0.008945 +0.008837 +0.008853 +0.008895 +0.008959 +0.008969 +0.008789 +0.008669 +0.008719 +0.008734 +0.008798 +0.008818 +0.008653 +0.008558 +0.008589 +0.008635 +0.008687 +0.008727 +0.008555 +0.008466 +0.008477 +0.008519 +0.008571 +0.008617 +0.008463 +0.008384 +0.00841 +0.00847 +0.008543 +0.008599 +0.008451 +0.008378 +0.008426 +0.008499 +0.00857 +0.008629 +0.008496 +0.00843 +0.008472 +0.00855 +0.00861 +0.008661 +0.008523 +0.008463 +0.008507 +0.008574 +0.008663 +0.008724 +0.0086 +0.008507 +0.00857 +0.008616 +0.008685 +0.008752 +0.008617 +0.008553 +0.008596 +0.008661 +0.008744 +0.008816 +0.00867 +0.008595 +0.008643 +0.008705 +0.008767 +0.008845 +0.008694 +0.008623 +0.008685 +0.008764 +0.008829 +0.008898 +0.008747 +0.008668 +0.008719 +0.008796 +0.00888 +0.008965 +0.008788 +0.008712 +0.00877 +0.008859 +0.008928 +0.008972 +0.008827 +0.008754 +0.00881 +0.008877 +0.008967 +0.009028 +0.008888 +0.008826 +0.008857 +0.00894 +0.009015 +0.009058 +0.008916 +0.008842 +0.008892 +0.008972 +0.009068 +0.009139 +0.008956 +0.008889 +0.008942 +0.009048 +0.009102 +0.009152 +0.008997 +0.008928 +0.008989 +0.009072 +0.009162 +0.009225 +0.00907 +0.00897 +0.009028 +0.009113 +0.009192 +0.009257 +0.009098 +0.009047 +0.009079 +0.00916 +0.00926 +0.009299 +0.009148 +0.009068 +0.009117 +0.009197 +0.009279 +0.009364 +0.009219 +0.009125 +0.009148 +0.009233 +0.009331 +0.009397 +0.009242 +0.009155 +0.009225 +0.0093 +0.009402 +0.00945 +0.009288 +0.009186 +0.00925 +0.009343 +0.009431 +0.009494 +0.009354 +0.00925 +0.009323 +0.009392 +0.009501 +0.009537 +0.009361 +0.009283 +0.009351 +0.009469 +0.009542 +0.009574 +0.009428 +0.009335 +0.009401 +0.009469 +0.009572 +0.009635 +0.009473 +0.009402 +0.009443 +0.009539 +0.009632 +0.009698 +0.009524 +0.00943 +0.009488 +0.009575 +0.009668 +0.009733 +0.009568 +0.009496 +0.009554 +0.009641 +0.00974 +0.009806 +0.009616 +0.009518 +0.009597 +0.009663 +0.009764 +0.009835 +0.009657 +0.009579 +0.009642 +0.009721 +0.009813 +0.009883 +0.009704 +0.009627 +0.009695 +0.009789 +0.009878 +0.009931 +0.009761 +0.009675 +0.009745 +0.009818 +0.009905 +0.00999 +0.009817 +0.00978 +0.009792 +0.009878 +0.00996 +0.010009 +0.009857 +0.009773 +0.009827 +0.00993 +0.010037 +0.010065 +0.009911 +0.009817 +0.00989 +0.00999 +0.010051 +0.010125 +0.009959 +0.009888 +0.009947 +0.010041 +0.0101 +0.010176 +0.010004 +0.009924 +0.010015 +0.010067 +0.010166 +0.010243 +0.010069 +0.009988 +0.010043 +0.010114 +0.010211 +0.010275 +0.010095 +0.010022 +0.010083 +0.010166 +0.01028 +0.01033 +0.010156 +0.010071 +0.010126 +0.010221 +0.010346 +0.010402 +0.010217 +0.010116 +0.010192 +0.010307 +0.010363 +0.010421 +0.010238 +0.010172 +0.01024 +0.010327 +0.010442 +0.010512 +0.010253 +0.010177 +0.010219 +0.010277 +0.010333 +0.010337 +0.010131 +0.010019 +0.010027 +0.010082 +0.010117 +0.010101 +0.009876 +0.009733 +0.009763 +0.009801 +0.009837 +0.009892 +0.009619 +0.009497 +0.009512 +0.009541 +0.00958 +0.00957 +0.009366 +0.009255 +0.009276 +0.009282 +0.00933 +0.009334 +0.009111 +0.009001 +0.009027 +0.009048 +0.009085 +0.0091 +0.008911 +0.008814 +0.00883 +0.008878 +0.008903 +0.008917 +0.00873 +0.008637 +0.008687 +0.008716 +0.008762 +0.008758 +0.008589 +0.008509 +0.00855 +0.008597 +0.008645 +0.008675 +0.008502 +0.008403 +0.008437 +0.008499 +0.008537 +0.008575 +0.00842 +0.008346 +0.008378 +0.008437 +0.008503 +0.008555 +0.008413 +0.008337 +0.008393 +0.008456 +0.008531 +0.008597 +0.008454 +0.008394 +0.008454 +0.008485 +0.00857 +0.008631 +0.008503 +0.008413 +0.008467 +0.008527 +0.008618 +0.00868 +0.008534 +0.008464 +0.008516 +0.008584 +0.008657 +0.008714 +0.008577 +0.008507 +0.008554 +0.008622 +0.008704 +0.008778 +0.008624 +0.008554 +0.008598 +0.008685 +0.008732 +0.008803 +0.008663 +0.008601 +0.00863 +0.008705 +0.008786 +0.008843 +0.008697 +0.008643 +0.008676 +0.008746 +0.008837 +0.008885 +0.008743 +0.008677 +0.008731 +0.008808 +0.008888 +0.008958 +0.008779 +0.00871 +0.008766 +0.008835 +0.008927 +0.00898 +0.008834 +0.008765 +0.008821 +0.008898 +0.008984 +0.009059 +0.00887 +0.008795 +0.008845 +0.00892 +0.009008 +0.009075 +0.008918 +0.008839 +0.008929 +0.008961 +0.009056 +0.00912 +0.00896 +0.008883 +0.008938 +0.009024 +0.009115 +0.009178 +0.009016 +0.008928 +0.008992 +0.009056 +0.009159 +0.009209 +0.009052 +0.008978 +0.009044 +0.009153 +0.009223 +0.009251 +0.009089 +0.009011 +0.009071 +0.009157 +0.009237 +0.009301 +0.009149 +0.009077 +0.009111 +0.009202 +0.009284 +0.009347 +0.009196 +0.009114 +0.009164 +0.009247 +0.009346 +0.009422 +0.009244 +0.009173 +0.009195 +0.009287 +0.009383 +0.009449 +0.009319 +0.009211 +0.009249 +0.009377 +0.009428 +0.009495 +0.009324 +0.009244 +0.009296 +0.009383 +0.009474 +0.009538 +0.009385 +0.009307 +0.009342 +0.009436 +0.009518 +0.009582 +0.009427 +0.009334 +0.009407 +0.009487 +0.009592 +0.009655 +0.009486 +0.009389 +0.009461 +0.009546 +0.00961 +0.009672 +0.009494 +0.009434 +0.009523 +0.009592 +0.009682 +0.009747 +0.00956 +0.00947 +0.00954 +0.009623 +0.009712 +0.009794 +0.00961 +0.00953 +0.009588 +0.009678 +0.009778 +0.009825 +0.009663 +0.009576 +0.009644 +0.00973 +0.009861 +0.009906 +0.009695 +0.009612 +0.009676 +0.00977 +0.009867 +0.009931 +0.009769 +0.009685 +0.009753 +0.009833 +0.00993 +0.009958 +0.009802 +0.009725 +0.009781 +0.009861 +0.009962 +0.010047 +0.009874 +0.009794 +0.009841 +0.009923 +0.010005 +0.010082 +0.009942 +0.009805 +0.009876 +0.009961 +0.010059 +0.010124 +0.009948 +0.009874 +0.009942 +0.010028 +0.010113 +0.010191 +0.010018 +0.009931 +0.009986 +0.010063 +0.010165 +0.010241 +0.010068 +0.009958 +0.010031 +0.010131 +0.010236 +0.010304 +0.010127 +0.010039 +0.01007 +0.010175 +0.010253 +0.010322 +0.010151 +0.010058 +0.010124 +0.010226 +0.010322 +0.010392 +0.010208 +0.010103 +0.010168 +0.010267 +0.010357 +0.010367 +0.010166 +0.010044 +0.010064 +0.010114 +0.010188 +0.010204 +0.009974 +0.009847 +0.009904 +0.009938 +0.009949 +0.009971 +0.00972 +0.009595 +0.009609 +0.009663 +0.009711 +0.009717 +0.009517 +0.009384 +0.009398 +0.009438 +0.009459 +0.009481 +0.009261 +0.009138 +0.009167 +0.009204 +0.009239 +0.009263 +0.009062 +0.008934 +0.008957 +0.009012 +0.009059 +0.009103 +0.008877 +0.008758 +0.008799 +0.008858 +0.008912 +0.008917 +0.008738 +0.008635 +0.008669 +0.008721 +0.008772 +0.008814 +0.008627 +0.008538 +0.00857 +0.008642 +0.008664 +0.00869 +0.008528 +0.008436 +0.008463 +0.008526 +0.008596 +0.008623 +0.008481 +0.008397 +0.008443 +0.008522 +0.008607 +0.008629 +0.008466 +0.008404 +0.00846 +0.008525 +0.008608 +0.008669 +0.008548 +0.008453 +0.008493 +0.008572 +0.008659 +0.008722 +0.008571 +0.00849 +0.008555 +0.00861 +0.008715 +0.008771 +0.008632 +0.008538 +0.008579 +0.008666 +0.008742 +0.008802 +0.008657 +0.008587 +0.008643 +0.008743 +0.008807 +0.008833 +0.008694 +0.008619 +0.00867 +0.008746 +0.008821 +0.008885 +0.008743 +0.008693 +0.008709 +0.008789 +0.008866 +0.008934 +0.008781 +0.008714 +0.008757 +0.008831 +0.008931 +0.008988 +0.008839 +0.008765 +0.008816 +0.00887 +0.008961 +0.009019 +0.008877 +0.008823 +0.008851 +0.008938 +0.009 +0.009076 +0.008926 +0.008844 +0.008895 +0.008969 +0.009052 +0.009113 +0.00895 +0.00889 +0.00893 +0.009025 +0.009111 +0.009156 +0.008999 +0.008924 +0.008984 +0.009054 +0.009146 +0.009206 +0.009056 +0.008979 +0.009039 +0.009126 +0.009217 +0.009267 +0.009081 +0.009005 +0.009065 +0.009169 +0.009237 +0.009287 +0.00915 +0.009076 +0.009122 +0.009203 +0.009293 +0.009335 +0.009175 +0.009107 +0.009166 +0.009239 +0.009345 +0.009392 +0.00924 +0.009149 +0.009207 +0.009293 +0.009382 +0.009441 +0.009282 +0.009206 +0.0093 +0.009344 +0.009424 +0.009472 +0.009314 +0.009245 +0.009312 +0.009376 +0.009471 +0.009529 +0.009375 +0.009295 +0.009365 +0.009441 +0.009511 +0.009586 +0.009409 +0.009335 +0.009403 +0.009481 +0.009564 +0.009634 +0.009476 +0.00938 +0.009437 +0.009533 +0.009639 +0.009691 +0.009503 +0.009418 +0.009507 +0.009583 +0.009654 +0.009726 +0.009557 +0.009497 +0.00952 +0.009609 +0.009721 +0.009781 +0.009623 +0.009547 +0.009594 +0.009652 +0.009766 +0.00983 +0.009648 +0.009569 +0.009626 +0.009715 +0.009829 +0.009894 +0.009748 +0.009658 +0.009672 +0.009734 +0.00984 +0.009919 +0.009741 +0.009683 +0.009735 +0.009801 +0.009909 +0.009961 +0.009802 +0.009717 +0.009767 +0.009872 +0.009973 +0.010041 +0.009886 +0.009767 +0.009814 +0.009915 +0.01001 +0.010067 +0.009904 +0.009821 +0.009926 +0.009972 +0.010062 +0.010141 +0.009946 +0.009843 +0.009911 +0.010004 +0.010108 +0.010185 +0.009988 +0.009916 +0.009984 +0.010058 +0.010171 +0.010214 +0.01005 +0.009981 +0.010023 +0.01014 +0.010237 +0.010269 +0.010088 +0.010012 +0.010086 +0.010191 +0.010251 +0.010305 +0.010165 +0.010095 +0.010131 +0.010217 +0.010317 +0.010356 +0.010188 +0.010108 +0.01017 +0.010279 +0.01037 +0.010428 +0.010248 +0.010168 +0.01023 +0.0103 +0.010402 +0.010466 +0.010271 +0.010163 +0.010179 +0.010239 +0.01026 +0.010263 +0.010053 +0.009938 +0.009939 +0.010003 +0.010079 +0.010033 +0.009822 +0.009675 +0.009679 +0.00972 +0.009764 +0.009778 +0.009566 +0.009439 +0.009446 +0.009489 +0.009535 +0.009528 +0.009336 +0.009211 +0.009229 +0.00926 +0.009299 +0.009349 +0.009146 +0.009013 +0.009009 +0.009044 +0.009094 +0.009121 +0.008939 +0.008824 +0.008846 +0.008925 +0.008935 +0.008969 +0.008781 +0.008681 +0.008694 +0.008741 +0.008804 +0.008828 +0.008662 +0.008559 +0.008584 +0.008623 +0.008679 +0.00872 +0.008551 +0.008451 +0.008476 +0.008539 +0.008593 +0.008667 +0.008474 +0.008397 +0.008427 +0.008476 +0.008553 +0.008612 +0.008471 +0.008392 +0.008454 +0.00852 +0.008612 +0.008653 +0.008524 +0.008441 +0.008497 +0.008566 +0.008631 +0.008693 +0.00856 +0.008484 +0.008536 +0.008598 +0.008675 +0.008759 +0.0086 +0.008531 +0.008583 +0.008659 +0.008746 +0.00878 +0.008627 +0.008585 +0.008619 +0.008684 +0.008779 +0.008816 +0.008675 +0.008612 +0.008652 +0.008727 +0.008802 +0.008886 +0.008723 +0.008651 +0.00871 +0.008774 +0.008874 +0.00894 +0.008783 +0.008688 +0.008742 +0.00882 +0.008901 +0.008958 +0.008814 +0.008745 +0.008803 +0.008904 +0.008954 +0.009006 +0.00886 +0.008772 +0.008837 +0.008901 +0.008986 +0.009058 +0.008901 +0.008817 +0.008877 +0.008956 +0.009037 +0.009102 +0.008949 +0.008873 +0.008921 +0.009001 +0.009097 +0.009157 +0.009003 +0.008922 +0.008958 +0.009041 +0.009136 +0.009197 +0.009051 +0.008965 +0.009003 +0.009086 +0.009187 +0.009246 +0.009091 +0.009001 +0.009076 +0.009132 +0.00921 +0.009277 +0.009116 +0.009042 +0.00909 +0.009179 +0.009282 +0.009332 +0.009186 +0.009098 +0.009152 +0.00923 +0.009312 +0.009385 +0.009218 +0.009153 +0.009198 +0.009282 +0.009392 +0.00941 +0.009253 +0.009182 +0.009234 +0.009327 +0.00941 +0.009483 +0.009331 +0.009254 +0.009268 +0.009364 +0.009451 +0.009515 +0.009353 +0.009284 +0.009331 +0.009437 +0.009526 +0.009575 +0.009401 +0.009327 +0.00937 +0.009462 +0.009549 +0.009618 +0.00947 +0.009386 +0.009438 +0.009488 +0.009589 +0.009658 +0.009493 +0.009409 +0.009478 +0.009566 +0.00966 +0.009731 +0.009555 +0.009447 +0.009514 +0.009612 +0.009697 +0.00977 +0.009591 +0.009517 +0.009577 +0.00967 +0.009765 +0.009812 +0.00963 +0.009558 +0.009635 +0.009727 +0.009784 +0.009843 +0.009707 +0.009602 +0.009655 +0.009741 +0.009845 +0.009909 +0.009733 +0.009656 +0.009722 +0.009809 +0.009912 +0.00997 +0.00978 +0.00971 +0.009764 +0.009847 +0.009941 +0.010015 +0.009843 +0.009772 +0.009824 +0.009919 +0.010032 +0.010041 +0.009878 +0.009792 +0.009852 +0.009965 +0.010055 +0.010123 +0.009944 +0.009854 +0.009914 +0.009997 +0.010083 +0.01014 +0.009996 +0.00991 +0.00998 +0.010048 +0.010141 +0.010212 +0.01004 +0.009947 +0.010002 +0.010107 +0.010211 +0.010322 +0.010092 +0.01 +0.010039 +0.010142 +0.010252 +0.010315 +0.010155 +0.010047 +0.010095 +0.010198 +0.01033 +0.010374 +0.010199 +0.010091 +0.010157 +0.01025 +0.010357 +0.010409 +0.010224 +0.010175 +0.010207 +0.010294 +0.010398 +0.01045 +0.010222 +0.010075 +0.0101 +0.010153 +0.010243 +0.010265 +0.010033 +0.009925 +0.009938 +0.009983 +0.010014 +0.01002 +0.009811 +0.009681 +0.009693 +0.009735 +0.009746 +0.009771 +0.009553 +0.009415 +0.009434 +0.009457 +0.009504 +0.009544 +0.009335 +0.009229 +0.009202 +0.009248 +0.009273 +0.00928 +0.009096 +0.008968 +0.008987 +0.009033 +0.009079 +0.009107 +0.008919 +0.00881 +0.008832 +0.008874 +0.008925 +0.008962 +0.008782 +0.008675 +0.008709 +0.008745 +0.00879 +0.00883 +0.008654 +0.008548 +0.008569 +0.008626 +0.008677 +0.008738 +0.008541 +0.008451 +0.008471 +0.008525 +0.008613 +0.008607 +0.008452 +0.008369 +0.008414 +0.00847 +0.008548 +0.008605 +0.008486 +0.008389 +0.00845 +0.008521 +0.008588 +0.008653 +0.008492 +0.008427 +0.008471 +0.008546 +0.008626 +0.008695 +0.008545 +0.008485 +0.008518 +0.008594 +0.008695 +0.008739 +0.008582 +0.008505 +0.008557 +0.008635 +0.008721 +0.008803 +0.00867 +0.008561 +0.008605 +0.008664 +0.008753 +0.008821 +0.008672 +0.008595 +0.008645 +0.008723 +0.008817 +0.008878 +0.00873 +0.008651 +0.008698 +0.00875 +0.00885 +0.008903 +0.008756 +0.008691 +0.00874 +0.008835 +0.008896 +0.008948 +0.008798 +0.008725 +0.008783 +0.008848 +0.008935 +0.009004 +0.008864 +0.008783 +0.008836 +0.008905 +0.008984 +0.009037 +0.008889 +0.008812 +0.008867 +0.008936 +0.009032 +0.009093 +0.008947 +0.00887 +0.008932 +0.009001 +0.009065 +0.009135 +0.008978 +0.008929 +0.008957 +0.009042 +0.0091 +0.009185 +0.009036 +0.008949 +0.009011 +0.009087 +0.009156 +0.009216 +0.009066 +0.008996 +0.009042 +0.009131 +0.009203 +0.009278 +0.009116 +0.009039 +0.009096 +0.009165 +0.009258 +0.009329 +0.00916 +0.009091 +0.009146 +0.00923 +0.009321 +0.009378 +0.009189 +0.009117 +0.0092 +0.009255 +0.00935 +0.009416 +0.009264 +0.009188 +0.00923 +0.009304 +0.009396 +0.009453 +0.009294 +0.009212 +0.009279 +0.009355 +0.009457 +0.009505 +0.009336 +0.00927 +0.009325 +0.0094 +0.009493 +0.009555 +0.009399 +0.00935 +0.009392 +0.00945 +0.009537 +0.009595 +0.00944 +0.009365 +0.009405 +0.009487 +0.009591 +0.009659 +0.009502 +0.009422 +0.00947 +0.009536 +0.009658 +0.009691 +0.009527 +0.009448 +0.009498 +0.009601 +0.009701 +0.009766 +0.009583 +0.00951 +0.009554 +0.009663 +0.009748 +0.009783 +0.009615 +0.009551 +0.009598 +0.009696 +0.00978 +0.009847 +0.009678 +0.009587 +0.009661 +0.009739 +0.009838 +0.009911 +0.009745 +0.009628 +0.009719 +0.009794 +0.009863 +0.009947 +0.009779 +0.009685 +0.00976 +0.009849 +0.009948 +0.010055 +0.009805 +0.009723 +0.009782 +0.009874 +0.009988 +0.010033 +0.009867 +0.009794 +0.00985 +0.009938 +0.01004 +0.010095 +0.00992 +0.009848 +0.009893 +0.010011 +0.010098 +0.01014 +0.00997 +0.009885 +0.009946 +0.010032 +0.010129 +0.010209 +0.010071 +0.009945 +0.010011 +0.010095 +0.010165 +0.010244 +0.010059 +0.009982 +0.010053 +0.010129 +0.010262 +0.010325 +0.010142 +0.010033 +0.010095 +0.01017 +0.010291 +0.010355 +0.010165 +0.0101 +0.010158 +0.010219 +0.010339 +0.010415 +0.01024 +0.010171 +0.010189 +0.010273 +0.010412 +0.010488 +0.010278 +0.010189 +0.010238 +0.010337 +0.010431 +0.010491 +0.010303 +0.01019 +0.010251 +0.010303 +0.010345 +0.010364 +0.010165 +0.010029 +0.010055 +0.010095 +0.010143 +0.010152 +0.009933 +0.009811 +0.009806 +0.009827 +0.009857 +0.009875 +0.009668 +0.009538 +0.009551 +0.009599 +0.009636 +0.009633 +0.009405 +0.009297 +0.009303 +0.009337 +0.009381 +0.009403 +0.009182 +0.009083 +0.009101 +0.009133 +0.009165 +0.009177 +0.008971 +0.008873 +0.008901 +0.008944 +0.009006 +0.008992 +0.008812 +0.008711 +0.008739 +0.008789 +0.008827 +0.008857 +0.008677 +0.0086 +0.008617 +0.008659 +0.008719 +0.008755 +0.00857 +0.008474 +0.008511 +0.008547 +0.008608 +0.008642 +0.008504 +0.008385 +0.008436 +0.008502 +0.008568 +0.008618 +0.008465 +0.008391 +0.008445 +0.008532 +0.008598 +0.008644 +0.008488 +0.008422 +0.008482 +0.008564 +0.008645 +0.008697 +0.008559 +0.008473 +0.008513 +0.008604 +0.008676 +0.008732 +0.008579 +0.008523 +0.008569 +0.008634 +0.008725 +0.008783 +0.008627 +0.008563 +0.008608 +0.008684 +0.008778 +0.008807 +0.008692 +0.008622 +0.008689 +0.008719 +0.008794 +0.008861 +0.008742 +0.008634 +0.00869 +0.008755 +0.008864 +0.008923 +0.008769 +0.008701 +0.008747 +0.008811 +0.008895 +0.008956 +0.0088 +0.00873 +0.008789 +0.008865 +0.008937 +0.009003 +0.008861 +0.008774 +0.00883 +0.008903 +0.008989 +0.009051 +0.008899 +0.008828 +0.008894 +0.008991 +0.009027 +0.009082 +0.008933 +0.008863 +0.008927 +0.008985 +0.009073 +0.009142 +0.008993 +0.008927 +0.008969 +0.009057 +0.009133 +0.009176 +0.009026 +0.008947 +0.009004 +0.009075 +0.009176 +0.009246 +0.009086 +0.009014 +0.009071 +0.009134 +0.009206 +0.009277 +0.009142 +0.009049 +0.009085 +0.009181 +0.009247 +0.009325 +0.009197 +0.009083 +0.009136 +0.009226 +0.009301 +0.009371 +0.009221 +0.009142 +0.009197 +0.009284 +0.009356 +0.009412 +0.009256 +0.009186 +0.009232 +0.009315 +0.009406 +0.009484 +0.009313 +0.009244 +0.009293 +0.009391 +0.009458 +0.0095 +0.009341 +0.009266 +0.009338 +0.00942 +0.009499 +0.009586 +0.009391 +0.009308 +0.00937 +0.009459 +0.009551 +0.009614 +0.009466 +0.009378 +0.009441 +0.009489 +0.009601 +0.009656 +0.009494 +0.009416 +0.009463 +0.009543 +0.009664 +0.00973 +0.009589 +0.009471 +0.009516 +0.009589 +0.00969 +0.009756 +0.009589 +0.009501 +0.009577 +0.009668 +0.009747 +0.009825 +0.009642 +0.009563 +0.009624 +0.009685 +0.009784 +0.009853 +0.009693 +0.009601 +0.009655 +0.009756 +0.009841 +0.009907 +0.009745 +0.00967 +0.009732 +0.009792 +0.009883 +0.009962 +0.0098 +0.009712 +0.009743 +0.009838 +0.009941 +0.01 +0.009833 +0.009781 +0.009806 +0.009902 +0.010016 +0.01006 +0.009887 +0.009793 +0.00984 +0.009936 +0.010055 +0.010098 +0.00993 +0.009852 +0.009913 +0.010031 +0.01009 +0.010146 +0.009976 +0.009921 +0.009952 +0.010045 +0.010155 +0.01023 +0.010015 +0.009939 +0.01001 +0.010089 +0.010199 +0.010269 +0.010091 +0.010002 +0.01008 +0.010152 +0.010244 +0.010302 +0.010123 +0.010047 +0.010112 +0.010217 +0.010329 +0.010371 +0.010185 +0.010098 +0.010166 +0.010249 +0.010353 +0.010402 +0.010218 +0.010141 +0.010221 +0.010289 +0.010397 +0.01048 +0.010274 +0.010171 +0.010233 +0.010329 +0.010395 +0.010445 +0.010213 +0.010063 +0.010088 +0.010149 +0.010205 +0.010237 +0.01003 +0.009857 +0.009896 +0.009954 +0.009991 +0.009981 +0.00977 +0.009602 +0.00963 +0.009674 +0.009718 +0.00973 +0.009504 +0.009366 +0.009395 +0.009425 +0.00947 +0.009474 +0.009261 +0.009133 +0.009147 +0.009196 +0.009244 +0.009239 +0.009034 +0.008916 +0.008966 +0.008973 +0.009018 +0.00904 +0.008853 +0.008753 +0.008784 +0.008843 +0.008895 +0.008936 +0.008717 +0.008615 +0.008658 +0.008705 +0.008766 +0.008786 +0.008611 +0.008518 +0.008552 +0.008612 +0.008667 +0.008688 +0.008506 +0.00842 +0.008454 +0.008516 +0.008587 +0.008638 +0.008494 +0.008404 +0.008447 +0.008513 +0.008589 +0.008641 +0.008509 +0.00842 +0.008473 +0.00855 +0.008638 +0.008708 +0.008555 +0.008478 +0.008517 +0.008597 +0.008674 +0.008741 +0.008587 +0.008506 +0.008558 +0.008647 +0.008735 +0.008797 +0.008646 +0.008565 +0.0086 +0.008677 +0.00877 +0.008839 +0.008697 +0.008611 +0.008644 +0.008714 +0.008828 +0.00886 +0.0087 +0.008637 +0.008689 +0.008767 +0.008846 +0.008907 +0.008772 +0.008698 +0.008754 +0.008819 +0.008894 +0.00895 +0.008801 +0.008734 +0.008779 +0.008855 +0.008935 +0.009013 +0.008864 +0.008782 +0.008832 +0.008904 +0.009015 +0.009039 +0.008885 +0.008812 +0.008863 +0.008941 +0.009058 +0.009095 +0.008922 +0.008862 +0.008905 +0.008982 +0.00908 +0.009129 +0.008981 +0.008904 +0.008969 +0.009049 +0.00913 +0.009198 +0.009017 +0.008941 +0.008995 +0.00908 +0.009163 +0.009227 +0.009074 +0.009014 +0.009087 +0.009132 +0.00922 +0.009272 +0.009101 +0.009036 +0.009086 +0.00916 +0.009283 +0.009318 +0.00915 +0.009079 +0.009142 +0.00922 +0.009305 +0.009375 +0.009197 +0.009126 +0.009183 +0.009268 +0.009361 +0.009426 +0.009255 +0.009167 +0.009232 +0.009322 +0.009407 +0.009492 +0.009287 +0.009221 +0.009281 +0.009361 +0.009461 +0.0095 +0.009331 +0.009268 +0.009324 +0.009403 +0.009491 +0.009563 +0.009392 +0.0093 +0.009375 +0.009441 +0.009546 +0.009611 +0.009445 +0.009367 +0.009418 +0.009508 +0.009607 +0.009649 +0.009494 +0.009423 +0.009476 +0.009541 +0.009658 +0.009681 +0.009534 +0.009462 +0.009518 +0.009603 +0.009691 +0.009733 +0.009578 +0.009502 +0.009574 +0.009642 +0.009735 +0.00981 +0.009639 +0.009563 +0.009609 +0.009694 +0.009772 +0.009844 +0.009676 +0.009592 +0.00966 +0.009781 +0.009841 +0.009882 +0.009705 +0.009628 +0.009738 +0.009768 +0.009885 +0.009956 +0.009784 +0.009705 +0.00976 +0.009831 +0.009939 +0.009997 +0.00983 +0.009741 +0.009791 +0.009897 +0.010001 +0.010063 +0.009888 +0.009787 +0.009839 +0.009934 +0.010053 +0.010124 +0.009912 +0.009823 +0.009894 +0.009991 +0.010081 +0.010173 +0.009964 +0.009882 +0.009947 +0.010039 +0.010146 +0.010227 +0.010016 +0.009941 +0.009997 +0.01008 +0.010196 +0.010245 +0.010073 +0.009994 +0.010061 +0.010161 +0.010256 +0.010311 +0.010134 +0.010024 +0.010083 +0.010167 +0.010295 +0.010369 +0.01019 +0.010104 +0.010183 +0.010238 +0.010325 +0.010397 +0.010215 +0.010139 +0.010206 +0.010283 +0.010396 +0.010462 +0.010275 +0.010191 +0.01024 +0.010326 +0.010415 +0.010483 +0.010305 +0.010147 +0.010145 +0.010184 +0.010249 +0.010261 +0.010044 +0.009916 +0.009935 +0.010005 +0.010047 +0.010045 +0.009831 +0.009669 +0.009667 +0.009713 +0.009765 +0.009773 +0.009568 +0.009425 +0.009429 +0.009461 +0.009512 +0.009525 +0.009305 +0.009173 +0.009187 +0.009245 +0.009285 +0.00931 +0.009072 +0.008946 +0.008967 +0.008998 +0.009053 +0.00908 +0.008899 +0.008778 +0.008798 +0.008856 +0.008895 +0.00893 +0.008751 +0.00865 +0.008662 +0.008709 +0.008778 +0.00881 +0.008638 +0.008545 +0.00858 +0.008612 +0.008667 +0.008704 +0.008542 +0.008456 +0.008494 +0.008524 +0.008578 +0.008636 +0.008508 +0.008401 +0.00843 +0.008501 +0.008578 +0.008625 +0.008479 +0.008402 +0.008465 +0.008528 +0.008606 +0.008667 +0.008525 +0.008451 +0.0085 +0.008573 +0.008658 +0.008712 +0.008574 +0.008498 +0.008548 +0.008617 +0.008707 +0.008772 +0.008616 +0.008556 +0.008577 +0.008657 +0.008739 +0.008789 +0.00865 +0.008584 +0.008644 +0.008694 +0.008788 +0.008851 +0.008705 +0.008643 +0.008675 +0.008733 +0.008825 +0.008888 +0.008738 +0.008661 +0.008713 +0.008794 +0.008874 +0.008934 +0.008792 +0.008713 +0.008755 +0.008831 +0.008926 +0.009004 +0.008837 +0.008751 +0.008807 +0.008879 +0.008957 +0.009023 +0.008874 +0.00879 +0.008853 +0.008921 +0.009 +0.009084 +0.008924 +0.008853 +0.008898 +0.008982 +0.009043 +0.009106 +0.008981 +0.008877 +0.008932 +0.009012 +0.009099 +0.009174 +0.009015 +0.008937 +0.008992 +0.009094 +0.009164 +0.009186 +0.009025 +0.008961 +0.009015 +0.009106 +0.009191 +0.00925 +0.009104 +0.009021 +0.009069 +0.009162 +0.009223 +0.0093 +0.00914 +0.009071 +0.009131 +0.009204 +0.009289 +0.009338 +0.009189 +0.009111 +0.009165 +0.009244 +0.009324 +0.009404 +0.009268 +0.00918 +0.009207 +0.009288 +0.009381 +0.009422 +0.009268 +0.009193 +0.009253 +0.009341 +0.009417 +0.009486 +0.009338 +0.009245 +0.009289 +0.009379 +0.009467 +0.009537 +0.009374 +0.00931 +0.009364 +0.00943 +0.009524 +0.009582 +0.00941 +0.00933 +0.009389 +0.0095 +0.009576 +0.009629 +0.009472 +0.009388 +0.009474 +0.00951 +0.009599 +0.009672 +0.009504 +0.009422 +0.00949 +0.009574 +0.009682 +0.00974 +0.009579 +0.009488 +0.009526 +0.009618 +0.009704 +0.009765 +0.009602 +0.009528 +0.009579 +0.00967 +0.009767 +0.009855 +0.009652 +0.009568 +0.009609 +0.009713 +0.009817 +0.009886 +0.009734 +0.009628 +0.009665 +0.009755 +0.009867 +0.009918 +0.009746 +0.00966 +0.009736 +0.009832 +0.009926 +0.009988 +0.009804 +0.0097 +0.009769 +0.009852 +0.009953 +0.010025 +0.009865 +0.009801 +0.009818 +0.009902 +0.010002 +0.010066 +0.009894 +0.009796 +0.009872 +0.009975 +0.010076 +0.010122 +0.009961 +0.009859 +0.009922 +0.010015 +0.010093 +0.010176 +0.010009 +0.009952 +0.009979 +0.010077 +0.010162 +0.010206 +0.010048 +0.009963 +0.010053 +0.010097 +0.010228 +0.010276 +0.010099 +0.010016 +0.01007 +0.010147 +0.010255 +0.010311 +0.010153 +0.010069 +0.010117 +0.010209 +0.010319 +0.01038 +0.010199 +0.010117 +0.010167 +0.010268 +0.010373 +0.010446 +0.01026 +0.010155 +0.010249 +0.010332 +0.010396 +0.010451 +0.01029 +0.010178 +0.010245 +0.010301 +0.010381 +0.010378 +0.01015 +0.010042 +0.010083 +0.010138 +0.010184 +0.010202 +0.009994 +0.009854 +0.009891 +0.009918 +0.009957 +0.009983 +0.00976 +0.009631 +0.009672 +0.009719 +0.009764 +0.009731 +0.009512 +0.009405 +0.009426 +0.009496 +0.009489 +0.009505 +0.009319 +0.009196 +0.009223 +0.009254 +0.009273 +0.009283 +0.009094 +0.008993 +0.009004 +0.009046 +0.009094 +0.009117 +0.008925 +0.008819 +0.008853 +0.008882 +0.008912 +0.008946 +0.008759 +0.008687 +0.008699 +0.008749 +0.008786 +0.008798 +0.008631 +0.008539 +0.008571 +0.008622 +0.008672 +0.008694 +0.008533 +0.008457 +0.0085 +0.008545 +0.008625 +0.008669 +0.008507 +0.00842 +0.0085 +0.00856 +0.008623 +0.008682 +0.008541 +0.008461 +0.008514 +0.008607 +0.008686 +0.008745 +0.008622 +0.008504 +0.008555 +0.00862 +0.008711 +0.008768 +0.008623 +0.008552 +0.008594 +0.008681 +0.00876 +0.00882 +0.008674 +0.008603 +0.008639 +0.008722 +0.008814 +0.008866 +0.00874 +0.008642 +0.008691 +0.008778 +0.008833 +0.0089 +0.008751 +0.008677 +0.008726 +0.008815 +0.008899 +0.008993 +0.008796 +0.008751 +0.008771 +0.008835 +0.008935 +0.008982 +0.00884 +0.008767 +0.008823 +0.008891 +0.008987 +0.009058 +0.008895 +0.008817 +0.00887 +0.00895 +0.00901 +0.00908 +0.008934 +0.00885 +0.008906 +0.008987 +0.009073 +0.009133 +0.008977 +0.008907 +0.008961 +0.009056 +0.009106 +0.009173 +0.009014 +0.008958 +0.00902 +0.009079 +0.009153 +0.00922 +0.009066 +0.008985 +0.009045 +0.009123 +0.009201 +0.009275 +0.00912 +0.009041 +0.009098 +0.009175 +0.009257 +0.009306 +0.009155 +0.009084 +0.009122 +0.009213 +0.009293 +0.009382 +0.009248 +0.009137 +0.009186 +0.009259 +0.009332 +0.009407 +0.009239 +0.009187 +0.009211 +0.009311 +0.009377 +0.009454 +0.009294 +0.009215 +0.009286 +0.009355 +0.00945 +0.00951 +0.009334 +0.009279 +0.009323 +0.009394 +0.009499 +0.009545 +0.009386 +0.009317 +0.009377 +0.00947 +0.009532 +0.009604 +0.009439 +0.009349 +0.009427 +0.009482 +0.009583 +0.009657 +0.009477 +0.009406 +0.009457 +0.009527 +0.009642 +0.009692 +0.009538 +0.009452 +0.009502 +0.009635 +0.00968 +0.009741 +0.009584 +0.009496 +0.009537 +0.009635 +0.00973 +0.009832 +0.009651 +0.009545 +0.009591 +0.009682 +0.009797 +0.009844 +0.009675 +0.009589 +0.009633 +0.009736 +0.009825 +0.009901 +0.009728 +0.00964 +0.009729 +0.009796 +0.009884 +0.009947 +0.00976 +0.009678 +0.009749 +0.009839 +0.009929 +0.010013 +0.009841 +0.009766 +0.009779 +0.009864 +0.009996 +0.010032 +0.009863 +0.009781 +0.009846 +0.009965 +0.010047 +0.010097 +0.009926 +0.009822 +0.009884 +0.009987 +0.010085 +0.010138 +0.009989 +0.009898 +0.009958 +0.010053 +0.010126 +0.010183 +0.010021 +0.009942 +0.01003 +0.010085 +0.010176 +0.010237 +0.010063 +0.010012 +0.01003 +0.010121 +0.010233 +0.010297 +0.010131 +0.01006 +0.010093 +0.010177 +0.010298 +0.010349 +0.010169 +0.010086 +0.010138 +0.010235 +0.010368 +0.010417 +0.010234 +0.010153 +0.010212 +0.010292 +0.010371 +0.010445 +0.010245 +0.010157 +0.010207 +0.010288 +0.010345 +0.010365 +0.010172 +0.010031 +0.010064 +0.010123 +0.010191 +0.010181 +0.009967 +0.009801 +0.009828 +0.009888 +0.00992 +0.009922 +0.00971 +0.009571 +0.009611 +0.009661 +0.009688 +0.009694 +0.009439 +0.009316 +0.009343 +0.009383 +0.009434 +0.009425 +0.009242 +0.009125 +0.009149 +0.009183 +0.009224 +0.009216 +0.009024 +0.008906 +0.008938 +0.008981 +0.009046 +0.00908 +0.008844 +0.008744 +0.008777 +0.00884 +0.008879 +0.008907 +0.008719 +0.008629 +0.008682 +0.008757 +0.008764 +0.008782 +0.008594 +0.008502 +0.008546 +0.008601 +0.008646 +0.008678 +0.008514 +0.008407 +0.008459 +0.008535 +0.008589 +0.008627 +0.008464 +0.008389 +0.008421 +0.008504 +0.008582 +0.00863 +0.00849 +0.008419 +0.008463 +0.00853 +0.008623 +0.008685 +0.008542 +0.008468 +0.008531 +0.008573 +0.008667 +0.008726 +0.008572 +0.008513 +0.008552 +0.008619 +0.008704 +0.008781 +0.008625 +0.00854 +0.008597 +0.008681 +0.008739 +0.008799 +0.008678 +0.008609 +0.008655 +0.008715 +0.008785 +0.008845 +0.008699 +0.008641 +0.008677 +0.00875 +0.008826 +0.008923 +0.008779 +0.008696 +0.008729 +0.008797 +0.008873 +0.008936 +0.008806 +0.008708 +0.008761 +0.008855 +0.008936 +0.008975 +0.008831 +0.008762 +0.008817 +0.008884 +0.008977 +0.00903 +0.00888 +0.008804 +0.00887 +0.008941 +0.009025 +0.009071 +0.008923 +0.008848 +0.008902 +0.008985 +0.009083 +0.009139 +0.008967 +0.008898 +0.00895 +0.009025 +0.009123 +0.009149 +0.009007 +0.008944 +0.008997 +0.009065 +0.009152 +0.009216 +0.009052 +0.008971 +0.009045 +0.009118 +0.009225 +0.009254 +0.009105 +0.009027 +0.009077 +0.009177 +0.00926 +0.009299 +0.009154 +0.009078 +0.009157 +0.009206 +0.009287 +0.009342 +0.009193 +0.009135 +0.009182 +0.009253 +0.009358 +0.009401 +0.009238 +0.00916 +0.009218 +0.009293 +0.00938 +0.00946 +0.009303 +0.009216 +0.009274 +0.009388 +0.00943 +0.00948 +0.009332 +0.009247 +0.009305 +0.009397 +0.009495 +0.009568 +0.009369 +0.009308 +0.009349 +0.009436 +0.009532 +0.009588 +0.009434 +0.009361 +0.009424 +0.009488 +0.00957 +0.009634 +0.009465 +0.009386 +0.009464 +0.009533 +0.009627 +0.009713 +0.009533 +0.009451 +0.009504 +0.009584 +0.009664 +0.009731 +0.009575 +0.00952 +0.009556 +0.009627 +0.009709 +0.009783 +0.009623 +0.009529 +0.00959 +0.009675 +0.00979 +0.009833 +0.009668 +0.009606 +0.009663 +0.009717 +0.009822 +0.009871 +0.009715 +0.009639 +0.009677 +0.009776 +0.009891 +0.009951 +0.009774 +0.009686 +0.009737 +0.009855 +0.009918 +0.009967 +0.009804 +0.009717 +0.009806 +0.009886 +0.009978 +0.01005 +0.009869 +0.009758 +0.009835 +0.009915 +0.010011 +0.01011 +0.009911 +0.009817 +0.009882 +0.009967 +0.010069 +0.010135 +0.009958 +0.009874 +0.00994 +0.010058 +0.010179 +0.010167 +0.009998 +0.009915 +0.009967 +0.01008 +0.010163 +0.010237 +0.010099 +0.009984 +0.010035 +0.010135 +0.010202 +0.010274 +0.010111 +0.010014 +0.010084 +0.010189 +0.010266 +0.010336 +0.010167 +0.010066 +0.010132 +0.01022 +0.010343 +0.01045 +0.010208 +0.010125 +0.010174 +0.010261 +0.010365 +0.01041 +0.010229 +0.010133 +0.010158 +0.010215 +0.010291 +0.010298 +0.010089 +0.009961 +0.009973 +0.01002 +0.010065 +0.010076 +0.009868 +0.009736 +0.009731 +0.009776 +0.009805 +0.009825 +0.009617 +0.009488 +0.00953 +0.009528 +0.009569 +0.009593 +0.009389 +0.009258 +0.009282 +0.009299 +0.009345 +0.00936 +0.009197 +0.00904 +0.009082 +0.009123 +0.009163 +0.009194 +0.008996 +0.008874 +0.008898 +0.008944 +0.008985 +0.009015 +0.008847 +0.008734 +0.008758 +0.008803 +0.008856 +0.008905 +0.00873 +0.008611 +0.008634 +0.008685 +0.008733 +0.008776 +0.008629 +0.008521 +0.008535 +0.008592 +0.008648 +0.008685 +0.008548 +0.00845 +0.008471 +0.008544 +0.008617 +0.008677 +0.008536 +0.008466 +0.008507 +0.008561 +0.008644 +0.008718 +0.008555 +0.008485 +0.008543 +0.008614 +0.008723 +0.008784 +0.008607 +0.008541 +0.008585 +0.008659 +0.008742 +0.008776 +0.008639 +0.008569 +0.008631 +0.008701 +0.008774 +0.008847 +0.008697 +0.008634 +0.008673 +0.008739 +0.00881 +0.008883 +0.008738 +0.00867 +0.008717 +0.008795 +0.008869 +0.008921 +0.008781 +0.008706 +0.008755 +0.008856 +0.008913 +0.008975 +0.00883 +0.008753 +0.008807 +0.00888 +0.008975 +0.009002 +0.008867 +0.008783 +0.008837 +0.008916 +0.009009 +0.009066 +0.00891 +0.008842 +0.008888 +0.00896 +0.009052 +0.009113 +0.008956 +0.008875 +0.008931 +0.009001 +0.009097 +0.009176 +0.009015 +0.008961 +0.008973 +0.009048 +0.009132 +0.009192 +0.009047 +0.008962 +0.009017 +0.009108 +0.009232 +0.00924 +0.00909 +0.009023 +0.009052 +0.009135 +0.009229 +0.009292 +0.009131 +0.009065 +0.009121 +0.009195 +0.009277 +0.00934 +0.00918 +0.0091 +0.009146 +0.009243 +0.009352 +0.009414 +0.009254 +0.009151 +0.009189 +0.009273 +0.009371 +0.009438 +0.009267 +0.009196 +0.009229 +0.009335 +0.009443 +0.009488 +0.009331 +0.009241 +0.009292 +0.009373 +0.009462 +0.009535 +0.009359 +0.009283 +0.00934 +0.009426 +0.009516 +0.009579 +0.009415 +0.009367 +0.009401 +0.009468 +0.009559 +0.009638 +0.009474 +0.009361 +0.009427 +0.009522 +0.00961 +0.009672 +0.009507 +0.009425 +0.009487 +0.009566 +0.009681 +0.009726 +0.009555 +0.009469 +0.009521 +0.009621 +0.00971 +0.009782 +0.009595 +0.009532 +0.009594 +0.009702 +0.009786 +0.009808 +0.009628 +0.00956 +0.009629 +0.009729 +0.009795 +0.009874 +0.009687 +0.009606 +0.009671 +0.009763 +0.009846 +0.009916 +0.009766 +0.009655 +0.009729 +0.009837 +0.009918 +0.009955 +0.009804 +0.009708 +0.009772 +0.009867 +0.009965 +0.010055 +0.009851 +0.009773 +0.009826 +0.009908 +0.009992 +0.010066 +0.009909 +0.009811 +0.009858 +0.009945 +0.010064 +0.010114 +0.009946 +0.009863 +0.009916 +0.010009 +0.010117 +0.010176 +0.009999 +0.009936 +0.009968 +0.01005 +0.010162 +0.010237 +0.010094 +0.009945 +0.01 +0.010107 +0.010204 +0.010285 +0.010126 +0.010014 +0.010063 +0.010166 +0.010255 +0.010325 +0.010142 +0.010048 +0.010131 +0.010219 +0.010331 +0.010401 +0.0102 +0.010095 +0.010174 +0.010249 +0.010364 +0.01045 +0.01027 +0.01018 +0.010215 +0.010332 +0.010406 +0.010476 +0.010298 +0.010211 +0.010283 +0.010392 +0.010471 +0.010538 +0.010329 +0.010241 +0.010315 +0.010387 +0.010458 +0.010514 +0.010299 +0.010159 +0.010194 +0.01025 +0.01029 +0.01032 +0.010086 +0.00998 +0.009969 +0.010003 +0.010056 +0.010111 +0.009838 +0.009718 +0.009742 +0.009791 +0.00984 +0.009845 +0.009645 +0.009528 +0.009532 +0.009571 +0.009604 +0.009618 +0.009403 +0.00928 +0.0093 +0.009352 +0.009402 +0.009417 +0.009213 +0.009094 +0.009107 +0.009168 +0.009181 +0.009192 +0.009007 +0.008897 +0.008924 +0.008992 +0.009058 +0.009055 +0.008862 +0.008755 +0.008772 +0.008835 +0.008875 +0.008903 +0.008725 +0.008629 +0.008669 +0.008707 +0.008775 +0.008792 +0.008625 +0.008531 +0.008575 +0.008629 +0.00871 +0.00875 +0.0086 +0.008535 +0.008573 +0.008637 +0.008702 +0.008764 +0.008622 +0.008547 +0.008593 +0.00866 +0.008757 +0.008844 +0.00867 +0.008597 +0.008652 +0.008717 +0.008803 +0.008853 +0.008702 +0.008628 +0.008681 +0.008757 +0.008858 +0.0089 +0.008748 +0.008681 +0.008728 +0.008808 +0.008897 +0.00896 +0.00882 +0.008759 +0.008759 +0.008851 +0.008934 +0.008999 +0.008825 +0.008761 +0.008807 +0.008892 +0.008974 +0.009037 +0.008885 +0.008813 +0.008871 +0.008947 +0.009028 +0.009088 +0.008924 +0.008853 +0.0089 +0.008977 +0.009067 +0.009139 +0.008988 +0.008893 +0.008953 +0.009033 +0.009146 +0.009167 +0.009004 +0.008952 +0.008992 +0.009069 +0.009165 +0.009227 +0.009066 +0.008975 +0.009036 +0.009115 +0.009215 +0.009269 +0.009106 +0.00904 +0.009091 +0.009174 +0.00926 +0.009313 +0.009148 +0.009064 +0.009136 +0.009213 +0.009294 +0.009367 +0.009213 +0.009152 +0.009187 +0.009238 +0.009331 +0.009394 +0.009246 +0.009182 +0.00922 +0.009303 +0.009403 +0.009471 +0.00929 +0.009223 +0.00926 +0.00934 +0.009447 +0.009498 +0.009331 +0.009261 +0.009328 +0.009417 +0.009504 +0.00957 +0.009379 +0.009297 +0.009363 +0.00944 +0.009567 +0.009593 +0.00943 +0.009343 +0.009408 +0.009497 +0.009586 +0.009655 +0.009485 +0.009387 +0.009451 +0.009539 +0.009639 +0.009708 +0.009526 +0.009453 +0.009509 +0.009592 +0.009672 +0.009735 +0.009566 +0.009507 +0.009562 +0.009655 +0.009744 +0.00981 +0.00966 +0.009536 +0.00959 +0.00967 +0.009767 +0.009834 +0.0097 +0.009595 +0.009664 +0.00975 +0.009839 +0.009876 +0.009711 +0.009633 +0.009689 +0.009787 +0.009872 +0.009941 +0.009772 +0.009685 +0.009751 +0.009851 +0.009929 +0.009992 +0.009831 +0.009762 +0.009834 +0.009881 +0.009982 +0.010016 +0.009862 +0.009782 +0.009845 +0.009925 +0.010042 +0.010114 +0.00993 +0.009852 +0.009886 +0.009969 +0.010081 +0.010137 +0.009967 +0.009883 +0.009943 +0.010031 +0.010146 +0.010202 +0.010019 +0.009943 +0.01 +0.010113 +0.010174 +0.010248 +0.010078 +0.010021 +0.010019 +0.010126 +0.010232 +0.010298 +0.01012 +0.010027 +0.01011 +0.010206 +0.010302 +0.010358 +0.010157 +0.010075 +0.010145 +0.01023 +0.010344 +0.010407 +0.010225 +0.010133 +0.010199 +0.010322 +0.0104 +0.010438 +0.010266 +0.010182 +0.010257 +0.010364 +0.010438 +0.010508 +0.010319 +0.010236 +0.010308 +0.010387 +0.010489 +0.010556 +0.010403 +0.010257 +0.010304 +0.010344 +0.010407 +0.010416 +0.010218 +0.010096 +0.010115 +0.010162 +0.010229 +0.010225 +0.009965 +0.009854 +0.009878 +0.00992 +0.009963 +0.009973 +0.009794 +0.009634 +0.009643 +0.009693 +0.009726 +0.009739 +0.009539 +0.009408 +0.009434 +0.009483 +0.009534 +0.009531 +0.009327 +0.009212 +0.009216 +0.009262 +0.009307 +0.009315 +0.009126 +0.009023 +0.009064 +0.009133 +0.009132 +0.00915 +0.008972 +0.008856 +0.008897 +0.008938 +0.008988 +0.009035 +0.008842 +0.008746 +0.008777 +0.008837 +0.008876 +0.008908 +0.008736 +0.008649 +0.008678 +0.00873 +0.008793 +0.008833 +0.008656 +0.008582 +0.008627 +0.008685 +0.008732 +0.008788 +0.008635 +0.008587 +0.008626 +0.008691 +0.008756 +0.008833 +0.008689 +0.00861 +0.008655 +0.008738 +0.008817 +0.008864 +0.008732 +0.008638 +0.008703 +0.00877 +0.008854 +0.00892 +0.008762 +0.008686 +0.008743 +0.008825 +0.008909 +0.008956 +0.008811 +0.008739 +0.008813 +0.008867 +0.008961 +0.009007 +0.008873 +0.008772 +0.008821 +0.008907 +0.00898 +0.009054 +0.008885 +0.008821 +0.008893 +0.008957 +0.009037 +0.009102 +0.008971 +0.008853 +0.008907 +0.008993 +0.00908 +0.009128 +0.008985 +0.008917 +0.008959 +0.009041 +0.009135 +0.009192 +0.009028 +0.008957 +0.009017 +0.009107 +0.009205 +0.009237 +0.009062 +0.008998 +0.009061 +0.009124 +0.009212 +0.009273 +0.009122 +0.009042 +0.0091 +0.009195 +0.009284 +0.009333 +0.009159 +0.009096 +0.00914 +0.009216 +0.009311 +0.009368 +0.009205 +0.009147 +0.00921 +0.009286 +0.009371 +0.009427 +0.009268 +0.009199 +0.009219 +0.009306 +0.009405 +0.009468 +0.009296 +0.009238 +0.009271 +0.009372 +0.009447 +0.009512 +0.009356 +0.009273 +0.009324 +0.009421 +0.009516 +0.009559 +0.009389 +0.009322 +0.009362 +0.00946 +0.009563 +0.009612 +0.009443 +0.009363 +0.00943 +0.009542 +0.009612 +0.009663 +0.009478 +0.009401 +0.009475 +0.009561 +0.009638 +0.009704 +0.00956 +0.009462 +0.00951 +0.009607 +0.009691 +0.009744 +0.009593 +0.009502 +0.00957 +0.009661 +0.009743 +0.009793 +0.00963 +0.009552 +0.009599 +0.009694 +0.009795 +0.009885 +0.009695 +0.009604 +0.009679 +0.009752 +0.009839 +0.009885 +0.009722 +0.009644 +0.009714 +0.00979 +0.00991 +0.009959 +0.009791 +0.009712 +0.009766 +0.009831 +0.009928 +0.010007 +0.009823 +0.009747 +0.00981 +0.009883 +0.009989 +0.010065 +0.009892 +0.009825 +0.009847 +0.009924 +0.010047 +0.010103 +0.009928 +0.009839 +0.009915 +0.009987 +0.010084 +0.010155 +0.009987 +0.009929 +0.009941 +0.010043 +0.010133 +0.01021 +0.01005 +0.009941 +0.009987 +0.010094 +0.010183 +0.010259 +0.010093 +0.010004 +0.010087 +0.010151 +0.010242 +0.010316 +0.010125 +0.010046 +0.010089 +0.010187 +0.010287 +0.010351 +0.010179 +0.010093 +0.010154 +0.010253 +0.01036 +0.010402 +0.010223 +0.01014 +0.010209 +0.010318 +0.010415 +0.010451 +0.010283 +0.010199 +0.010296 +0.010345 +0.010434 +0.010518 +0.010345 +0.01026 +0.010312 +0.010418 +0.01049 +0.010554 +0.01037 +0.010302 +0.010346 +0.010468 +0.010584 +0.010638 +0.010443 +0.010344 +0.010375 +0.010466 +0.010559 +0.010589 +0.010388 +0.010242 +0.01027 +0.010336 +0.010409 +0.010382 +0.010146 +0.010008 +0.010035 +0.010077 +0.010141 +0.010211 +0.009921 +0.009793 +0.009789 +0.009842 +0.009894 +0.009904 +0.009689 +0.009537 +0.009584 +0.009635 +0.009676 +0.009673 +0.009467 +0.009315 +0.00933 +0.009379 +0.009435 +0.009483 +0.00926 +0.00912 +0.009129 +0.009179 +0.009243 +0.009254 +0.009063 +0.008939 +0.008977 +0.009025 +0.009096 +0.009118 +0.008914 +0.008793 +0.008825 +0.008883 +0.008935 +0.008969 +0.00879 +0.008677 +0.008715 +0.008781 +0.008845 +0.008877 +0.008693 +0.008592 +0.008617 +0.00871 +0.008758 +0.008796 +0.008646 +0.008538 +0.008587 +0.008665 +0.008746 +0.008813 +0.00867 +0.008581 +0.008629 +0.008703 +0.008801 +0.008854 +0.008709 +0.008645 +0.008682 +0.00875 +0.008836 +0.008904 +0.00874 +0.008667 +0.008723 +0.008798 +0.008876 +0.008956 +0.008796 +0.008724 +0.008792 +0.00884 +0.008916 +0.00898 +0.008822 +0.008785 +0.008816 +0.008878 +0.00898 +0.009041 +0.008872 +0.008792 +0.008855 +0.008928 +0.009009 +0.009085 +0.008918 +0.008834 +0.008904 +0.008995 +0.009067 +0.009128 +0.008972 +0.008878 +0.008935 +0.00902 +0.009108 +0.009182 +0.009031 +0.008925 +0.008986 +0.009061 +0.009148 +0.009219 +0.009048 +0.008988 +0.009032 +0.009106 +0.009197 +0.009272 +0.009119 +0.00901 +0.009074 +0.009161 +0.009232 +0.00932 +0.009146 +0.009068 +0.009121 +0.009213 +0.009309 +0.009359 +0.009198 +0.009111 +0.009173 +0.009281 +0.009333 +0.009392 +0.009226 +0.009161 +0.009231 +0.00931 +0.009387 +0.009455 +0.00931 +0.009187 +0.009247 +0.009337 +0.00943 +0.00949 +0.009336 +0.009236 +0.009305 +0.009392 +0.009488 +0.009542 +0.009374 +0.009304 +0.009348 +0.009436 +0.009547 +0.009619 +0.009441 +0.009337 +0.009392 +0.00948 +0.009568 +0.009641 +0.00946 +0.009397 +0.009469 +0.009548 +0.009624 +0.009711 +0.0095 +0.009434 +0.009495 +0.009574 +0.009654 +0.009725 +0.009579 +0.00948 +0.009552 +0.00964 +0.009721 +0.009784 +0.009631 +0.009551 +0.009624 +0.00969 +0.009773 +0.009816 +0.009662 +0.009578 +0.009636 +0.009732 +0.009823 +0.009889 +0.009721 +0.00965 +0.0097 +0.009773 +0.00986 +0.00992 +0.009754 +0.009683 +0.009741 +0.009819 +0.009935 +0.010005 +0.009822 +0.009732 +0.009793 +0.009885 +0.009982 +0.010027 +0.00984 +0.009773 +0.009842 +0.009939 +0.010006 +0.010077 +0.009904 +0.00982 +0.009882 +0.009979 +0.01007 +0.010151 +0.009982 +0.009865 +0.009924 +0.010025 +0.01011 +0.010189 +0.010023 +0.009919 +0.009996 +0.010097 +0.010207 +0.010256 +0.010054 +0.009942 +0.010024 +0.010125 +0.010215 +0.010291 +0.010141 +0.010021 +0.010071 +0.010173 +0.010271 +0.010326 +0.010169 +0.010077 +0.010134 +0.01025 +0.010345 +0.010375 +0.010209 +0.010122 +0.010186 +0.010279 +0.010385 +0.01049 +0.010279 +0.01018 +0.010235 +0.010308 +0.010426 +0.010493 +0.010301 +0.010234 +0.010307 +0.010396 +0.010497 +0.010551 +0.010368 +0.010283 +0.01032 +0.010411 +0.01053 +0.010608 +0.010433 +0.010329 +0.01039 +0.010488 +0.010586 +0.010674 +0.010431 +0.010335 +0.010383 +0.010449 +0.010505 +0.010493 +0.010265 +0.010125 +0.010153 +0.010197 +0.010248 +0.010214 +0.009998 +0.009865 +0.009889 +0.009936 +0.009955 +0.009964 +0.009751 +0.009639 +0.009633 +0.009675 +0.009727 +0.009708 +0.009489 +0.009382 +0.009413 +0.009441 +0.009452 +0.009471 +0.009254 +0.009153 +0.009187 +0.009221 +0.009255 +0.009256 +0.009061 +0.008957 +0.008996 +0.009027 +0.009068 +0.009081 +0.008929 +0.008813 +0.008842 +0.0089 +0.008932 +0.008943 +0.008773 +0.008683 +0.008708 +0.008758 +0.008813 +0.008856 +0.00868 +0.008579 +0.008598 +0.008654 +0.008702 +0.00876 +0.008573 +0.008489 +0.008535 +0.008596 +0.008655 +0.008713 +0.008566 +0.008481 +0.008533 +0.008613 +0.008684 +0.008747 +0.008598 +0.008512 +0.008576 +0.008647 +0.008744 +0.0088 +0.008655 +0.008561 +0.008609 +0.008697 +0.008782 +0.008856 +0.008679 +0.008603 +0.008652 +0.008729 +0.008818 +0.008892 +0.008743 +0.008656 +0.008713 +0.008786 +0.008858 +0.008906 +0.008766 +0.008687 +0.008745 +0.008822 +0.008913 +0.008977 +0.008804 +0.00873 +0.008781 +0.00886 +0.00896 +0.009011 +0.008855 +0.008789 +0.008858 +0.008944 +0.009003 +0.009047 +0.008893 +0.00882 +0.008875 +0.008957 +0.009044 +0.009098 +0.008964 +0.008892 +0.008925 +0.009003 +0.009079 +0.009136 +0.008989 +0.008918 +0.00896 +0.009042 +0.009123 +0.009215 +0.009044 +0.008966 +0.009027 +0.009094 +0.009165 +0.009233 +0.009087 +0.009028 +0.009066 +0.009154 +0.009205 +0.009271 +0.009125 +0.009039 +0.009098 +0.009176 +0.009265 +0.009337 +0.009173 +0.009103 +0.009159 +0.009235 +0.009312 +0.009374 +0.009215 +0.009135 +0.00918 +0.009257 +0.009366 +0.009445 +0.009281 +0.009194 +0.009253 +0.009342 +0.009431 +0.009471 +0.009294 +0.009221 +0.009287 +0.009357 +0.00944 +0.009512 +0.00936 +0.009283 +0.009332 +0.009406 +0.009512 +0.00958 +0.009395 +0.009333 +0.009382 +0.009452 +0.009551 +0.009615 +0.009453 +0.009371 +0.009437 +0.009496 +0.009597 +0.009678 +0.009531 +0.009444 +0.009476 +0.009543 +0.009634 +0.009702 +0.009569 +0.009453 +0.0095 +0.009617 +0.009698 +0.009762 +0.009603 +0.009511 +0.009556 +0.009656 +0.00975 +0.009805 +0.009635 +0.009563 +0.0096 +0.009683 +0.009804 +0.009851 +0.009689 +0.009613 +0.009693 +0.009786 +0.009838 +0.009916 +0.009737 +0.009633 +0.009704 +0.009791 +0.009919 +0.009953 +0.00977 +0.0097 +0.009781 +0.009854 +0.009953 +0.010014 +0.009821 +0.009748 +0.009812 +0.009904 +0.009988 +0.010057 +0.009876 +0.009794 +0.009855 +0.009965 +0.010095 +0.010091 +0.009926 +0.009841 +0.0099 +0.01002 +0.010094 +0.010146 +0.009984 +0.00989 +0.009956 +0.010054 +0.010138 +0.010214 +0.010054 +0.009959 +0.010017 +0.010103 +0.010201 +0.010233 +0.010081 +0.010002 +0.010057 +0.01015 +0.010288 +0.010364 +0.01013 +0.010075 +0.010074 +0.010181 +0.010292 +0.010352 +0.010179 +0.010115 +0.010143 +0.010247 +0.010356 +0.010412 +0.010233 +0.010148 +0.010211 +0.010301 +0.010434 +0.010483 +0.010277 +0.010201 +0.01025 +0.010343 +0.010472 +0.010556 +0.010348 +0.010252 +0.010315 +0.010448 +0.010504 +0.010518 +0.010358 +0.01025 +0.010307 +0.010377 +0.010415 +0.010429 +0.010224 +0.0101 +0.010125 +0.010165 +0.010208 +0.010209 +0.009991 +0.009862 +0.009892 +0.009908 +0.009944 +0.00995 +0.00973 +0.009611 +0.00965 +0.009673 +0.009673 +0.009697 +0.00951 +0.009363 +0.009357 +0.009397 +0.009435 +0.00944 +0.009234 +0.009126 +0.009153 +0.0092 +0.009243 +0.009246 +0.009036 +0.008933 +0.008942 +0.008991 +0.009024 +0.009045 +0.008869 +0.008774 +0.008793 +0.008833 +0.008881 +0.008922 +0.00874 +0.008623 +0.008658 +0.008707 +0.008748 +0.008792 +0.00863 +0.00853 +0.008567 +0.008603 +0.008655 +0.008691 +0.008537 +0.008442 +0.008492 +0.008546 +0.008621 +0.008679 +0.00851 +0.00844 +0.008493 +0.008553 +0.00861 +0.00869 +0.008545 +0.008467 +0.008516 +0.0086 +0.008695 +0.008745 +0.008577 +0.008502 +0.008562 +0.008636 +0.008718 +0.008785 +0.008624 +0.00856 +0.008623 +0.008687 +0.008761 +0.008823 +0.008662 +0.008595 +0.008648 +0.008728 +0.008801 +0.008861 +0.008725 +0.00865 +0.008717 +0.008772 +0.008864 +0.008895 +0.008753 +0.008681 +0.008737 +0.008836 +0.00891 +0.008937 +0.008787 +0.008724 +0.008776 +0.008852 +0.008934 +0.008995 +0.00885 +0.008776 +0.008845 +0.008902 +0.008985 +0.009048 +0.008887 +0.00882 +0.008859 +0.008937 +0.009027 +0.009092 +0.008932 +0.008853 +0.008918 +0.009002 +0.009093 +0.009149 +0.00899 +0.008918 +0.008957 +0.009026 +0.009123 +0.009181 +0.009016 +0.008948 +0.009012 +0.00908 +0.009184 +0.009227 +0.009068 +0.008987 +0.009049 +0.009121 +0.009203 +0.00928 +0.00912 +0.009032 +0.009098 +0.009161 +0.009257 +0.009329 +0.009168 +0.009082 +0.009135 +0.009223 +0.009337 +0.009401 +0.009206 +0.009105 +0.009173 +0.009254 +0.009379 +0.009405 +0.009239 +0.009182 +0.009235 +0.009322 +0.009415 +0.009466 +0.009291 +0.009213 +0.009278 +0.00936 +0.009442 +0.009517 +0.009341 +0.009269 +0.009319 +0.009403 +0.009492 +0.009552 +0.009395 +0.009338 +0.009388 +0.009452 +0.009547 +0.009609 +0.009435 +0.00935 +0.00941 +0.009515 +0.009586 +0.009642 +0.009486 +0.009417 +0.009478 +0.009557 +0.009657 +0.009688 +0.009525 +0.009445 +0.009513 +0.009601 +0.009686 +0.009754 +0.009611 +0.009514 +0.009574 +0.009665 +0.00976 +0.009766 +0.009609 +0.009534 +0.009603 +0.009699 +0.009785 +0.009847 +0.009682 +0.009613 +0.009644 +0.009741 +0.009812 +0.009902 +0.00974 +0.009654 +0.009718 +0.009797 +0.009874 +0.009948 +0.009774 +0.009696 +0.009757 +0.009828 +0.009943 +0.010047 +0.009843 +0.009769 +0.009779 +0.009862 +0.009981 +0.010029 +0.009864 +0.009793 +0.009845 +0.009941 +0.010049 +0.010094 +0.009925 +0.009833 +0.009896 +0.009986 +0.010085 +0.010165 +0.009993 +0.009888 +0.009955 +0.010036 +0.010132 +0.010203 +0.010046 +0.009955 +0.009984 +0.01009 +0.010232 +0.010254 +0.010049 +0.009977 +0.010049 +0.010128 +0.010231 +0.010304 +0.010136 +0.010057 +0.010117 +0.010195 +0.010294 +0.010342 +0.010167 +0.010078 +0.010147 +0.010251 +0.010345 +0.010398 +0.01023 +0.010178 +0.01019 +0.010288 +0.010379 +0.010457 +0.010284 +0.010202 +0.010264 +0.010348 +0.010431 +0.010509 +0.010327 +0.010236 +0.0103 +0.010392 +0.010501 +0.010586 +0.010395 +0.010283 +0.010323 +0.010421 +0.010506 +0.010521 +0.010305 +0.010155 +0.010207 +0.010281 +0.010322 +0.010295 +0.010083 +0.009924 +0.009955 +0.010015 +0.010034 +0.010053 +0.009814 +0.009697 +0.009716 +0.009741 +0.009791 +0.009802 +0.009583 +0.009442 +0.009452 +0.00951 +0.009554 +0.009573 +0.009341 +0.0092 +0.009207 +0.009264 +0.009305 +0.00934 +0.009103 +0.008976 +0.008984 +0.009053 +0.009104 +0.009113 +0.008911 +0.00881 +0.008815 +0.008851 +0.008907 +0.008933 +0.008738 +0.008645 +0.00866 +0.008725 +0.008761 +0.008791 +0.008611 +0.008508 +0.008534 +0.008586 +0.008651 +0.00867 +0.008509 +0.008412 +0.008451 +0.008509 +0.008594 +0.008588 +0.008427 +0.008349 +0.008401 +0.008477 +0.008549 +0.008623 +0.008475 +0.008409 +0.008449 +0.00852 +0.008579 +0.008649 +0.008506 +0.008431 +0.008495 +0.008556 +0.008629 +0.008696 +0.008554 +0.008473 +0.008523 +0.008601 +0.008681 +0.008732 +0.008595 +0.00853 +0.00858 +0.008683 +0.008732 +0.008789 +0.008665 +0.008547 +0.008604 +0.008675 +0.00876 +0.008821 +0.008682 +0.008592 +0.008667 +0.008739 +0.008826 +0.008876 +0.008729 +0.008649 +0.008694 +0.008764 +0.008855 +0.008916 +0.008768 +0.0087 +0.008752 +0.00881 +0.008896 +0.008956 +0.008816 +0.008747 +0.008799 +0.00886 +0.008934 +0.009019 +0.008868 +0.008802 +0.008817 +0.008891 +0.008994 +0.009045 +0.008898 +0.008821 +0.008873 +0.008953 +0.009056 +0.009116 +0.008952 +0.008872 +0.008907 +0.008992 +0.009075 +0.009146 +0.008992 +0.008903 +0.008958 +0.009046 +0.009132 +0.009216 +0.009045 +0.008947 +0.008995 +0.009079 +0.009171 +0.00924 +0.009093 +0.009025 +0.009057 +0.009126 +0.009217 +0.009282 +0.009121 +0.009045 +0.009089 +0.009183 +0.009279 +0.00934 +0.009185 +0.009107 +0.009141 +0.009226 +0.009313 +0.00936 +0.00921 +0.009138 +0.0092 +0.009297 +0.009361 +0.00943 +0.009268 +0.00919 +0.009234 +0.009306 +0.009399 +0.009477 +0.009305 +0.00922 +0.009283 +0.009384 +0.009481 +0.009523 +0.009367 +0.009279 +0.009305 +0.009418 +0.009491 +0.009556 +0.00941 +0.009332 +0.009373 +0.00945 +0.00955 +0.009646 +0.009462 +0.00939 +0.009399 +0.009494 +0.009606 +0.009668 +0.009505 +0.009406 +0.009473 +0.009551 +0.009637 +0.00972 +0.009541 +0.009453 +0.009535 +0.00962 +0.009709 +0.009768 +0.009588 +0.009501 +0.009562 +0.009652 +0.009737 +0.009803 +0.009641 +0.009581 +0.009636 +0.009689 +0.009785 +0.009876 +0.009676 +0.009598 +0.009657 +0.009753 +0.009862 +0.0099 +0.009734 +0.00966 +0.009715 +0.009794 +0.0099 +0.009954 +0.009786 +0.009719 +0.009772 +0.009864 +0.00995 +0.009996 +0.009827 +0.009747 +0.009802 +0.009933 +0.009983 +0.010075 +0.009893 +0.009804 +0.00988 +0.009949 +0.010025 +0.010103 +0.009921 +0.009844 +0.009925 +0.009983 +0.010094 +0.010168 +0.00999 +0.009891 +0.009959 +0.01004 +0.010147 +0.010229 +0.010044 +0.009955 +0.010002 +0.010105 +0.010238 +0.010245 +0.010067 +0.009983 +0.010055 +0.010178 +0.010266 +0.01034 +0.01014 +0.010022 +0.010099 +0.0102 +0.010298 +0.010349 +0.010183 +0.010095 +0.010162 +0.010258 +0.01035 +0.010405 +0.01025 +0.010141 +0.010212 +0.010328 +0.010449 +0.010493 +0.01026 +0.010176 +0.010254 +0.010349 +0.010427 +0.01048 +0.01029 +0.010179 +0.010246 +0.010297 +0.010339 +0.010341 +0.010125 +0.010002 +0.010004 +0.010053 +0.010099 +0.010124 +0.009893 +0.009746 +0.009765 +0.009798 +0.009839 +0.009884 +0.009624 +0.009502 +0.009535 +0.009545 +0.009581 +0.009578 +0.009358 +0.00925 +0.009252 +0.009284 +0.009325 +0.009341 +0.009134 +0.009011 +0.009037 +0.009058 +0.009098 +0.009107 +0.008905 +0.008798 +0.008822 +0.008861 +0.008887 +0.008914 +0.008733 +0.008653 +0.008682 +0.008718 +0.008743 +0.008752 +0.008586 +0.00849 +0.008528 +0.008561 +0.008601 +0.008636 +0.008483 +0.008385 +0.008404 +0.008449 +0.008508 +0.008549 +0.008382 +0.008296 +0.008333 +0.008383 +0.00845 +0.008515 +0.008367 +0.008294 +0.008333 +0.008392 +0.008471 +0.008536 +0.00839 +0.008329 +0.008373 +0.008457 +0.008528 +0.008578 +0.008432 +0.008359 +0.00842 +0.008482 +0.008552 +0.008615 +0.008474 +0.008401 +0.008447 +0.008535 +0.008602 +0.008661 +0.008513 +0.008447 +0.008523 +0.00859 +0.008632 +0.008707 +0.008551 +0.00849 +0.008549 +0.008621 +0.008691 +0.008737 +0.008608 +0.008534 +0.008603 +0.008654 +0.008731 +0.008778 +0.008636 +0.008576 +0.008629 +0.00869 +0.008771 +0.00885 +0.008693 +0.008614 +0.008672 +0.008741 +0.008825 +0.008889 +0.008741 +0.008663 +0.008722 +0.008791 +0.008867 +0.008933 +0.008769 +0.008705 +0.008757 +0.008818 +0.008917 +0.008983 +0.008848 +0.008792 +0.008815 +0.008864 +0.008935 +0.009006 +0.008863 +0.008777 +0.008836 +0.008917 +0.009006 +0.00908 +0.008917 +0.008846 +0.008897 +0.008962 +0.009038 +0.0091 +0.008951 +0.008873 +0.008934 +0.009017 +0.009087 +0.009157 +0.008997 +0.008918 +0.008972 +0.009057 +0.009153 +0.00922 +0.009041 +0.008976 +0.009039 +0.009095 +0.009176 +0.009237 +0.009081 +0.009017 +0.009067 +0.009136 +0.009227 +0.009302 +0.009131 +0.00907 +0.009128 +0.009192 +0.009261 +0.009339 +0.009177 +0.009103 +0.009155 +0.009247 +0.009316 +0.009397 +0.009251 +0.009171 +0.009237 +0.009281 +0.009359 +0.009408 +0.009262 +0.009189 +0.00924 +0.009338 +0.009433 +0.00948 +0.009318 +0.009233 +0.009291 +0.00937 +0.009466 +0.009546 +0.009357 +0.009289 +0.009364 +0.00944 +0.009512 +0.009573 +0.00941 +0.009322 +0.009383 +0.00947 +0.009576 +0.009644 +0.009471 +0.009384 +0.009448 +0.009527 +0.009584 +0.009655 +0.009503 +0.009428 +0.009474 +0.009554 +0.009667 +0.009733 +0.009569 +0.009491 +0.00954 +0.009628 +0.009696 +0.009756 +0.009593 +0.009512 +0.009576 +0.009654 +0.009757 +0.009827 +0.009676 +0.009586 +0.009615 +0.009695 +0.009796 +0.00987 +0.00971 +0.009628 +0.009663 +0.009768 +0.009864 +0.009909 +0.009749 +0.009656 +0.009712 +0.009814 +0.009914 +0.009979 +0.009811 +0.009699 +0.009765 +0.009859 +0.00995 +0.010018 +0.009843 +0.00977 +0.009868 +0.009916 +0.010034 +0.010055 +0.009871 +0.009797 +0.009858 +0.009954 +0.010062 +0.01014 +0.009924 +0.009865 +0.009911 +0.010002 +0.010112 +0.010159 +0.009992 +0.009915 +0.009983 +0.010087 +0.010174 +0.0102 +0.010038 +0.009958 +0.01002 +0.010149 +0.010192 +0.010264 +0.010085 +0.010021 +0.010091 +0.010154 +0.010251 +0.010315 +0.010147 +0.010052 +0.010124 +0.010213 +0.010341 +0.010392 +0.010195 +0.010112 +0.010165 +0.010251 +0.010357 +0.010424 +0.010227 +0.01012 +0.010198 +0.010286 +0.010352 +0.010338 +0.010108 +0.009986 +0.010017 +0.010083 +0.010117 +0.010119 +0.009912 +0.009778 +0.009781 +0.009803 +0.009846 +0.009849 +0.009654 +0.009493 +0.00949 +0.00955 +0.009591 +0.009589 +0.009369 +0.009227 +0.009223 +0.009276 +0.009314 +0.00934 +0.009105 +0.00897 +0.008982 +0.009024 +0.009067 +0.009075 +0.008865 +0.00875 +0.008777 +0.008814 +0.008856 +0.008876 +0.008688 +0.008584 +0.0086 +0.008655 +0.00869 +0.008716 +0.008531 +0.008439 +0.008474 +0.008515 +0.008569 +0.008593 +0.008427 +0.008325 +0.008365 +0.008411 +0.008477 +0.008482 +0.00831 +0.008216 +0.00826 +0.008328 +0.008384 +0.008436 +0.00827 +0.00819 +0.008239 +0.00832 +0.008403 +0.008419 +0.008274 +0.008223 +0.008263 +0.008338 +0.008424 +0.008469 +0.008338 +0.008263 +0.008313 +0.008377 +0.008463 +0.008524 +0.008371 +0.008305 +0.008355 +0.008435 +0.008544 +0.008562 +0.008407 +0.008359 +0.008382 +0.008464 +0.008543 +0.008615 +0.008448 +0.008391 +0.008438 +0.008515 +0.008596 +0.008659 +0.008506 +0.008415 +0.008475 +0.008548 +0.008627 +0.00869 +0.008549 +0.008465 +0.008527 +0.00859 +0.008686 +0.008731 +0.008584 +0.008516 +0.008566 +0.008659 +0.008735 +0.008769 +0.008636 +0.008564 +0.0086 +0.008695 +0.008754 +0.00881 +0.008664 +0.008585 +0.008644 +0.008716 +0.008822 +0.008868 +0.008719 +0.008657 +0.0087 +0.008765 +0.008848 +0.008917 +0.00876 +0.008685 +0.008737 +0.008809 +0.008891 +0.008964 +0.008812 +0.008753 +0.008815 +0.008855 +0.008928 +0.008993 +0.008842 +0.008772 +0.008822 +0.008916 +0.008973 +0.009046 +0.008885 +0.008821 +0.008889 +0.008945 +0.009019 +0.009084 +0.008931 +0.008861 +0.00891 +0.008998 +0.009067 +0.009146 +0.009 +0.008913 +0.008959 +0.009044 +0.009118 +0.009198 +0.00904 +0.008935 +0.008994 +0.009076 +0.009158 +0.009234 +0.009078 +0.008985 +0.009041 +0.009138 +0.009191 +0.009277 +0.009126 +0.009035 +0.009104 +0.009189 +0.009258 +0.009318 +0.00916 +0.009086 +0.00914 +0.009215 +0.009308 +0.009364 +0.009216 +0.00915 +0.009202 +0.00931 +0.009336 +0.009425 +0.009233 +0.009165 +0.009229 +0.009301 +0.0094 +0.009481 +0.009318 +0.009234 +0.009279 +0.00937 +0.009428 +0.009497 +0.009351 +0.009255 +0.009324 +0.009423 +0.009492 +0.009565 +0.009397 +0.009314 +0.009362 +0.009442 +0.009549 +0.00962 +0.009465 +0.009353 +0.009427 +0.0095 +0.009606 +0.009651 +0.009481 +0.009399 +0.009462 +0.009526 +0.009626 +0.009717 +0.009549 +0.009464 +0.009533 +0.009613 +0.009681 +0.009742 +0.009586 +0.009494 +0.009553 +0.00965 +0.00973 +0.009811 +0.009659 +0.009575 +0.009648 +0.009693 +0.00976 +0.009831 +0.009671 +0.009596 +0.009674 +0.009721 +0.009845 +0.009911 +0.009738 +0.009663 +0.009702 +0.009768 +0.009882 +0.009956 +0.009768 +0.00969 +0.009755 +0.009844 +0.009938 +0.010002 +0.009825 +0.009742 +0.009804 +0.009911 +0.009993 +0.01004 +0.00988 +0.009789 +0.009847 +0.00993 +0.010038 +0.010113 +0.009914 +0.00983 +0.0099 +0.009999 +0.010105 +0.010172 +0.009975 +0.009876 +0.009941 +0.010037 +0.010153 +0.010192 +0.010021 +0.009954 +0.010011 +0.010108 +0.010232 +0.010253 +0.010053 +0.009961 +0.01004 +0.010141 +0.010229 +0.010305 +0.010149 +0.010046 +0.010147 +0.010195 +0.010261 +0.010333 +0.010173 +0.010075 +0.010147 +0.010253 +0.010336 +0.010398 +0.010204 +0.010111 +0.01015 +0.010227 +0.010298 +0.010351 +0.010094 +0.009968 +0.009998 +0.01005 +0.010069 +0.010084 +0.009875 +0.009752 +0.009757 +0.009818 +0.009848 +0.009869 +0.009631 +0.0095 +0.009519 +0.00956 +0.009587 +0.009601 +0.009392 +0.009274 +0.009284 +0.00931 +0.009342 +0.009348 +0.009131 +0.009017 +0.009037 +0.009091 +0.009116 +0.009109 +0.008932 +0.008795 +0.008819 +0.008861 +0.008881 +0.008898 +0.00872 +0.008602 +0.008638 +0.008687 +0.008738 +0.008746 +0.00857 +0.008477 +0.008504 +0.008554 +0.008604 +0.008631 +0.008449 +0.008358 +0.008394 +0.008466 +0.0085 +0.008515 +0.008359 +0.008273 +0.008333 +0.008378 +0.008419 +0.008446 +0.008299 +0.008231 +0.008295 +0.008352 +0.008425 +0.008472 +0.008315 +0.008257 +0.008305 +0.008368 +0.008452 +0.008515 +0.008378 +0.008297 +0.00834 +0.008417 +0.008501 +0.008564 +0.008411 +0.008336 +0.008388 +0.00846 +0.008541 +0.008619 +0.008465 +0.008396 +0.00845 +0.008499 +0.008581 +0.008644 +0.008498 +0.00842 +0.00847 +0.008552 +0.008637 +0.008695 +0.008537 +0.008471 +0.008521 +0.008581 +0.008665 +0.008722 +0.008582 +0.008508 +0.008557 +0.008632 +0.008724 +0.008783 +0.008632 +0.00856 +0.008612 +0.008677 +0.008749 +0.008816 +0.008674 +0.008617 +0.008642 +0.00871 +0.00879 +0.008869 +0.008723 +0.008644 +0.008697 +0.008768 +0.008851 +0.008909 +0.008733 +0.008672 +0.008733 +0.008803 +0.008894 +0.008956 +0.008795 +0.008723 +0.008781 +0.008856 +0.008925 +0.008997 +0.00885 +0.008768 +0.008835 +0.008909 +0.008998 +0.00906 +0.008893 +0.008825 +0.008848 +0.008928 +0.009021 +0.009078 +0.008929 +0.008865 +0.008925 +0.009001 +0.009079 +0.009123 +0.008973 +0.008894 +0.008949 +0.009034 +0.009114 +0.009172 +0.009029 +0.008946 +0.008995 +0.009081 +0.009171 +0.009216 +0.009071 +0.008992 +0.009049 +0.009162 +0.009218 +0.009263 +0.009098 +0.009045 +0.009084 +0.009159 +0.009252 +0.009308 +0.009165 +0.009085 +0.009141 +0.009235 +0.009315 +0.009349 +0.009197 +0.009117 +0.009174 +0.009263 +0.009354 +0.009401 +0.009267 +0.009187 +0.009238 +0.009314 +0.009395 +0.009446 +0.009317 +0.009208 +0.00926 +0.009344 +0.009448 +0.009506 +0.009358 +0.009256 +0.00931 +0.009396 +0.009476 +0.009548 +0.009396 +0.009303 +0.009377 +0.009472 +0.009548 +0.009595 +0.009438 +0.009352 +0.009406 +0.009488 +0.009596 +0.009637 +0.009478 +0.00943 +0.009487 +0.009581 +0.009635 +0.009671 +0.009519 +0.009439 +0.009503 +0.009592 +0.009697 +0.009755 +0.009569 +0.009486 +0.009551 +0.00963 +0.009734 +0.0098 +0.009631 +0.009547 +0.009615 +0.009717 +0.009791 +0.009839 +0.009683 +0.00959 +0.009643 +0.009735 +0.009836 +0.009937 +0.009718 +0.009645 +0.00971 +0.009793 +0.009864 +0.009934 +0.009768 +0.009689 +0.009743 +0.009823 +0.009953 +0.010005 +0.009833 +0.009756 +0.009801 +0.009876 +0.009983 +0.010037 +0.00986 +0.009794 +0.009855 +0.009931 +0.010027 +0.010091 +0.009959 +0.009834 +0.009905 +0.009977 +0.010067 +0.01016 +0.009974 +0.009879 +0.00995 +0.010028 +0.010122 +0.010207 +0.010012 +0.009927 +0.010017 +0.010095 +0.010203 +0.010267 +0.010051 +0.009982 +0.010048 +0.010133 +0.010225 +0.010296 +0.010123 +0.010069 +0.010092 +0.010171 +0.01028 +0.010376 +0.010157 +0.010078 +0.010142 +0.010259 +0.010329 +0.0104 +0.010221 +0.010123 +0.010197 +0.010281 +0.01036 +0.010441 +0.010253 +0.010134 +0.010154 +0.010193 +0.01025 +0.010266 +0.010064 +0.009954 +0.009967 +0.009993 +0.010055 +0.010036 +0.00983 +0.009684 +0.009705 +0.009716 +0.009754 +0.00977 +0.009552 +0.009423 +0.009449 +0.009482 +0.009523 +0.009519 +0.00932 +0.00918 +0.009181 +0.009216 +0.009255 +0.009275 +0.009087 +0.008953 +0.008975 +0.009015 +0.009068 +0.00905 +0.008861 +0.008751 +0.008777 +0.008819 +0.008863 +0.008894 +0.008719 +0.008631 +0.008609 +0.008655 +0.008716 +0.008736 +0.008573 +0.008473 +0.008502 +0.008542 +0.008612 +0.008646 +0.008466 +0.008372 +0.008387 +0.008441 +0.008481 +0.008533 +0.008375 +0.008295 +0.008339 +0.008393 +0.008461 +0.008501 +0.008355 +0.008285 +0.008334 +0.008388 +0.008466 +0.008519 +0.008393 +0.00832 +0.008363 +0.008431 +0.008514 +0.008569 +0.008429 +0.008365 +0.008412 +0.008477 +0.008563 +0.008627 +0.008473 +0.008402 +0.008453 +0.008542 +0.008611 +0.00865 +0.008517 +0.008441 +0.008522 +0.008575 +0.008636 +0.008698 +0.008592 +0.008475 +0.008544 +0.008608 +0.00869 +0.008742 +0.008588 +0.008521 +0.008574 +0.008657 +0.008735 +0.008789 +0.00864 +0.008586 +0.008629 +0.008707 +0.008793 +0.008832 +0.008671 +0.008607 +0.008664 +0.008741 +0.008813 +0.008889 +0.00874 +0.008671 +0.008708 +0.008775 +0.008851 +0.008922 +0.008769 +0.008703 +0.008747 +0.008822 +0.008931 +0.008974 +0.008824 +0.008742 +0.008786 +0.008867 +0.00895 +0.009021 +0.008867 +0.008783 +0.008848 +0.008911 +0.009003 +0.009058 +0.00891 +0.008828 +0.008881 +0.008963 +0.009051 +0.00913 +0.008958 +0.008882 +0.008927 +0.008994 +0.009086 +0.009151 +0.00899 +0.008934 +0.008987 +0.009034 +0.009132 +0.009203 +0.009054 +0.008971 +0.009029 +0.009091 +0.009173 +0.009249 +0.00909 +0.009013 +0.009049 +0.009132 +0.009249 +0.009298 +0.009139 +0.009068 +0.009128 +0.009219 +0.009256 +0.009315 +0.009165 +0.009089 +0.009168 +0.009221 +0.009317 +0.00939 +0.009217 +0.009152 +0.009191 +0.009275 +0.009369 +0.009425 +0.009291 +0.009196 +0.009256 +0.009326 +0.009412 +0.009477 +0.009312 +0.009239 +0.009286 +0.009367 +0.009482 +0.009551 +0.009391 +0.00928 +0.009339 +0.009402 +0.009503 +0.009566 +0.009401 +0.009322 +0.009383 +0.009453 +0.009557 +0.00963 +0.00947 +0.009369 +0.009426 +0.00952 +0.00961 +0.009678 +0.00952 +0.009426 +0.009463 +0.009568 +0.009644 +0.009714 +0.009553 +0.00947 +0.009558 +0.009612 +0.00969 +0.009765 +0.009629 +0.009515 +0.009563 +0.009659 +0.009743 +0.009811 +0.009644 +0.009556 +0.009613 +0.009707 +0.009832 +0.009878 +0.009697 +0.009623 +0.009669 +0.009745 +0.009852 +0.009905 +0.009736 +0.009682 +0.009728 +0.009817 +0.009913 +0.009975 +0.009811 +0.009705 +0.009749 +0.009839 +0.009985 +0.009991 +0.009825 +0.009757 +0.009807 +0.009906 +0.010003 +0.010061 +0.009897 +0.009808 +0.009889 +0.009963 +0.010038 +0.010113 +0.009928 +0.009849 +0.009925 +0.009993 +0.01011 +0.01017 +0.009987 +0.00992 +0.009985 +0.010076 +0.010178 +0.010198 +0.01002 +0.009971 +0.010007 +0.010088 +0.010209 +0.01028 +0.010085 +0.010014 +0.010061 +0.010169 +0.010255 +0.010308 +0.010139 +0.010041 +0.010114 +0.010232 +0.010323 +0.010385 +0.010201 +0.010094 +0.010156 +0.010259 +0.010395 +0.010414 +0.010233 +0.010151 +0.010212 +0.010313 +0.0104 +0.010437 +0.010266 +0.010153 +0.010197 +0.01027 +0.010327 +0.010312 +0.010112 +0.009978 +0.009995 +0.01005 +0.010092 +0.010089 +0.009878 +0.009743 +0.009787 +0.009826 +0.009851 +0.009884 +0.009625 +0.009485 +0.009507 +0.00954 +0.009584 +0.009592 +0.009383 +0.009257 +0.00926 +0.009301 +0.009337 +0.009355 +0.00914 +0.009025 +0.009046 +0.009058 +0.00912 +0.009139 +0.008921 +0.008803 +0.008826 +0.008866 +0.008897 +0.008917 +0.008741 +0.008639 +0.008696 +0.008727 +0.008749 +0.008759 +0.008592 +0.008487 +0.008522 +0.008559 +0.008608 +0.008643 +0.008473 +0.00839 +0.008411 +0.008465 +0.008514 +0.008542 +0.008386 +0.00831 +0.008336 +0.008398 +0.008461 +0.008505 +0.008372 +0.008296 +0.008345 +0.008397 +0.008471 +0.008543 +0.008401 +0.008334 +0.008392 +0.00843 +0.008521 +0.008627 +0.008425 +0.00837 +0.008411 +0.008476 +0.008559 +0.008622 +0.008467 +0.008393 +0.008448 +0.008537 +0.008612 +0.008663 +0.008533 +0.008456 +0.008507 +0.008583 +0.008651 +0.008717 +0.00856 +0.008485 +0.008546 +0.008608 +0.008684 +0.008761 +0.008622 +0.008564 +0.008587 +0.008661 +0.008735 +0.008777 +0.008661 +0.008569 +0.008615 +0.008692 +0.0088 +0.008826 +0.008687 +0.008615 +0.008674 +0.008743 +0.008823 +0.008884 +0.008736 +0.008659 +0.008706 +0.008802 +0.008875 +0.008922 +0.008783 +0.008701 +0.008751 +0.008835 +0.008915 +0.008991 +0.008834 +0.008737 +0.008792 +0.008877 +0.008967 +0.009031 +0.008852 +0.008786 +0.008843 +0.008913 +0.008992 +0.009051 +0.008917 +0.008837 +0.008893 +0.008983 +0.009056 +0.00914 +0.008937 +0.008873 +0.008931 +0.008996 +0.009096 +0.009148 +0.008991 +0.008921 +0.008976 +0.009078 +0.009155 +0.009191 +0.009029 +0.008959 +0.00902 +0.009104 +0.00919 +0.00926 +0.009099 +0.009033 +0.00905 +0.009135 +0.009231 +0.009286 +0.009127 +0.009058 +0.00911 +0.009211 +0.0093 +0.009353 +0.009173 +0.009095 +0.009154 +0.009233 +0.009317 +0.009392 +0.009231 +0.009184 +0.009215 +0.009307 +0.009371 +0.009432 +0.009258 +0.009176 +0.009241 +0.009325 +0.00942 +0.009478 +0.009317 +0.009238 +0.009301 +0.00938 +0.009474 +0.009523 +0.00936 +0.00929 +0.009329 +0.009425 +0.009521 +0.009591 +0.009405 +0.009337 +0.009402 +0.009497 +0.009587 +0.009606 +0.009442 +0.009372 +0.009437 +0.009537 +0.009624 +0.009685 +0.0095 +0.009421 +0.009478 +0.00957 +0.009659 +0.009733 +0.00956 +0.009478 +0.009537 +0.009623 +0.009718 +0.009756 +0.009596 +0.009519 +0.009576 +0.009658 +0.009762 +0.009838 +0.009685 +0.009572 +0.009634 +0.009712 +0.009808 +0.009846 +0.009694 +0.009607 +0.009677 +0.009771 +0.00985 +0.009916 +0.009735 +0.00966 +0.009739 +0.00982 +0.009895 +0.009978 +0.009789 +0.009715 +0.009786 +0.009859 +0.00995 +0.010022 +0.009852 +0.009791 +0.009814 +0.009896 +0.010001 +0.010069 +0.009919 +0.009835 +0.009859 +0.009959 +0.01005 +0.01011 +0.009945 +0.009859 +0.00991 +0.010013 +0.010104 +0.010192 +0.010044 +0.009899 +0.00995 +0.010054 +0.010156 +0.010223 +0.010045 +0.009961 +0.010068 +0.010126 +0.010207 +0.010266 +0.010078 +0.010002 +0.010063 +0.010155 +0.010268 +0.010337 +0.010155 +0.010075 +0.010115 +0.010221 +0.010331 +0.010353 +0.010187 +0.010113 +0.010167 +0.01026 +0.010364 +0.010417 +0.010251 +0.010167 +0.010244 +0.010334 +0.010407 +0.010472 +0.010285 +0.010205 +0.010264 +0.010313 +0.010407 +0.010433 +0.010203 +0.010075 +0.010145 +0.010175 +0.010225 +0.010248 +0.01003 +0.009887 +0.009919 +0.009934 +0.009982 +0.010003 +0.009759 +0.009631 +0.009655 +0.009702 +0.009794 +0.009753 +0.009522 +0.009386 +0.009402 +0.009446 +0.009483 +0.009482 +0.009278 +0.009155 +0.009164 +0.009209 +0.009267 +0.009252 +0.009076 +0.008944 +0.008959 +0.009006 +0.009052 +0.009077 +0.008886 +0.00877 +0.008793 +0.008832 +0.008889 +0.008906 +0.008724 +0.008635 +0.008663 +0.008693 +0.008755 +0.008787 +0.008602 +0.008514 +0.008553 +0.008575 +0.00864 +0.00867 +0.008496 +0.008407 +0.008462 +0.008506 +0.008574 +0.008613 +0.008461 +0.008382 +0.008421 +0.008497 +0.008573 +0.008635 +0.008483 +0.008423 +0.008459 +0.008539 +0.008607 +0.008679 +0.008543 +0.008471 +0.008495 +0.008568 +0.00865 +0.008718 +0.008574 +0.008507 +0.008551 +0.008626 +0.008734 +0.008739 +0.008607 +0.008537 +0.008591 +0.008674 +0.008731 +0.008803 +0.008661 +0.008588 +0.008655 +0.008712 +0.008786 +0.008843 +0.008694 +0.008625 +0.008669 +0.008745 +0.008839 +0.008904 +0.008762 +0.008674 +0.008719 +0.008806 +0.008877 +0.008945 +0.008776 +0.008702 +0.008758 +0.008831 +0.008915 +0.00901 +0.008824 +0.008744 +0.008797 +0.008879 +0.008966 +0.009024 +0.008869 +0.008807 +0.008843 +0.008931 +0.009026 +0.009087 +0.008916 +0.008838 +0.008897 +0.008981 +0.009083 +0.009107 +0.008948 +0.008881 +0.008942 +0.009034 +0.009116 +0.009163 +0.009009 +0.008917 +0.008974 +0.009054 +0.009156 +0.009225 +0.00904 +0.008967 +0.009017 +0.009106 +0.009198 +0.009266 +0.009096 +0.00902 +0.009082 +0.009147 +0.00924 +0.009315 +0.009155 +0.009086 +0.009124 +0.009192 +0.009285 +0.009345 +0.009197 +0.009125 +0.009155 +0.009245 +0.009343 +0.009399 +0.009229 +0.009163 +0.009201 +0.009295 +0.009393 +0.009441 +0.009275 +0.009208 +0.009263 +0.00934 +0.009443 +0.009499 +0.009313 +0.009241 +0.009311 +0.009397 +0.009506 +0.009541 +0.009363 +0.00928 +0.009337 +0.009436 +0.009523 +0.009595 +0.009432 +0.009342 +0.009401 +0.009475 +0.009577 +0.009631 +0.009458 +0.009383 +0.009439 +0.009529 +0.009622 +0.009685 +0.009524 +0.009444 +0.009507 +0.00958 +0.009676 +0.009723 +0.009578 +0.009493 +0.009538 +0.009619 +0.00971 +0.009774 +0.009622 +0.009545 +0.009592 +0.009683 +0.00978 +0.009803 +0.009646 +0.009566 +0.009635 +0.009731 +0.009806 +0.009879 +0.009715 +0.009626 +0.009676 +0.009765 +0.009865 +0.009928 +0.009759 +0.009688 +0.009786 +0.009822 +0.009936 +0.009954 +0.00979 +0.009718 +0.009762 +0.009859 +0.009975 +0.010024 +0.009857 +0.009801 +0.009836 +0.009906 +0.010009 +0.010074 +0.009895 +0.009809 +0.009881 +0.009966 +0.010087 +0.010154 +0.009959 +0.00986 +0.009929 +0.010036 +0.010135 +0.010166 +0.009993 +0.009927 +0.009979 +0.010067 +0.010172 +0.010243 +0.01004 +0.009963 +0.010036 +0.010107 +0.010223 +0.010278 +0.010118 +0.010028 +0.010082 +0.010179 +0.010275 +0.010314 +0.010151 +0.010063 +0.010122 +0.010226 +0.010369 +0.010379 +0.010192 +0.01011 +0.010171 +0.010273 +0.010392 +0.010425 +0.010262 +0.010179 +0.010239 +0.010324 +0.010411 +0.010484 +0.010306 +0.010211 +0.010266 +0.010384 +0.010509 +0.010554 +0.010375 +0.010262 +0.010317 +0.010413 +0.010538 +0.010562 +0.010344 +0.010234 +0.010248 +0.010298 +0.010378 +0.010402 +0.010205 +0.010025 +0.010061 +0.010089 +0.010151 +0.010151 +0.009912 +0.009784 +0.009779 +0.009822 +0.009866 +0.009879 +0.009648 +0.009527 +0.009526 +0.009574 +0.009616 +0.009638 +0.009434 +0.009276 +0.009279 +0.009311 +0.009351 +0.00938 +0.009175 +0.009072 +0.009085 +0.009122 +0.009158 +0.009179 +0.008985 +0.008864 +0.008889 +0.008929 +0.008981 +0.009005 +0.008831 +0.008732 +0.008758 +0.008786 +0.008844 +0.008884 +0.008707 +0.008593 +0.008636 +0.008673 +0.008763 +0.008771 +0.008602 +0.008482 +0.008513 +0.008568 +0.008626 +0.008668 +0.008511 +0.008426 +0.00846 +0.00853 +0.008613 +0.008665 +0.008507 +0.008434 +0.008464 +0.008544 +0.008624 +0.008686 +0.008546 +0.008466 +0.008516 +0.008589 +0.008688 +0.008741 +0.008591 +0.008531 +0.008569 +0.008651 +0.008725 +0.008756 +0.008615 +0.008548 +0.008623 +0.00867 +0.008752 +0.008815 +0.008678 +0.008604 +0.008657 +0.008718 +0.008805 +0.008853 +0.008728 +0.008645 +0.008697 +0.008762 +0.008843 +0.008911 +0.008755 +0.008681 +0.008737 +0.008809 +0.00889 +0.008957 +0.008811 +0.008743 +0.008814 +0.008856 +0.008944 +0.008986 +0.008836 +0.00877 +0.008815 +0.008905 +0.008975 +0.009044 +0.008881 +0.008819 +0.008877 +0.008949 +0.009038 +0.009095 +0.00893 +0.008854 +0.008903 +0.008993 +0.009066 +0.009132 +0.00898 +0.008915 +0.008963 +0.009043 +0.009132 +0.009202 +0.009026 +0.008929 +0.008985 +0.009067 +0.009164 +0.009225 +0.009066 +0.009002 +0.009054 +0.00913 +0.009216 +0.009282 +0.009099 +0.009022 +0.009085 +0.00917 +0.009253 +0.009329 +0.009173 +0.009084 +0.009125 +0.009208 +0.009299 +0.009358 +0.009209 +0.009123 +0.009186 +0.009307 +0.00939 +0.009403 +0.009247 +0.009161 +0.009218 +0.009294 +0.009389 +0.009453 +0.009302 +0.009205 +0.009283 +0.009377 +0.009452 +0.009517 +0.009357 +0.00925 +0.009309 +0.009393 +0.009488 +0.009554 +0.009386 +0.009331 +0.009356 +0.00945 +0.009547 +0.009617 +0.009463 +0.009344 +0.009396 +0.009515 +0.009575 +0.009663 +0.009484 +0.009395 +0.009465 +0.009534 +0.009637 +0.009705 +0.009541 +0.009437 +0.009501 +0.009589 +0.00969 +0.00976 +0.00958 +0.009485 +0.009552 +0.009645 +0.00974 +0.009787 +0.009629 +0.009556 +0.009645 +0.009694 +0.00978 +0.009842 +0.009651 +0.009613 +0.009637 +0.009725 +0.009831 +0.009885 +0.009722 +0.009661 +0.009708 +0.009792 +0.009903 +0.009938 +0.009756 +0.009681 +0.009737 +0.009828 +0.009924 +0.009998 +0.009818 +0.00974 +0.0098 +0.009916 +0.009991 +0.010029 +0.009863 +0.009779 +0.009855 +0.009942 +0.010014 +0.010096 +0.009914 +0.009832 +0.009909 +0.009974 +0.010078 +0.010146 +0.009972 +0.009898 +0.009971 +0.010024 +0.010116 +0.010179 +0.010037 +0.009927 +0.009983 +0.010091 +0.010222 +0.010279 +0.010069 +0.00997 +0.01002 +0.010133 +0.010226 +0.010291 +0.010122 +0.010016 +0.010094 +0.010208 +0.010298 +0.010348 +0.010174 +0.010068 +0.010139 +0.010238 +0.010349 +0.01039 +0.010218 +0.010154 +0.010205 +0.010295 +0.010402 +0.010473 +0.010251 +0.010195 +0.010221 +0.010327 +0.010425 +0.010509 +0.01033 +0.010246 +0.0103 +0.010404 +0.010473 +0.010541 +0.010364 +0.010279 +0.010358 +0.010431 +0.010544 +0.010597 +0.01042 +0.010319 +0.010364 +0.010456 +0.010534 +0.010608 +0.010338 +0.010184 +0.010204 +0.010294 +0.010325 +0.010335 +0.010109 +0.009973 +0.010021 +0.010066 +0.010102 +0.010091 +0.009852 +0.009726 +0.009736 +0.009784 +0.009839 +0.009828 +0.00961 +0.009492 +0.009514 +0.009549 +0.009586 +0.009587 +0.009379 +0.009237 +0.009247 +0.009295 +0.009332 +0.009348 +0.009139 +0.009044 +0.009044 +0.009099 +0.00914 +0.009161 +0.008973 +0.00885 +0.008883 +0.008936 +0.009006 +0.00901 +0.008808 +0.00872 +0.00874 +0.008804 +0.008841 +0.008877 +0.008697 +0.008594 +0.008637 +0.008692 +0.008781 +0.008783 +0.00859 +0.008492 +0.008539 +0.008604 +0.008669 +0.008712 +0.008536 +0.008469 +0.00851 +0.008602 +0.008686 +0.008728 +0.008573 +0.008493 +0.008543 +0.00862 +0.008701 +0.008764 +0.008628 +0.00853 +0.008603 +0.008677 +0.008767 +0.008811 +0.008668 +0.008585 +0.00863 +0.008735 +0.008813 +0.008851 +0.008688 +0.008624 +0.008673 +0.008749 +0.008848 +0.008887 +0.008745 +0.008673 +0.008713 +0.008801 +0.008885 +0.008933 +0.00879 +0.00873 +0.008758 +0.008843 +0.008921 +0.008991 +0.008838 +0.008758 +0.008806 +0.008876 +0.00897 +0.009033 +0.008882 +0.008827 +0.008878 +0.008937 +0.009006 +0.009086 +0.00891 +0.008843 +0.008892 +0.008966 +0.009059 +0.009116 +0.008977 +0.008908 +0.008952 +0.009034 +0.009097 +0.009162 +0.009001 +0.008924 +0.008983 +0.009067 +0.009137 +0.009207 +0.009065 +0.008985 +0.009033 +0.00911 +0.009202 +0.009286 +0.009102 +0.009016 +0.009066 +0.009167 +0.009247 +0.009317 +0.009133 +0.009057 +0.009123 +0.009194 +0.009289 +0.009362 +0.009182 +0.009125 +0.009178 +0.009255 +0.009332 +0.009383 +0.009243 +0.009149 +0.009209 +0.009292 +0.009376 +0.009441 +0.009293 +0.009203 +0.009286 +0.009337 +0.009424 +0.009473 +0.009325 +0.009256 +0.0093 +0.0094 +0.009495 +0.009529 +0.00937 +0.009299 +0.009346 +0.009426 +0.009514 +0.009594 +0.00942 +0.009379 +0.009416 +0.009497 +0.009564 +0.009621 +0.009465 +0.009377 +0.009433 +0.009532 +0.009627 +0.009717 +0.009513 +0.00944 +0.009499 +0.009582 +0.00966 +0.009719 +0.009555 +0.009485 +0.009529 +0.009619 +0.009739 +0.00979 +0.009623 +0.009543 +0.009594 +0.009661 +0.009765 +0.009838 +0.009656 +0.009567 +0.009627 +0.009718 +0.009828 +0.009895 +0.009735 +0.009656 +0.009679 +0.00977 +0.009846 +0.009918 +0.009748 +0.009678 +0.00972 +0.009816 +0.009909 +0.009985 +0.009809 +0.009713 +0.009779 +0.009874 +0.009964 +0.010035 +0.009874 +0.00977 +0.009824 +0.009925 +0.010006 +0.010076 +0.009901 +0.009826 +0.009919 +0.009967 +0.010072 +0.010133 +0.009967 +0.009858 +0.009916 +0.010013 +0.010118 +0.010167 +0.010011 +0.009924 +0.009973 +0.010089 +0.010188 +0.010214 +0.010047 +0.009976 +0.010021 +0.010115 +0.010222 +0.01028 +0.010115 +0.010034 +0.010102 +0.010216 +0.010246 +0.010325 +0.010144 +0.010067 +0.010123 +0.010226 +0.010321 +0.010403 +0.010213 +0.010138 +0.01018 +0.010254 +0.010368 +0.010438 +0.010254 +0.010164 +0.010227 +0.010321 +0.010426 +0.010501 +0.010306 +0.010214 +0.010294 +0.010415 +0.010473 +0.010552 +0.010351 +0.010261 +0.010336 +0.010425 +0.010528 +0.010598 +0.01039 +0.01031 +0.010381 +0.010447 +0.010522 +0.010523 +0.010299 +0.010169 +0.010225 +0.010257 +0.010293 +0.010325 +0.010091 +0.009935 +0.00997 +0.010022 +0.010063 +0.010037 +0.009784 +0.009661 +0.009693 +0.009713 +0.009783 +0.009788 +0.009556 +0.00942 +0.00944 +0.009455 +0.009494 +0.009499 +0.009293 +0.009155 +0.009189 +0.009216 +0.009259 +0.009271 +0.009054 +0.008944 +0.008954 +0.009001 +0.009059 +0.009076 +0.008901 +0.008761 +0.008801 +0.008847 +0.008897 +0.008916 +0.008726 +0.008628 +0.008661 +0.008723 +0.008762 +0.008795 +0.008634 +0.008534 +0.008572 +0.008616 +0.008669 +0.008686 +0.00852 +0.00843 +0.008476 +0.008529 +0.008596 +0.008644 +0.008493 +0.0084 +0.008449 +0.008532 +0.008611 +0.008667 +0.008493 +0.008435 +0.00847 +0.008547 +0.008636 +0.008718 +0.00854 +0.00847 +0.008529 +0.008593 +0.008675 +0.008746 +0.00859 +0.008512 +0.008565 +0.008656 +0.008737 +0.008794 +0.008624 +0.00856 +0.008607 +0.008681 +0.008767 +0.008829 +0.00869 +0.008603 +0.008668 +0.008743 +0.008843 +0.008867 +0.008718 +0.008625 +0.00869 +0.008768 +0.008844 +0.008912 +0.008781 +0.008686 +0.008735 +0.008812 +0.008908 +0.008957 +0.008807 +0.008728 +0.008794 +0.008861 +0.008945 +0.009013 +0.008862 +0.008774 +0.008824 +0.008906 +0.008984 +0.009044 +0.008891 +0.008822 +0.008913 +0.008952 +0.00903 +0.00909 +0.008952 +0.00887 +0.00891 +0.008987 +0.009074 +0.009147 +0.008978 +0.008906 +0.00897 +0.009038 +0.009133 +0.009207 +0.009049 +0.00896 +0.008992 +0.009084 +0.009178 +0.009225 +0.009074 +0.009001 +0.009053 +0.009128 +0.009238 +0.009303 +0.009153 +0.009041 +0.009101 +0.009157 +0.009251 +0.009322 +0.009159 +0.009081 +0.009149 +0.009234 +0.009322 +0.009392 +0.00922 +0.009127 +0.009175 +0.009268 +0.009352 +0.009413 +0.009265 +0.009175 +0.009239 +0.009332 +0.009421 +0.00947 +0.009306 +0.009227 +0.009286 +0.009393 +0.009444 +0.009492 +0.009354 +0.009303 +0.009314 +0.009399 +0.009491 +0.009565 +0.009396 +0.009316 +0.009385 +0.009454 +0.009556 +0.009624 +0.009459 +0.009357 +0.009411 +0.009511 +0.009591 +0.009657 +0.009499 +0.009404 +0.009464 +0.009549 +0.009668 +0.009754 +0.009547 +0.009447 +0.009515 +0.009595 +0.009694 +0.009756 +0.00959 +0.009503 +0.009563 +0.009656 +0.009759 +0.009805 +0.009632 +0.009543 +0.009622 +0.009697 +0.009776 +0.009856 +0.00967 +0.009615 +0.009674 +0.009751 +0.00985 +0.009897 +0.009734 +0.009681 +0.009702 +0.009783 +0.009885 +0.009948 +0.009781 +0.0097 +0.009752 +0.009849 +0.009937 +0.009999 +0.009841 +0.00975 +0.009821 +0.009917 +0.009998 +0.010043 +0.009887 +0.009792 +0.009853 +0.009936 +0.010037 +0.010116 +0.009947 +0.009868 +0.009949 +0.010014 +0.010104 +0.010123 +0.009965 +0.009893 +0.009936 +0.010032 +0.010154 +0.010213 +0.010033 +0.009961 +0.01 +0.010094 +0.010201 +0.010269 +0.010094 +0.010007 +0.010045 +0.010148 +0.010249 +0.010312 +0.010132 +0.010041 +0.010113 +0.010235 +0.010308 +0.010369 +0.010203 +0.01008 +0.010151 +0.01024 +0.010346 +0.010416 +0.010235 +0.01017 +0.010233 +0.010317 +0.010407 +0.010452 +0.010274 +0.010188 +0.010261 +0.010352 +0.010438 +0.010496 +0.010304 +0.010196 +0.010243 +0.010313 +0.010344 +0.010335 +0.010135 +0.010003 +0.010063 +0.010067 +0.010102 +0.010095 +0.009886 +0.009749 +0.009769 +0.009796 +0.00983 +0.009836 +0.009633 +0.009495 +0.009493 +0.009528 +0.009562 +0.009574 +0.009369 +0.009256 +0.009254 +0.009302 +0.009345 +0.009357 +0.009183 +0.00903 +0.00903 +0.009065 +0.009102 +0.00913 +0.00893 +0.008846 +0.00884 +0.008885 +0.008924 +0.008955 +0.008783 +0.008671 +0.008698 +0.008745 +0.008794 +0.008829 +0.008649 +0.008566 +0.008579 +0.008614 +0.008663 +0.008702 +0.008527 +0.008438 +0.008469 +0.00852 +0.008595 +0.008624 +0.008459 +0.00837 +0.00841 +0.00846 +0.008534 +0.008575 +0.008438 +0.008373 +0.008424 +0.008487 +0.008574 +0.008643 +0.00849 +0.008419 +0.00846 +0.008544 +0.008595 +0.00866 +0.008521 +0.008464 +0.008503 +0.008572 +0.008661 +0.008702 +0.008559 +0.008487 +0.008551 +0.008629 +0.008711 +0.008766 +0.008594 +0.008545 +0.008578 +0.008663 +0.008743 +0.008809 +0.008659 +0.00859 +0.008637 +0.0087 +0.008786 +0.008839 +0.008688 +0.008617 +0.008675 +0.008736 +0.008828 +0.00889 +0.00874 +0.008667 +0.008723 +0.008787 +0.008881 +0.008933 +0.008781 +0.008718 +0.008798 +0.008844 +0.008914 +0.008963 +0.008845 +0.008744 +0.008798 +0.008867 +0.008958 +0.009031 +0.008873 +0.0088 +0.008856 +0.008935 +0.009003 +0.009065 +0.008915 +0.008835 +0.008886 +0.008965 +0.009056 +0.009113 +0.008967 +0.008903 +0.008947 +0.009018 +0.009091 +0.009161 +0.009023 +0.008936 +0.008967 +0.009046 +0.009138 +0.009206 +0.009056 +0.008983 +0.009029 +0.009131 +0.009176 +0.009256 +0.009092 +0.009016 +0.009065 +0.009143 +0.009263 +0.009305 +0.009147 +0.009076 +0.009118 +0.009201 +0.009275 +0.009339 +0.009181 +0.0091 +0.009166 +0.009258 +0.009348 +0.009398 +0.009234 +0.009159 +0.009215 +0.009299 +0.009361 +0.00943 +0.009283 +0.009192 +0.009254 +0.009346 +0.009416 +0.009481 +0.009324 +0.009251 +0.009301 +0.009404 +0.009475 +0.009526 +0.009367 +0.00929 +0.009349 +0.009425 +0.009523 +0.009592 +0.009451 +0.00936 +0.009384 +0.009457 +0.009552 +0.009641 +0.009478 +0.009398 +0.009456 +0.009536 +0.009617 +0.009665 +0.009513 +0.009424 +0.009485 +0.009576 +0.009673 +0.009739 +0.009577 +0.009489 +0.009544 +0.009603 +0.009714 +0.009768 +0.009605 +0.009535 +0.009606 +0.00969 +0.009779 +0.00985 +0.009652 +0.009579 +0.009621 +0.009701 +0.00981 +0.009878 +0.009707 +0.009626 +0.009687 +0.009759 +0.009863 +0.009932 +0.009752 +0.009666 +0.009732 +0.009817 +0.009912 +0.00999 +0.009812 +0.009716 +0.009781 +0.009871 +0.010006 +0.010022 +0.009846 +0.009752 +0.009817 +0.009921 +0.010027 +0.0101 +0.009922 +0.009832 +0.009869 +0.009957 +0.010061 +0.010135 +0.009972 +0.009854 +0.009915 +0.01001 +0.010112 +0.010179 +0.010004 +0.009913 +0.009978 +0.010064 +0.010175 +0.010283 +0.010047 +0.009954 +0.010017 +0.010108 +0.010223 +0.010276 +0.0101 +0.010024 +0.010096 +0.010183 +0.010285 +0.010304 +0.010144 +0.010068 +0.010156 +0.010209 +0.01031 +0.010389 +0.010208 +0.010132 +0.010179 +0.010268 +0.010362 +0.010449 +0.010272 +0.010152 +0.010209 +0.010298 +0.010428 +0.010502 +0.010322 +0.010227 +0.010302 +0.010322 +0.010428 +0.010463 +0.010255 +0.010133 +0.010159 +0.010209 +0.010274 +0.010274 +0.010043 +0.009915 +0.009935 +0.009964 +0.010019 +0.010032 +0.009822 +0.009696 +0.009701 +0.009747 +0.00974 +0.009749 +0.009549 +0.009413 +0.009424 +0.009454 +0.009517 +0.009564 +0.009311 +0.009187 +0.009207 +0.009221 +0.009272 +0.00929 +0.009093 +0.008973 +0.008982 +0.009028 +0.009071 +0.009097 +0.008911 +0.008795 +0.008814 +0.008854 +0.008927 +0.008954 +0.008781 +0.008649 +0.008683 +0.008724 +0.008776 +0.008803 +0.008628 +0.008523 +0.008554 +0.0086 +0.008653 +0.008707 +0.008516 +0.008416 +0.008453 +0.008514 +0.008584 +0.008614 +0.008442 +0.008359 +0.008408 +0.008472 +0.008544 +0.008605 +0.008461 +0.008372 +0.008438 +0.008521 +0.008599 +0.00868 +0.008497 +0.008424 +0.00848 +0.008545 +0.008619 +0.008681 +0.00854 +0.008459 +0.008534 +0.008592 +0.008677 +0.008736 +0.008589 +0.008514 +0.00855 +0.008627 +0.008714 +0.008786 +0.008621 +0.008557 +0.008591 +0.008685 +0.008769 +0.008842 +0.008677 +0.0086 +0.008646 +0.008716 +0.008825 +0.00885 +0.00871 +0.008631 +0.008684 +0.008769 +0.008863 +0.008898 +0.008753 +0.008682 +0.008721 +0.0088 +0.008894 +0.008957 +0.008801 +0.008727 +0.0088 +0.008843 +0.008937 +0.009 +0.008846 +0.00877 +0.008816 +0.008896 +0.00898 +0.009044 +0.008904 +0.00883 +0.008903 +0.008944 +0.00902 +0.009071 +0.008921 +0.008855 +0.008911 +0.00898 +0.009097 +0.009132 +0.008972 +0.00891 +0.008961 +0.009034 +0.009104 +0.009174 +0.009027 +0.008939 +0.009002 +0.009074 +0.009162 +0.009234 +0.009079 +0.009003 +0.009044 +0.009115 +0.009201 +0.009277 +0.009139 +0.00903 +0.009085 +0.009161 +0.009248 +0.009322 +0.009155 +0.00908 +0.009154 +0.009217 +0.009308 +0.009351 +0.009204 +0.009118 +0.009175 +0.009258 +0.009345 +0.009414 +0.00925 +0.00917 +0.009225 +0.009296 +0.00941 +0.009457 +0.009298 +0.009234 +0.009292 +0.009378 +0.009442 +0.009507 +0.009334 +0.009253 +0.009319 +0.009393 +0.009488 +0.009565 +0.009382 +0.009321 +0.009379 +0.009447 +0.009529 +0.009595 +0.009441 +0.009351 +0.009405 +0.009503 +0.009584 +0.009665 +0.009504 +0.00941 +0.009448 +0.009534 +0.009637 +0.009723 +0.009537 +0.009444 +0.009495 +0.009597 +0.009699 +0.009725 +0.009582 +0.009517 +0.00956 +0.009627 +0.00972 +0.009797 +0.009617 +0.009545 +0.0096 +0.009682 +0.009801 +0.009861 +0.009677 +0.009593 +0.009638 +0.009729 +0.009828 +0.009889 +0.009722 +0.009666 +0.009694 +0.009791 +0.009887 +0.009945 +0.00978 +0.009693 +0.009732 +0.009828 +0.009917 +0.009983 +0.009818 +0.009733 +0.00979 +0.009878 +0.009983 +0.010042 +0.009862 +0.009792 +0.009839 +0.009929 +0.010046 +0.010096 +0.009915 +0.009837 +0.009908 +0.009998 +0.010071 +0.010133 +0.009962 +0.009883 +0.009935 +0.010027 +0.010148 +0.010206 +0.010004 +0.009927 +0.009986 +0.010078 +0.010183 +0.010245 +0.010067 +0.009986 +0.010064 +0.010144 +0.010222 +0.010295 +0.010114 +0.010026 +0.010099 +0.010211 +0.010318 +0.01033 +0.010153 +0.010091 +0.010156 +0.010215 +0.010331 +0.010405 +0.010205 +0.010126 +0.010191 +0.010279 +0.01041 +0.010476 +0.010274 +0.010178 +0.010242 +0.010336 +0.010433 +0.010503 +0.010311 +0.010226 +0.010315 +0.010417 +0.01051 +0.010512 +0.010322 +0.010188 +0.01022 +0.010263 +0.010323 +0.010343 +0.010121 +0.010004 +0.010002 +0.010061 +0.010104 +0.010106 +0.009893 +0.00975 +0.009773 +0.009813 +0.009874 +0.00989 +0.009668 +0.009524 +0.009535 +0.009576 +0.009626 +0.009662 +0.009447 +0.009305 +0.009332 +0.009376 +0.009452 +0.009432 +0.009224 +0.009112 +0.009138 +0.009167 +0.009216 +0.009248 +0.009051 +0.008937 +0.008962 +0.008999 +0.009039 +0.009071 +0.008889 +0.008773 +0.008803 +0.008843 +0.008899 +0.008943 +0.008755 +0.00864 +0.008668 +0.008738 +0.008769 +0.008788 +0.008624 +0.008525 +0.008563 +0.008618 +0.008696 +0.00872 +0.008576 +0.008471 +0.008508 +0.008573 +0.008658 +0.008705 +0.00857 +0.008499 +0.008547 +0.008621 +0.008705 +0.008776 +0.00861 +0.008541 +0.008578 +0.008659 +0.008738 +0.008799 +0.008657 +0.008588 +0.008649 +0.008712 +0.008771 +0.008848 +0.0087 +0.00863 +0.008675 +0.008731 +0.008824 +0.008906 +0.008733 +0.00866 +0.008715 +0.00879 +0.008907 +0.008931 +0.008791 +0.008708 +0.00876 +0.008823 +0.008917 +0.008966 +0.008819 +0.008756 +0.008798 +0.008872 +0.008975 +0.00904 +0.008904 +0.008809 +0.008861 +0.008908 +0.008989 +0.009055 +0.008909 +0.008831 +0.0089 +0.008973 +0.009047 +0.009109 +0.008953 +0.008886 +0.008926 +0.009004 +0.009103 +0.009155 +0.009005 +0.008924 +0.008988 +0.009065 +0.009144 +0.009209 +0.009053 +0.008968 +0.009023 +0.009107 +0.009219 +0.009253 +0.009086 +0.009025 +0.009096 +0.009152 +0.009226 +0.009297 +0.009127 +0.009056 +0.009109 +0.009184 +0.009292 +0.009345 +0.009182 +0.009124 +0.009168 +0.009231 +0.009321 +0.009392 +0.009227 +0.009147 +0.009212 +0.009287 +0.009373 +0.009456 +0.009288 +0.009232 +0.009266 +0.009327 +0.009398 +0.009467 +0.009322 +0.009261 +0.009292 +0.009372 +0.009468 +0.009542 +0.009367 +0.009289 +0.009345 +0.009431 +0.009512 +0.009576 +0.009411 +0.009337 +0.009392 +0.009462 +0.009577 +0.009652 +0.009469 +0.009385 +0.009459 +0.009538 +0.009636 +0.009659 +0.009493 +0.00942 +0.009483 +0.009564 +0.009659 +0.009746 +0.00956 +0.009466 +0.009529 +0.009624 +0.009718 +0.009771 +0.009606 +0.009527 +0.009579 +0.009664 +0.009766 +0.009818 +0.009654 +0.009586 +0.009616 +0.009714 +0.009812 +0.009899 +0.009757 +0.009616 +0.009661 +0.009751 +0.009851 +0.009919 +0.00975 +0.009665 +0.009722 +0.009841 +0.009907 +0.009986 +0.009796 +0.009714 +0.009772 +0.00986 +0.009951 +0.01002 +0.009837 +0.009751 +0.009847 +0.009933 +0.010019 +0.010079 +0.009906 +0.009831 +0.009869 +0.009963 +0.01004 +0.010113 +0.009939 +0.009851 +0.009926 +0.010013 +0.010101 +0.010167 +0.010011 +0.009902 +0.009959 +0.010064 +0.010171 +0.01022 +0.010053 +0.00997 +0.010013 +0.010121 +0.010208 +0.010272 +0.010095 +0.010019 +0.010129 +0.010174 +0.010243 +0.010307 +0.010137 +0.010057 +0.010117 +0.010196 +0.010322 +0.010374 +0.010211 +0.010135 +0.010166 +0.010287 +0.010359 +0.010416 +0.010242 +0.010163 +0.010213 +0.010315 +0.01043 +0.010513 +0.010313 +0.010226 +0.010283 +0.010379 +0.010447 +0.010515 +0.010342 +0.010261 +0.01034 +0.010429 +0.010516 +0.01055 +0.010337 +0.010229 +0.010247 +0.010288 +0.010354 +0.010422 +0.010154 +0.010054 +0.010061 +0.010089 +0.010137 +0.01013 +0.009902 +0.00979 +0.009806 +0.009868 +0.009894 +0.009876 +0.009663 +0.00954 +0.009558 +0.009583 +0.009628 +0.009661 +0.009426 +0.0093 +0.009332 +0.00935 +0.009381 +0.009388 +0.009201 +0.009075 +0.009086 +0.009135 +0.009166 +0.009186 +0.009016 +0.0089 +0.008913 +0.008944 +0.008998 +0.009012 +0.008848 +0.008739 +0.008731 +0.008784 +0.008834 +0.008868 +0.008687 +0.008597 +0.008615 +0.008666 +0.008711 +0.008749 +0.008585 +0.008485 +0.008517 +0.008574 +0.008642 +0.00867 +0.0085 +0.008423 +0.008464 +0.008522 +0.008593 +0.00866 +0.008506 +0.008433 +0.00849 +0.008571 +0.008652 +0.008723 +0.008544 +0.00846 +0.008529 +0.008591 +0.008678 +0.008728 +0.008586 +0.008508 +0.00857 +0.008649 +0.008729 +0.008791 +0.008639 +0.008561 +0.008611 +0.008684 +0.008762 +0.008812 +0.008674 +0.008613 +0.008684 +0.008733 +0.00882 +0.008873 +0.008704 +0.008635 +0.008694 +0.008781 +0.008866 +0.008903 +0.008756 +0.008699 +0.008748 +0.008827 +0.008903 +0.008963 +0.0088 +0.008733 +0.008777 +0.008854 +0.008945 +0.008995 +0.008848 +0.008798 +0.008852 +0.008908 +0.008996 +0.009069 +0.008878 +0.008813 +0.008866 +0.008943 +0.009031 +0.009104 +0.008945 +0.008872 +0.008938 +0.008987 +0.009074 +0.009138 +0.008975 +0.008915 +0.008955 +0.009045 +0.009132 +0.00919 +0.009026 +0.008951 +0.009012 +0.009086 +0.009166 +0.009235 +0.009071 +0.008996 +0.009054 +0.009139 +0.009237 +0.00929 +0.009122 +0.009033 +0.009091 +0.009172 +0.009268 +0.00937 +0.009166 +0.009077 +0.00913 +0.009222 +0.009331 +0.009377 +0.009208 +0.009129 +0.009183 +0.009262 +0.009348 +0.009426 +0.009244 +0.009177 +0.009256 +0.00934 +0.00942 +0.00948 +0.009294 +0.009212 +0.009265 +0.00936 +0.009444 +0.009509 +0.00935 +0.009287 +0.009381 +0.009422 +0.009522 +0.009545 +0.009369 +0.009311 +0.009352 +0.00946 +0.009543 +0.009605 +0.009443 +0.009368 +0.009434 +0.009498 +0.009594 +0.009655 +0.009479 +0.009414 +0.009474 +0.009569 +0.009664 +0.009721 +0.009542 +0.009444 +0.009502 +0.009602 +0.009719 +0.009763 +0.009571 +0.009507 +0.009572 +0.009663 +0.009749 +0.009811 +0.009621 +0.009544 +0.009599 +0.00969 +0.009791 +0.009843 +0.00972 +0.009617 +0.00966 +0.00975 +0.009848 +0.009884 +0.009719 +0.009647 +0.009709 +0.009796 +0.009903 +0.009945 +0.009813 +0.009693 +0.009746 +0.009828 +0.009926 +0.010011 +0.009822 +0.009755 +0.00981 +0.009902 +0.009982 +0.010059 +0.009871 +0.009793 +0.009852 +0.009945 +0.010055 +0.010108 +0.009939 +0.009854 +0.009916 +0.009981 +0.010086 +0.010152 +0.009986 +0.009926 +0.009956 +0.010034 +0.010135 +0.010209 +0.01005 +0.009939 +0.009992 +0.010108 +0.010194 +0.010243 +0.010083 +0.009982 +0.010055 +0.010173 +0.010258 +0.010313 +0.010134 +0.010034 +0.010092 +0.01019 +0.010294 +0.010356 +0.010175 +0.010109 +0.010212 +0.010276 +0.010345 +0.010384 +0.010213 +0.010136 +0.0102 +0.010293 +0.010398 +0.010456 +0.010286 +0.010195 +0.010252 +0.010348 +0.010459 +0.010509 +0.010328 +0.010259 +0.010326 +0.010375 +0.010465 +0.010509 +0.010299 +0.010178 +0.010227 +0.010232 +0.010283 +0.010319 +0.010088 +0.009958 +0.009978 +0.009998 +0.010036 +0.010014 +0.009804 +0.009683 +0.009687 +0.009724 +0.009783 +0.009765 +0.00956 +0.009427 +0.009438 +0.009469 +0.009503 +0.009533 +0.009315 +0.009184 +0.009213 +0.009243 +0.009292 +0.009269 +0.009072 +0.008949 +0.008968 +0.009016 +0.009045 +0.009088 +0.008903 +0.00879 +0.008804 +0.008846 +0.008893 +0.008908 +0.008737 +0.008633 +0.00868 +0.0087 +0.008754 +0.008792 +0.00861 +0.008516 +0.008541 +0.008598 +0.008643 +0.008676 +0.008521 +0.008417 +0.008467 +0.008517 +0.00857 +0.008599 +0.008438 +0.008354 +0.00841 +0.008465 +0.008555 +0.008626 +0.008458 +0.008387 +0.008438 +0.008523 +0.008602 +0.008653 +0.008494 +0.008434 +0.008478 +0.008555 +0.00863 +0.008692 +0.00854 +0.008475 +0.008542 +0.0086 +0.008686 +0.008738 +0.008595 +0.008525 +0.00859 +0.008631 +0.008708 +0.00879 +0.008629 +0.008556 +0.008602 +0.008683 +0.008762 +0.008825 +0.008692 +0.008593 +0.00865 +0.00872 +0.008806 +0.00887 +0.008724 +0.008663 +0.008685 +0.008768 +0.008854 +0.008905 +0.008754 +0.008689 +0.008734 +0.008812 +0.008904 +0.008974 +0.00885 +0.008749 +0.008791 +0.008865 +0.008931 +0.008993 +0.008845 +0.008771 +0.008826 +0.008896 +0.008991 +0.009056 +0.008902 +0.008832 +0.008872 +0.008943 +0.00904 +0.009085 +0.008938 +0.008863 +0.008915 +0.00899 +0.009084 +0.009161 +0.008989 +0.008913 +0.008969 +0.009046 +0.009146 +0.009181 +0.009019 +0.008942 +0.008996 +0.009093 +0.009164 +0.009228 +0.009073 +0.009003 +0.009047 +0.009122 +0.009224 +0.009278 +0.009127 +0.009064 +0.009096 +0.009181 +0.00926 +0.00933 +0.009163 +0.009087 +0.00914 +0.009212 +0.009313 +0.009376 +0.009219 +0.009164 +0.009225 +0.009271 +0.009337 +0.009411 +0.009257 +0.009179 +0.009228 +0.009308 +0.009411 +0.00946 +0.009307 +0.009244 +0.009295 +0.00936 +0.009451 +0.009519 +0.009374 +0.009261 +0.009324 +0.009387 +0.009502 +0.009581 +0.009405 +0.009321 +0.009386 +0.009461 +0.009574 +0.009613 +0.009427 +0.009353 +0.009412 +0.009494 +0.009594 +0.009664 +0.009495 +0.009436 +0.009456 +0.009561 +0.009641 +0.009707 +0.009556 +0.009473 +0.009505 +0.009595 +0.009692 +0.009755 +0.009584 +0.00951 +0.009551 +0.009636 +0.009757 +0.009808 +0.009657 +0.009567 +0.009623 +0.00973 +0.009784 +0.009839 +0.009675 +0.009601 +0.009659 +0.009735 +0.009839 +0.009947 +0.009724 +0.009642 +0.009712 +0.00979 +0.009887 +0.009963 +0.00977 +0.009696 +0.009765 +0.009844 +0.009948 +0.010023 +0.009825 +0.009745 +0.009806 +0.009897 +0.010028 +0.010079 +0.009864 +0.009785 +0.009845 +0.009968 +0.01004 +0.010086 +0.009934 +0.009848 +0.009909 +0.009991 +0.010089 +0.010153 +0.009985 +0.009895 +0.009963 +0.010057 +0.010131 +0.010208 +0.010014 +0.009951 +0.010006 +0.010076 +0.010209 +0.010308 +0.010103 +0.01 +0.010045 +0.010135 +0.010251 +0.010305 +0.010125 +0.010053 +0.010087 +0.010193 +0.010318 +0.010405 +0.01018 +0.010089 +0.01014 +0.010235 +0.010347 +0.010406 +0.010228 +0.01016 +0.010224 +0.010312 +0.010421 +0.010485 +0.010293 +0.010178 +0.010241 +0.010333 +0.010463 +0.010479 +0.010309 +0.01021 +0.010247 +0.010285 +0.010343 +0.010335 +0.010132 +0.01 +0.01001 +0.010076 +0.010098 +0.010124 +0.009899 +0.009754 +0.009766 +0.009806 +0.009852 +0.009865 +0.009658 +0.009566 +0.00955 +0.009554 +0.00958 +0.009611 +0.009405 +0.009272 +0.009307 +0.00931 +0.009362 +0.009389 +0.009174 +0.009062 +0.009059 +0.009075 +0.009125 +0.009146 +0.008953 +0.008842 +0.00887 +0.008893 +0.008945 +0.008981 +0.008795 +0.008692 +0.008716 +0.008762 +0.00882 +0.008862 +0.008659 +0.008564 +0.0086 +0.008637 +0.008685 +0.008709 +0.008552 +0.008451 +0.00851 +0.008529 +0.008581 +0.008632 +0.008479 +0.008406 +0.008439 +0.008507 +0.008553 +0.008611 +0.008476 +0.008414 +0.008448 +0.008518 +0.008606 +0.008668 +0.008532 +0.008455 +0.008511 +0.008586 +0.008684 +0.008687 +0.008551 +0.008477 +0.008541 +0.008611 +0.008692 +0.008751 +0.008599 +0.008534 +0.008585 +0.008659 +0.008747 +0.008793 +0.008638 +0.008573 +0.008626 +0.008709 +0.00881 +0.008839 +0.00868 +0.008608 +0.008674 +0.008739 +0.008818 +0.008882 +0.008726 +0.008667 +0.008724 +0.008807 +0.008874 +0.008929 +0.008771 +0.0087 +0.008758 +0.008826 +0.008904 +0.008974 +0.008822 +0.008738 +0.008804 +0.00889 +0.008983 +0.009022 +0.008863 +0.008781 +0.008832 +0.00891 +0.009009 +0.009064 +0.008906 +0.008827 +0.008892 +0.008968 +0.00906 +0.009126 +0.00897 +0.008892 +0.008918 +0.008994 +0.009085 +0.009149 +0.009006 +0.008909 +0.00897 +0.009051 +0.00914 +0.009198 +0.009042 +0.008968 +0.009019 +0.009094 +0.009211 +0.009251 +0.009095 +0.009002 +0.00906 +0.009135 +0.009229 +0.009293 +0.00914 +0.009044 +0.009111 +0.00919 +0.009307 +0.009357 +0.009186 +0.009086 +0.009153 +0.009227 +0.009322 +0.009377 +0.009243 +0.009139 +0.009198 +0.009284 +0.00939 +0.009437 +0.009275 +0.009178 +0.009241 +0.009316 +0.009419 +0.009482 +0.009302 +0.009235 +0.0093 +0.00938 +0.009478 +0.009541 +0.00938 +0.009305 +0.00934 +0.009403 +0.009496 +0.009574 +0.009406 +0.009338 +0.009399 +0.009482 +0.009573 +0.009628 +0.009442 +0.009371 +0.009413 +0.009507 +0.00961 +0.009663 +0.009497 +0.009444 +0.009517 +0.009568 +0.009665 +0.00972 +0.00953 +0.00946 +0.009523 +0.009628 +0.009722 +0.009763 +0.009587 +0.009513 +0.009582 +0.009658 +0.009746 +0.009799 +0.009648 +0.009559 +0.009618 +0.009736 +0.009801 +0.009853 +0.009696 +0.009605 +0.009671 +0.009765 +0.00984 +0.009917 +0.009755 +0.009665 +0.009724 +0.009806 +0.009898 +0.009998 +0.009788 +0.009696 +0.00976 +0.009876 +0.009957 +0.010009 +0.009839 +0.009777 +0.009817 +0.009888 +0.009999 +0.010057 +0.009889 +0.009803 +0.009859 +0.009954 +0.010075 +0.010133 +0.00995 +0.009864 +0.009906 +0.00999 +0.010095 +0.010167 +0.010012 +0.009906 +0.009961 +0.010063 +0.01017 +0.010229 +0.010062 +0.009936 +0.010003 +0.010097 +0.010194 +0.010266 +0.010084 +0.009998 +0.010071 +0.010154 +0.010243 +0.010322 +0.010137 +0.010054 +0.010119 +0.010215 +0.010321 +0.010365 +0.0102 +0.010139 +0.010156 +0.010247 +0.010351 +0.010426 +0.010244 +0.010153 +0.010227 +0.010353 +0.010384 +0.010458 +0.010299 +0.010194 +0.010245 +0.010332 +0.010422 +0.010478 +0.010268 +0.010128 +0.010142 +0.010193 +0.010256 +0.010253 +0.010031 +0.009903 +0.009958 +0.009997 +0.010013 +0.010006 +0.00978 +0.00967 +0.009681 +0.009717 +0.009774 +0.00978 +0.009596 +0.009441 +0.009449 +0.009488 +0.009509 +0.009526 +0.009313 +0.009184 +0.009207 +0.009247 +0.009277 +0.009303 +0.009107 +0.008969 +0.008987 +0.009038 +0.009087 +0.009108 +0.008893 +0.008802 +0.008791 +0.008841 +0.008899 +0.008918 +0.008739 +0.008633 +0.008654 +0.008703 +0.008752 +0.008802 +0.008603 +0.008506 +0.00853 +0.008602 +0.008652 +0.008666 +0.008502 +0.008399 +0.008432 +0.008494 +0.00857 +0.008596 +0.008444 +0.008354 +0.008404 +0.008487 +0.008582 +0.008613 +0.008445 +0.008384 +0.008436 +0.008512 +0.008574 +0.008636 +0.008494 +0.008431 +0.00847 +0.008558 +0.008639 +0.008698 +0.008535 +0.008466 +0.00852 +0.008581 +0.00867 +0.008729 +0.008594 +0.008515 +0.008572 +0.008654 +0.008723 +0.008798 +0.008628 +0.008548 +0.008611 +0.008694 +0.00875 +0.008812 +0.008672 +0.008591 +0.008646 +0.008711 +0.008826 +0.008852 +0.008715 +0.008636 +0.008685 +0.008771 +0.008845 +0.00891 +0.008761 +0.008701 +0.008733 +0.008802 +0.008895 +0.008953 +0.008802 +0.008725 +0.008778 +0.008858 +0.008936 +0.009008 +0.008864 +0.008811 +0.008823 +0.008882 +0.008984 +0.009035 +0.008885 +0.008812 +0.008858 +0.008941 +0.009055 +0.009089 +0.008942 +0.008866 +0.008908 +0.008974 +0.009068 +0.009137 +0.008973 +0.008903 +0.00896 +0.00903 +0.009133 +0.009193 +0.00904 +0.008947 +0.008995 +0.009069 +0.009158 +0.009256 +0.009087 +0.008982 +0.009033 +0.009115 +0.009225 +0.00928 +0.009119 +0.009048 +0.009092 +0.009151 +0.009257 +0.009318 +0.009153 +0.009073 +0.009134 +0.009216 +0.009294 +0.009374 +0.009215 +0.009124 +0.009179 +0.009266 +0.009349 +0.009414 +0.009258 +0.009192 +0.00926 +0.009308 +0.009392 +0.009465 +0.009296 +0.009215 +0.009258 +0.009347 +0.009443 +0.009514 +0.009347 +0.009268 +0.00932 +0.009413 +0.009492 +0.009553 +0.009383 +0.009307 +0.009361 +0.009455 +0.009539 +0.009606 +0.009458 +0.009359 +0.009405 +0.009498 +0.0096 +0.009668 +0.009475 +0.009402 +0.009458 +0.009541 +0.009661 +0.009697 +0.009524 +0.009462 +0.009491 +0.009587 +0.009688 +0.009745 +0.009581 +0.009503 +0.009555 +0.009639 +0.009731 +0.009803 +0.009632 +0.009538 +0.009609 +0.009683 +0.009782 +0.009862 +0.009688 +0.009618 +0.009642 +0.009734 +0.009832 +0.009889 +0.00974 +0.009628 +0.009721 +0.009784 +0.00988 +0.009935 +0.009769 +0.009686 +0.009745 +0.009854 +0.009947 +0.010006 +0.009818 +0.009727 +0.009788 +0.009881 +0.009981 +0.010037 +0.009873 +0.009799 +0.00989 +0.009954 +0.010044 +0.010078 +0.009907 +0.009832 +0.009884 +0.00998 +0.010086 +0.01014 +0.009969 +0.009903 +0.009967 +0.010037 +0.010139 +0.010179 +0.010015 +0.009935 +0.009996 +0.010083 +0.010179 +0.010273 +0.010079 +0.01 +0.010059 +0.010162 +0.010227 +0.010297 +0.010111 +0.010022 +0.010091 +0.010179 +0.010302 +0.01036 +0.010177 +0.01009 +0.010146 +0.010222 +0.010331 +0.01041 +0.010224 +0.010122 +0.010218 +0.010301 +0.010406 +0.010473 +0.01027 +0.010173 +0.01025 +0.010363 +0.010457 +0.010485 +0.010311 +0.010257 +0.010342 +0.01039 +0.010498 +0.010556 +0.010352 +0.010259 +0.010332 +0.010403 +0.010489 +0.010539 +0.010301 +0.010162 +0.010189 +0.010266 +0.010274 +0.010297 +0.010065 +0.00992 +0.009942 +0.00999 +0.010083 +0.010035 +0.0098 +0.009676 +0.009684 +0.009728 +0.009781 +0.009751 +0.009541 +0.009405 +0.009437 +0.009469 +0.009503 +0.009511 +0.009302 +0.00917 +0.009188 +0.009215 +0.009281 +0.009278 +0.009075 +0.008962 +0.00898 +0.009013 +0.009054 +0.009076 +0.0089 +0.008776 +0.008774 +0.008826 +0.008887 +0.008912 +0.008736 +0.008618 +0.008649 +0.008712 +0.008784 +0.008773 +0.0086 +0.008501 +0.008525 +0.008584 +0.00864 +0.008671 +0.008496 +0.008398 +0.008456 +0.008505 +0.008568 +0.008593 +0.008437 +0.00835 +0.008394 +0.008469 +0.008552 +0.00862 +0.008472 +0.008401 +0.008419 +0.008497 +0.00859 +0.00865 +0.00849 +0.008421 +0.008471 +0.008548 +0.008624 +0.008691 +0.008542 +0.008465 +0.008512 +0.008586 +0.008692 +0.008737 +0.008593 +0.008519 +0.008555 +0.008629 +0.008713 +0.008782 +0.008635 +0.008563 +0.00859 +0.008676 +0.008775 +0.008845 +0.00867 +0.008594 +0.008662 +0.008713 +0.008795 +0.008853 +0.008705 +0.008634 +0.008689 +0.008756 +0.00886 +0.008924 +0.00876 +0.008691 +0.008739 +0.008798 +0.008884 +0.008947 +0.008805 +0.008721 +0.008784 +0.008841 +0.008947 +0.008994 +0.008841 +0.008771 +0.008825 +0.008916 +0.008984 +0.009029 +0.008891 +0.008811 +0.008871 +0.008978 +0.009015 +0.009077 +0.008936 +0.008848 +0.008903 +0.008982 +0.009063 +0.009132 +0.008978 +0.008904 +0.008954 +0.009039 +0.009123 +0.00918 +0.009024 +0.008947 +0.009005 +0.009065 +0.009159 +0.009239 +0.009077 +0.009013 +0.009038 +0.00912 +0.009205 +0.009256 +0.009123 +0.009031 +0.009085 +0.009189 +0.00925 +0.009314 +0.009155 +0.009078 +0.009134 +0.009218 +0.009309 +0.009362 +0.009207 +0.009127 +0.009179 +0.009251 +0.009353 +0.009424 +0.009254 +0.009163 +0.009234 +0.00932 +0.009435 +0.009447 +0.009284 +0.009205 +0.009271 +0.009351 +0.009442 +0.009504 +0.009351 +0.009259 +0.009337 +0.009391 +0.009492 +0.009555 +0.009387 +0.009313 +0.009364 +0.009444 +0.00955 +0.009602 +0.009436 +0.009366 +0.009424 +0.009487 +0.009576 +0.009652 +0.009505 +0.009417 +0.009453 +0.009559 +0.009613 +0.009705 +0.009541 +0.009459 +0.009491 +0.009589 +0.009677 +0.009742 +0.009591 +0.009489 +0.009551 +0.009647 +0.009742 +0.009808 +0.00964 +0.009543 +0.009594 +0.009691 +0.009787 +0.009847 +0.009671 +0.009591 +0.009673 +0.009771 +0.009836 +0.009894 +0.009722 +0.009655 +0.009683 +0.009776 +0.009875 +0.009944 +0.009757 +0.009687 +0.009767 +0.009838 +0.00995 +0.010002 +0.009807 +0.009733 +0.009801 +0.009887 +0.009978 +0.010043 +0.009877 +0.009784 +0.009847 +0.009939 +0.010068 +0.01008 +0.009909 +0.009841 +0.009888 +0.009991 +0.010105 +0.01013 +0.009974 +0.009877 +0.009942 +0.010036 +0.010125 +0.010196 +0.010045 +0.009952 +0.010005 +0.010084 +0.010177 +0.010243 +0.010075 +0.009991 +0.010031 +0.010135 +0.010247 +0.01035 +0.010118 +0.010033 +0.010082 +0.010177 +0.010278 +0.010342 +0.010171 +0.010104 +0.010133 +0.010235 +0.010359 +0.010412 +0.01023 +0.010119 +0.010188 +0.010283 +0.010395 +0.010458 +0.010263 +0.010198 +0.010251 +0.010329 +0.010423 +0.010474 +0.010253 +0.010092 +0.010115 +0.010167 +0.010239 +0.01025 +0.010028 +0.009905 +0.009924 +0.009956 +0.010003 +0.009993 +0.009779 +0.009656 +0.009643 +0.009712 +0.009752 +0.009743 +0.009525 +0.009392 +0.009403 +0.009443 +0.00947 +0.009499 +0.009291 +0.009187 +0.009161 +0.009206 +0.009259 +0.009228 +0.009039 +0.008916 +0.008932 +0.008985 +0.009017 +0.00904 +0.008857 +0.008761 +0.008773 +0.008816 +0.008866 +0.008873 +0.0087 +0.008591 +0.008632 +0.008663 +0.008723 +0.008755 +0.008592 +0.008484 +0.008503 +0.00856 +0.008611 +0.008643 +0.008451 +0.008352 +0.008389 +0.008439 +0.008503 +0.008569 +0.008388 +0.008304 +0.008363 +0.008421 +0.008502 +0.008559 +0.008407 +0.008334 +0.008384 +0.008455 +0.008538 +0.008589 +0.008449 +0.008395 +0.008446 +0.008509 +0.008585 +0.00865 +0.008492 +0.00841 +0.008473 +0.008536 +0.008646 +0.00869 +0.00853 +0.00846 +0.008521 +0.008595 +0.00868 +0.008743 +0.008583 +0.008533 +0.00854 +0.008622 +0.008695 +0.008767 +0.00861 +0.008555 +0.008595 +0.008673 +0.00875 +0.008811 +0.008675 +0.008591 +0.008635 +0.008717 +0.008805 +0.008855 +0.008711 +0.00864 +0.008706 +0.008788 +0.008867 +0.008891 +0.008739 +0.008674 +0.008722 +0.008799 +0.008882 +0.008934 +0.008806 +0.008727 +0.008793 +0.008845 +0.008939 +0.008999 +0.008841 +0.008775 +0.008815 +0.008887 +0.008986 +0.009045 +0.008897 +0.008821 +0.008877 +0.008936 +0.009017 +0.009083 +0.008932 +0.008874 +0.00892 +0.008973 +0.00906 +0.009143 +0.008984 +0.008915 +0.008958 +0.009027 +0.009109 +0.009168 +0.009016 +0.008941 +0.009017 +0.00906 +0.009156 +0.009238 +0.009087 +0.009001 +0.009054 +0.009126 +0.009196 +0.009258 +0.009112 +0.009024 +0.009077 +0.00917 +0.009256 +0.00935 +0.009156 +0.009074 +0.009141 +0.009199 +0.009326 +0.009344 +0.009195 +0.009124 +0.009183 +0.009253 +0.009348 +0.00942 +0.009251 +0.009174 +0.009229 +0.00929 +0.009394 +0.009461 +0.009302 +0.009226 +0.009286 +0.009361 +0.00943 +0.00951 +0.009347 +0.009263 +0.009339 +0.009374 +0.009487 +0.009559 +0.009397 +0.009324 +0.009377 +0.009456 +0.009532 +0.009586 +0.009424 +0.009348 +0.009396 +0.009483 +0.009581 +0.009669 +0.0095 +0.009412 +0.009471 +0.00954 +0.009623 +0.009695 +0.009527 +0.009442 +0.009491 +0.009611 +0.009728 +0.009757 +0.009575 +0.009496 +0.009531 +0.009635 +0.009713 +0.009784 +0.009624 +0.009548 +0.009609 +0.009694 +0.009797 +0.009848 +0.009675 +0.00958 +0.009636 +0.009725 +0.009834 +0.009888 +0.009715 +0.009648 +0.00972 +0.009789 +0.009892 +0.009953 +0.00979 +0.009676 +0.009719 +0.009828 +0.009923 +0.009969 +0.009821 +0.009766 +0.009801 +0.009891 +0.010005 +0.010035 +0.009851 +0.009773 +0.009839 +0.00993 +0.010037 +0.01009 +0.009917 +0.009837 +0.009896 +0.009972 +0.010072 +0.010153 +0.009963 +0.009882 +0.009953 +0.010067 +0.010174 +0.010184 +0.009994 +0.00992 +0.009994 +0.010065 +0.010167 +0.010255 +0.010073 +0.009982 +0.010064 +0.010157 +0.01022 +0.010294 +0.010119 +0.010031 +0.010085 +0.010183 +0.01029 +0.010365 +0.010182 +0.010079 +0.010132 +0.010203 +0.010316 +0.010322 +0.010088 +0.010003 +0.009997 +0.010056 +0.010121 +0.010112 +0.00989 +0.009773 +0.009797 +0.009822 +0.00989 +0.00988 +0.009658 +0.009546 +0.009554 +0.009601 +0.009634 +0.009619 +0.009409 +0.009287 +0.009308 +0.009338 +0.009375 +0.00941 +0.009207 +0.009099 +0.009081 +0.009126 +0.009159 +0.009186 +0.00897 +0.008846 +0.008879 +0.008932 +0.008975 +0.009 +0.008825 +0.008709 +0.008725 +0.00878 +0.008822 +0.008827 +0.008648 +0.008543 +0.008579 +0.00862 +0.008682 +0.008694 +0.008523 +0.008412 +0.008445 +0.008503 +0.008552 +0.008604 +0.008419 +0.008314 +0.008346 +0.008414 +0.008476 +0.008535 +0.008373 +0.008287 +0.008324 +0.008403 +0.008473 +0.008536 +0.008396 +0.008322 +0.008366 +0.008435 +0.008527 +0.008585 +0.008441 +0.008352 +0.00841 +0.008473 +0.008558 +0.008626 +0.008474 +0.008399 +0.00845 +0.008529 +0.008616 +0.008703 +0.008521 +0.008449 +0.008482 +0.008558 +0.008647 +0.008703 +0.008558 +0.008473 +0.008553 +0.008611 +0.008684 +0.008744 +0.008616 +0.008536 +0.008579 +0.008665 +0.008727 +0.008785 +0.008643 +0.008575 +0.008616 +0.008689 +0.008776 +0.008837 +0.008684 +0.008614 +0.008673 +0.008748 +0.008851 +0.008891 +0.008717 +0.008651 +0.008703 +0.008793 +0.008872 +0.008917 +0.008773 +0.0087 +0.008754 +0.008829 +0.008915 +0.008971 +0.008817 +0.008738 +0.008795 +0.008868 +0.008966 +0.009025 +0.008856 +0.008785 +0.008842 +0.008921 +0.008998 +0.009061 +0.008911 +0.008841 +0.008915 +0.008951 +0.009054 +0.009104 +0.008949 +0.00887 +0.008918 +0.009 +0.009089 +0.009163 +0.008992 +0.008921 +0.008977 +0.009053 +0.009146 +0.009204 +0.009034 +0.008964 +0.009011 +0.009099 +0.00918 +0.009244 +0.009084 +0.009016 +0.009062 +0.009155 +0.009248 +0.009317 +0.009139 +0.00904 +0.009091 +0.009179 +0.00927 +0.009363 +0.009163 +0.009094 +0.009173 +0.009251 +0.009327 +0.009387 +0.009212 +0.009136 +0.009187 +0.009281 +0.009361 +0.009424 +0.009275 +0.009201 +0.009267 +0.009345 +0.009428 +0.009473 +0.009303 +0.009235 +0.009303 +0.009393 +0.009452 +0.009513 +0.009356 +0.009289 +0.009364 +0.00943 +0.00954 +0.009559 +0.009394 +0.009314 +0.009377 +0.009464 +0.009557 +0.00963 +0.009455 +0.009395 +0.009456 +0.00953 +0.009617 +0.009656 +0.009505 +0.009417 +0.009474 +0.009565 +0.009658 +0.009766 +0.009545 +0.009477 +0.00953 +0.009617 +0.009717 +0.009742 +0.009592 +0.009528 +0.009583 +0.009657 +0.009753 +0.009805 +0.009648 +0.009564 +0.009628 +0.009708 +0.0098 +0.009886 +0.009701 +0.009618 +0.00969 +0.009763 +0.009844 +0.00992 +0.009756 +0.009694 +0.009714 +0.009819 +0.009894 +0.009961 +0.009799 +0.009713 +0.009776 +0.00985 +0.009953 +0.010013 +0.009851 +0.009753 +0.009812 +0.009919 +0.010001 +0.010069 +0.00991 +0.00982 +0.009857 +0.009953 +0.010051 +0.010117 +0.009945 +0.009868 +0.009963 +0.010004 +0.010098 +0.010172 +0.01003 +0.009893 +0.009955 +0.010051 +0.010158 +0.010213 +0.010031 +0.009962 +0.010014 +0.010131 +0.010231 +0.010267 +0.01008 +0.010012 +0.010061 +0.010154 +0.010267 +0.010311 +0.010147 +0.010069 +0.010149 +0.010268 +0.010307 +0.010341 +0.010181 +0.010103 +0.010174 +0.010282 +0.010354 +0.010434 +0.010223 +0.010144 +0.01021 +0.010267 +0.010348 +0.010379 +0.010145 +0.010013 +0.01006 +0.010107 +0.010168 +0.010169 +0.009936 +0.009785 +0.009796 +0.009866 +0.009898 +0.009872 +0.009643 +0.009531 +0.009537 +0.009576 +0.009622 +0.009618 +0.009427 +0.009259 +0.009277 +0.009325 +0.009353 +0.009362 +0.009164 +0.009032 +0.009048 +0.009095 +0.009142 +0.009143 +0.008943 +0.008832 +0.008852 +0.008904 +0.008949 +0.008971 +0.008801 +0.008688 +0.008695 +0.008737 +0.008792 +0.008813 +0.008636 +0.008531 +0.008555 +0.008617 +0.008665 +0.008688 +0.008514 +0.008426 +0.008454 +0.008499 +0.008555 +0.008591 +0.008414 +0.008326 +0.008371 +0.00843 +0.008497 +0.00855 +0.008396 +0.008311 +0.008368 +0.008427 +0.008514 +0.008575 +0.008446 +0.008342 +0.008386 +0.008473 +0.008544 +0.00861 +0.00846 +0.008394 +0.00845 +0.008525 +0.008584 +0.008647 +0.008502 +0.008434 +0.00848 +0.008548 +0.008638 +0.008702 +0.008553 +0.008474 +0.008544 +0.008621 +0.008692 +0.008728 +0.008596 +0.008517 +0.00856 +0.008646 +0.008727 +0.008803 +0.00863 +0.008553 +0.008607 +0.008695 +0.008778 +0.008851 +0.008662 +0.008598 +0.008654 +0.008719 +0.008812 +0.008872 +0.008713 +0.008642 +0.008699 +0.008789 +0.008855 +0.008919 +0.008772 +0.008697 +0.008739 +0.008814 +0.008903 +0.008956 +0.008819 +0.008741 +0.008785 +0.008887 +0.008961 +0.008998 +0.008842 +0.008772 +0.00882 +0.008912 +0.008993 +0.009075 +0.008916 +0.008822 +0.008865 +0.008944 +0.009038 +0.009089 +0.008936 +0.008867 +0.008901 +0.008993 +0.009081 +0.009159 +0.009 +0.008917 +0.008971 +0.009045 +0.009112 +0.009188 +0.009031 +0.008979 +0.009003 +0.009081 +0.009173 +0.009234 +0.009091 +0.009007 +0.009052 +0.009126 +0.009216 +0.009289 +0.009115 +0.009038 +0.009079 +0.009159 +0.009256 +0.009333 +0.009169 +0.009085 +0.009159 +0.009224 +0.009314 +0.00938 +0.009217 +0.009138 +0.009197 +0.009267 +0.009378 +0.009444 +0.009246 +0.009192 +0.009226 +0.00931 +0.009409 +0.009459 +0.009305 +0.009242 +0.009285 +0.00935 +0.009453 +0.009511 +0.009355 +0.009273 +0.009336 +0.009402 +0.009502 +0.009574 +0.009392 +0.009333 +0.009383 +0.009448 +0.009546 +0.00961 +0.009454 +0.009406 +0.009421 +0.009503 +0.009594 +0.009665 +0.009514 +0.009412 +0.009451 +0.009544 +0.009639 +0.009701 +0.009537 +0.009466 +0.009506 +0.009596 +0.009708 +0.009757 +0.00961 +0.009514 +0.009554 +0.009644 +0.009743 +0.009802 +0.009636 +0.009547 +0.009618 +0.009735 +0.009798 +0.009862 +0.009684 +0.00959 +0.009662 +0.009757 +0.009849 +0.009897 +0.009718 +0.009644 +0.009711 +0.009792 +0.0099 +0.009966 +0.009776 +0.009699 +0.009753 +0.009851 +0.009934 +0.009994 +0.009837 +0.00974 +0.009805 +0.009907 +0.010024 +0.010084 +0.009867 +0.009788 +0.009854 +0.009937 +0.010036 +0.010097 +0.009925 +0.009871 +0.009906 +0.009999 +0.010084 +0.010136 +0.009971 +0.009898 +0.009949 +0.010035 +0.010158 +0.010204 +0.01003 +0.009965 +0.010018 +0.010083 +0.010197 +0.010271 +0.01011 +0.010002 +0.010026 +0.010127 +0.01024 +0.010305 +0.010138 +0.010056 +0.010086 +0.010189 +0.010298 +0.010369 +0.01018 +0.010088 +0.010158 +0.010242 +0.010331 +0.010417 +0.010208 +0.010101 +0.010152 +0.0102 +0.01025 +0.010279 +0.01009 +0.009982 +0.009973 +0.01001 +0.010049 +0.010046 +0.00982 +0.009691 +0.009708 +0.00975 +0.009774 +0.009786 +0.009591 +0.009462 +0.009451 +0.009488 +0.009543 +0.00955 +0.009343 +0.009227 +0.009233 +0.009265 +0.009319 +0.009312 +0.009127 +0.009008 +0.009041 +0.009101 +0.009128 +0.009113 +0.008922 +0.008817 +0.00887 +0.008878 +0.008926 +0.008947 +0.008789 +0.008674 +0.008709 +0.008762 +0.008817 +0.008834 +0.008663 +0.008574 +0.008602 +0.008649 +0.008699 +0.008738 +0.008564 +0.008484 +0.008533 +0.008579 +0.008633 +0.008672 +0.008514 +0.008436 +0.008498 +0.008531 +0.008595 +0.008647 +0.008507 +0.008444 +0.008497 +0.008579 +0.008644 +0.008704 +0.008548 +0.008475 +0.008529 +0.008615 +0.008683 +0.008748 +0.00861 +0.008528 +0.008576 +0.008653 +0.008739 +0.008795 +0.00864 +0.008569 +0.008624 +0.008687 +0.008784 +0.008856 +0.008708 +0.008648 +0.008683 +0.008737 +0.008799 +0.008862 +0.008725 +0.008645 +0.008697 +0.008786 +0.008874 +0.00893 +0.008762 +0.008699 +0.008745 +0.008819 +0.008902 +0.008976 +0.008815 +0.008742 +0.008794 +0.008874 +0.008954 +0.00901 +0.00887 +0.008788 +0.008835 +0.008914 +0.009009 +0.009087 +0.008905 +0.008846 +0.008883 +0.008959 +0.009049 +0.009088 +0.008941 +0.008868 +0.008927 +0.008994 +0.009086 +0.009155 +0.008994 +0.008916 +0.008978 +0.009068 +0.009137 +0.009189 +0.009043 +0.008961 +0.009015 +0.009095 +0.00918 +0.009239 +0.009082 +0.009011 +0.009089 +0.009181 +0.009242 +0.009288 +0.009116 +0.009055 +0.0091 +0.009173 +0.009267 +0.009329 +0.009178 +0.009105 +0.009162 +0.009251 +0.009329 +0.009379 +0.009218 +0.00913 +0.009195 +0.009275 +0.009372 +0.009421 +0.009278 +0.009209 +0.009248 +0.009334 +0.009431 +0.009491 +0.009333 +0.009225 +0.009283 +0.009367 +0.009466 +0.009528 +0.009369 +0.009274 +0.009332 +0.009413 +0.009504 +0.00957 +0.009417 +0.009327 +0.009385 +0.009484 +0.009561 +0.009614 +0.009464 +0.009368 +0.00943 +0.009522 +0.009608 +0.009673 +0.009509 +0.009448 +0.009526 +0.009584 +0.00965 +0.009696 +0.009536 +0.00946 +0.00953 +0.009609 +0.00973 +0.009766 +0.009592 +0.009529 +0.009589 +0.009655 +0.00974 +0.009818 +0.00964 +0.009564 +0.009633 +0.009703 +0.009805 +0.009892 +0.00971 +0.009622 +0.009674 +0.009752 +0.009885 +0.009917 +0.00972 +0.009647 +0.009711 +0.009815 +0.009911 +0.009976 +0.009812 +0.009721 +0.009756 +0.009848 +0.009943 +0.01001 +0.009834 +0.009756 +0.009813 +0.00989 +0.010005 +0.010069 +0.0099 +0.009811 +0.009867 +0.009961 +0.010071 +0.010161 +0.009963 +0.009834 +0.009924 +0.009987 +0.010099 +0.010158 +0.009983 +0.009912 +0.009965 +0.010044 +0.010172 +0.010242 +0.010021 +0.00995 +0.010014 +0.010104 +0.010209 +0.010274 +0.010096 +0.010012 +0.010084 +0.010163 +0.010257 +0.010327 +0.010165 +0.010059 +0.010094 +0.010189 +0.010302 +0.010391 +0.010196 +0.010108 +0.010186 +0.01024 +0.010355 +0.01043 +0.010237 +0.010167 +0.01022 +0.010325 +0.010423 +0.010483 +0.010283 +0.010202 +0.010267 +0.010357 +0.010467 +0.010538 +0.010373 +0.010275 +0.010315 +0.010391 +0.010496 +0.010539 +0.010303 +0.010215 +0.010216 +0.010275 +0.010357 +0.010389 +0.010151 +0.010029 +0.010055 +0.010093 +0.010157 +0.010158 +0.009933 +0.009811 +0.009828 +0.009878 +0.009928 +0.009921 +0.009702 +0.009578 +0.009587 +0.00967 +0.009677 +0.009688 +0.009475 +0.009358 +0.009388 +0.009415 +0.009472 +0.00947 +0.009254 +0.009138 +0.009155 +0.009205 +0.00924 +0.009281 +0.009093 +0.008954 +0.008983 +0.009032 +0.009064 +0.009103 +0.008907 +0.008804 +0.008823 +0.008875 +0.008921 +0.008959 +0.008809 +0.008681 +0.008693 +0.008741 +0.008781 +0.008819 +0.00865 +0.008545 +0.00858 +0.008648 +0.008707 +0.008739 +0.008584 +0.008499 +0.008535 +0.008607 +0.008687 +0.008744 +0.008596 +0.008516 +0.008586 +0.008642 +0.008736 +0.00878 +0.008638 +0.008571 +0.008611 +0.008687 +0.00876 +0.00885 +0.008684 +0.008632 +0.00865 +0.008714 +0.008817 +0.008871 +0.00871 +0.008643 +0.0087 +0.008767 +0.008853 +0.008921 +0.008765 +0.008708 +0.008741 +0.008829 +0.008902 +0.008967 +0.008798 +0.008726 +0.008771 +0.008857 +0.008956 +0.009013 +0.008847 +0.008776 +0.00883 +0.008937 +0.009028 +0.009052 +0.00888 +0.008813 +0.008888 +0.008941 +0.009026 +0.009091 +0.008938 +0.008862 +0.008911 +0.009005 +0.009101 +0.009148 +0.008992 +0.008904 +0.008955 +0.009035 +0.009123 +0.009193 +0.009028 +0.008953 +0.009017 +0.009104 +0.009188 +0.009243 +0.009085 +0.009013 +0.009048 +0.009124 +0.00921 +0.00927 +0.009117 +0.009047 +0.009127 +0.009184 +0.00928 +0.009327 +0.009161 +0.009087 +0.00913 +0.009211 +0.009311 +0.00938 +0.009204 +0.009139 +0.009203 +0.009286 +0.00937 +0.009436 +0.009263 +0.009174 +0.009233 +0.009316 +0.009429 +0.009476 +0.009301 +0.009209 +0.009274 +0.009369 +0.009455 +0.009518 +0.009347 +0.009295 +0.009321 +0.009403 +0.009507 +0.009569 +0.009389 +0.009323 +0.009374 +0.009462 +0.009554 +0.009624 +0.009452 +0.009366 +0.00943 +0.00952 +0.00961 +0.009667 +0.009491 +0.009442 +0.009472 +0.009538 +0.009643 +0.0097 +0.009549 +0.009454 +0.009518 +0.009621 +0.00971 +0.009748 +0.009587 +0.009502 +0.009572 +0.009658 +0.009742 +0.009805 +0.00965 +0.009572 +0.009619 +0.009709 +0.009791 +0.009846 +0.009691 +0.00961 +0.009668 +0.009783 +0.009827 +0.00991 +0.00976 +0.009654 +0.009717 +0.009783 +0.009884 +0.009954 +0.009776 +0.009702 +0.009763 +0.009852 +0.009938 +0.010014 +0.009832 +0.009742 +0.00982 +0.009893 +0.009996 +0.010064 +0.0099 +0.009816 +0.009848 +0.009947 +0.010082 +0.010106 +0.009927 +0.009832 +0.009904 +0.010027 +0.01008 +0.010159 +0.010004 +0.009882 +0.009952 +0.010056 +0.010142 +0.010211 +0.01004 +0.009954 +0.009999 +0.010125 +0.010202 +0.010268 +0.010073 +0.009994 +0.010061 +0.010153 +0.010249 +0.010347 +0.010126 +0.010044 +0.010113 +0.010205 +0.010308 +0.010346 +0.010201 +0.010095 +0.01015 +0.010244 +0.01035 +0.010435 +0.010249 +0.010162 +0.010216 +0.010294 +0.0104 +0.010468 +0.010275 +0.010198 +0.010244 +0.010371 +0.010485 +0.010564 +0.010351 +0.010246 +0.010285 +0.010389 +0.01051 +0.010562 +0.010394 +0.010301 +0.010376 +0.010447 +0.010566 +0.010617 +0.010434 +0.010363 +0.010404 +0.010493 +0.010584 +0.010642 +0.010413 +0.010301 +0.010307 +0.010356 +0.010413 +0.010451 +0.010216 +0.01007 +0.010068 +0.010086 +0.010141 +0.010112 +0.009899 +0.009773 +0.009766 +0.0098 +0.009856 +0.009857 +0.00965 +0.009503 +0.009514 +0.009542 +0.009556 +0.009571 +0.009363 +0.009228 +0.00924 +0.009255 +0.009297 +0.009332 +0.00913 +0.009026 +0.009016 +0.009034 +0.00906 +0.009097 +0.008901 +0.00878 +0.008797 +0.008843 +0.008895 +0.00891 +0.008737 +0.008633 +0.008652 +0.008692 +0.008739 +0.008786 +0.008601 +0.008505 +0.008531 +0.008582 +0.008623 +0.008642 +0.008485 +0.008383 +0.008416 +0.008455 +0.008527 +0.008562 +0.00843 +0.008325 +0.008357 +0.008413 +0.008485 +0.00854 +0.008388 +0.008332 +0.008353 +0.008443 +0.008505 +0.008566 +0.008425 +0.008364 +0.008418 +0.008486 +0.008568 +0.008626 +0.008471 +0.008397 +0.00845 +0.008523 +0.008597 +0.008654 +0.008518 +0.008433 +0.008494 +0.008567 +0.008653 +0.008726 +0.008565 +0.008483 +0.008528 +0.008621 +0.00868 +0.008746 +0.008613 +0.008531 +0.008585 +0.008657 +0.008718 +0.008783 +0.008646 +0.008563 +0.008614 +0.008689 +0.008784 +0.008834 +0.008686 +0.008636 +0.008672 +0.008734 +0.008811 +0.008881 +0.008727 +0.008661 +0.008709 +0.008778 +0.008886 +0.008949 +0.008761 +0.008698 +0.008753 +0.008829 +0.008913 +0.008953 +0.008809 +0.008746 +0.008794 +0.008864 +0.008949 +0.009006 +0.008876 +0.008796 +0.008857 +0.008917 +0.009003 +0.009047 +0.008903 +0.008833 +0.008871 +0.008958 +0.009035 +0.009099 +0.008966 +0.008885 +0.008963 +0.009021 +0.009093 +0.009138 +0.00898 +0.008912 +0.008964 +0.009034 +0.009138 +0.009196 +0.009031 +0.008958 +0.009018 +0.009103 +0.009184 +0.00924 +0.009099 +0.009012 +0.009056 +0.009134 +0.009224 +0.009284 +0.00913 +0.009065 +0.009104 +0.009188 +0.009289 +0.009346 +0.009202 +0.009089 +0.009144 +0.009229 +0.009327 +0.009425 +0.0092 +0.009131 +0.0092 +0.009276 +0.009368 +0.009422 +0.009268 +0.009179 +0.009244 +0.009347 +0.009433 +0.009453 +0.009318 +0.009229 +0.009299 +0.009373 +0.00946 +0.00952 +0.009366 +0.009294 +0.009361 +0.009449 +0.009505 +0.009545 +0.009402 +0.009327 +0.009392 +0.009458 +0.00957 +0.009614 +0.009458 +0.009378 +0.009445 +0.009517 +0.009596 +0.009666 +0.009506 +0.009431 +0.009474 +0.00956 +0.00965 +0.009699 +0.009547 +0.009458 +0.009538 +0.009619 +0.009723 +0.009808 +0.009595 +0.009511 +0.009583 +0.009645 +0.009751 +0.009803 +0.009644 +0.009569 +0.009634 +0.009704 +0.009808 +0.009863 +0.009701 +0.009626 +0.009678 +0.009744 +0.009847 +0.00992 +0.009739 +0.009659 +0.009719 +0.009813 +0.009917 +0.009982 +0.009823 +0.009724 +0.009767 +0.009839 +0.009929 +0.010013 +0.009829 +0.009745 +0.009815 +0.00991 +0.010022 +0.010073 +0.009905 +0.009799 +0.009858 +0.009957 +0.010054 +0.010111 +0.009934 +0.009862 +0.009933 +0.010022 +0.010115 +0.010162 +0.009983 +0.00992 +0.009994 +0.010041 +0.010133 +0.010216 +0.010065 +0.009958 +0.010026 +0.010118 +0.010204 +0.010251 +0.010082 +0.010006 +0.01007 +0.010163 +0.010256 +0.010317 +0.01014 +0.010053 +0.010112 +0.010201 +0.010319 +0.010376 +0.010203 +0.010112 +0.010207 +0.010266 +0.010354 +0.010418 +0.010236 +0.010162 +0.010229 +0.010284 +0.010393 +0.010457 +0.010255 +0.010127 +0.010145 +0.010205 +0.010259 +0.010285 +0.010075 +0.009936 +0.009961 +0.010006 +0.010042 +0.010028 +0.009831 +0.009666 +0.009678 +0.009716 +0.009761 +0.00979 +0.009548 +0.009404 +0.00941 +0.009442 +0.009483 +0.009526 +0.009278 +0.009172 +0.009167 +0.009208 +0.009249 +0.009275 +0.009063 +0.008944 +0.008959 +0.008992 +0.009034 +0.009069 +0.008865 +0.008763 +0.008779 +0.008831 +0.008884 +0.008908 +0.008726 +0.008614 +0.008658 +0.008681 +0.008728 +0.008752 +0.008587 +0.008486 +0.008518 +0.008577 +0.00862 +0.008662 +0.008483 +0.008383 +0.008408 +0.008465 +0.008522 +0.008559 +0.008404 +0.008318 +0.008346 +0.008403 +0.008501 +0.008527 +0.008379 +0.008301 +0.008352 +0.008423 +0.00851 +0.008564 +0.008422 +0.008377 +0.008419 +0.008467 +0.008532 +0.008594 +0.008449 +0.008393 +0.008435 +0.008504 +0.008592 +0.008651 +0.008524 +0.008441 +0.008495 +0.008556 +0.008619 +0.008678 +0.008545 +0.008477 +0.008528 +0.008589 +0.008672 +0.008743 +0.008595 +0.008529 +0.008575 +0.008641 +0.008705 +0.008776 +0.008639 +0.008573 +0.0086 +0.008675 +0.008765 +0.008833 +0.008683 +0.008598 +0.008656 +0.008725 +0.00881 +0.008851 +0.008708 +0.008642 +0.008695 +0.008771 +0.008851 +0.008912 +0.008755 +0.008681 +0.008742 +0.008805 +0.008893 +0.00895 +0.008805 +0.008731 +0.008774 +0.008863 +0.00895 +0.009051 +0.008869 +0.008778 +0.008816 +0.008884 +0.008975 +0.009066 +0.008879 +0.008809 +0.008872 +0.008936 +0.009033 +0.009097 +0.008941 +0.008863 +0.008909 +0.008994 +0.009081 +0.009133 +0.008987 +0.008906 +0.008972 +0.009033 +0.009127 +0.009182 +0.009021 +0.008957 +0.009003 +0.009107 +0.009172 +0.009223 +0.009066 +0.008993 +0.009065 +0.00912 +0.009206 +0.009273 +0.009122 +0.009034 +0.009095 +0.009175 +0.009272 +0.009323 +0.009183 +0.009093 +0.00914 +0.009221 +0.009303 +0.009364 +0.009204 +0.00913 +0.009187 +0.009263 +0.009357 +0.009428 +0.009297 +0.009189 +0.009228 +0.009292 +0.009391 +0.009451 +0.009298 +0.00922 +0.009286 +0.009367 +0.009492 +0.009509 +0.009342 +0.009264 +0.009318 +0.009398 +0.009485 +0.009555 +0.009399 +0.009319 +0.009372 +0.009458 +0.009564 +0.009625 +0.009437 +0.009364 +0.009419 +0.009521 +0.009577 +0.009668 +0.009471 +0.009412 +0.009467 +0.009526 +0.009643 +0.009702 +0.009535 +0.009441 +0.009508 +0.009598 +0.009684 +0.009758 +0.009589 +0.009508 +0.009554 +0.009643 +0.009733 +0.009796 +0.009643 +0.009545 +0.009604 +0.009688 +0.009802 +0.0099 +0.009691 +0.009583 +0.009637 +0.009756 +0.009829 +0.009887 +0.00972 +0.009645 +0.009694 +0.009789 +0.00991 +0.009951 +0.009784 +0.009682 +0.009745 +0.009833 +0.009933 +0.01 +0.009817 +0.009733 +0.00981 +0.009901 +0.01 +0.010059 +0.009885 +0.009796 +0.009837 +0.009934 +0.010022 +0.01009 +0.009924 +0.009852 +0.009909 +0.009987 +0.010087 +0.010131 +0.009957 +0.009884 +0.009942 +0.010034 +0.010148 +0.010203 +0.010038 +0.00994 +0.010005 +0.010104 +0.010164 +0.010228 +0.010074 +0.01001 +0.01006 +0.010125 +0.010215 +0.01029 +0.010122 +0.010041 +0.010083 +0.010194 +0.010282 +0.010347 +0.010179 +0.010089 +0.010136 +0.010243 +0.010333 +0.010404 +0.010246 +0.010123 +0.010188 +0.010278 +0.010386 +0.010437 +0.010232 +0.010113 +0.010175 +0.010202 +0.01023 +0.010277 +0.010061 +0.009962 +0.009984 +0.010013 +0.01006 +0.010043 +0.009834 +0.009699 +0.009713 +0.009741 +0.009787 +0.009804 +0.009593 +0.009468 +0.009484 +0.009497 +0.009537 +0.00955 +0.009332 +0.009209 +0.009218 +0.009247 +0.00932 +0.009324 +0.009114 +0.009011 +0.008999 +0.009038 +0.009084 +0.009102 +0.008924 +0.008818 +0.008837 +0.00888 +0.008935 +0.008951 +0.008774 +0.008669 +0.008702 +0.008732 +0.008792 +0.008822 +0.008655 +0.00855 +0.008589 +0.008644 +0.008678 +0.008701 +0.008539 +0.008451 +0.008484 +0.008547 +0.00858 +0.008622 +0.008463 +0.008386 +0.008435 +0.008496 +0.008567 +0.008617 +0.008466 +0.00839 +0.008431 +0.008515 +0.008597 +0.008647 +0.008517 +0.008445 +0.00849 +0.008562 +0.008642 +0.008706 +0.008547 +0.008467 +0.008528 +0.008601 +0.008674 +0.008738 +0.008596 +0.00852 +0.008587 +0.008643 +0.008718 +0.008775 +0.008639 +0.008562 +0.008617 +0.008717 +0.008771 +0.008823 +0.008667 +0.008613 +0.008664 +0.008724 +0.008807 +0.008868 +0.008727 +0.008652 +0.008697 +0.008765 +0.00886 +0.008916 +0.008766 +0.008711 +0.008751 +0.00881 +0.008896 +0.008966 +0.008834 +0.008743 +0.008778 +0.008854 +0.008939 +0.009009 +0.008859 +0.008781 +0.008843 +0.008911 +0.009009 +0.009041 +0.008892 +0.008827 +0.008867 +0.008944 +0.009037 +0.009096 +0.008951 +0.008873 +0.008935 +0.009005 +0.009066 +0.00915 +0.008978 +0.00891 +0.008962 +0.009045 +0.009142 +0.009188 +0.009058 +0.00895 +0.009012 +0.009092 +0.00918 +0.009223 +0.009064 +0.008999 +0.009045 +0.009129 +0.009223 +0.00928 +0.009123 +0.009058 +0.009115 +0.009173 +0.009273 +0.009327 +0.009155 +0.009083 +0.009142 +0.009235 +0.009317 +0.009368 +0.009231 +0.009155 +0.009225 +0.009262 +0.009357 +0.00942 +0.009252 +0.009176 +0.009232 +0.009313 +0.009412 +0.009453 +0.009314 +0.009237 +0.009286 +0.00937 +0.009459 +0.009505 +0.009341 +0.009265 +0.00934 +0.009414 +0.009499 +0.009567 +0.009416 +0.009326 +0.00937 +0.00947 +0.009578 +0.009609 +0.009431 +0.009355 +0.009421 +0.009501 +0.009625 +0.009647 +0.009478 +0.00941 +0.009454 +0.009549 +0.009653 +0.009699 +0.009546 +0.009476 +0.009534 +0.009608 +0.009703 +0.009747 +0.009582 +0.009503 +0.009571 +0.009644 +0.009738 +0.009812 +0.009661 +0.00959 +0.009617 +0.009712 +0.00977 +0.009847 +0.009684 +0.009598 +0.009684 +0.009739 +0.009827 +0.009899 +0.009751 +0.009659 +0.009719 +0.009795 +0.009881 +0.009954 +0.009786 +0.009689 +0.009756 +0.009849 +0.009944 +0.010015 +0.009848 +0.009766 +0.009843 +0.009894 +0.009967 +0.010029 +0.009879 +0.009802 +0.009852 +0.009943 +0.010062 +0.01012 +0.009943 +0.009844 +0.009893 +0.009984 +0.010099 +0.010156 +0.009979 +0.009896 +0.009954 +0.010057 +0.010168 +0.010222 +0.01002 +0.009937 +0.010007 +0.010131 +0.0102 +0.010255 +0.010077 +0.009983 +0.010054 +0.010142 +0.010256 +0.010331 +0.010124 +0.010037 +0.010112 +0.010186 +0.010291 +0.010362 +0.010178 +0.010095 +0.010175 +0.010263 +0.010353 +0.010421 +0.010221 +0.010137 +0.010213 +0.010314 +0.010435 +0.01045 +0.010272 +0.010203 +0.010305 +0.010333 +0.010439 +0.010499 +0.010302 +0.010213 +0.01027 +0.010348 +0.010399 +0.010419 +0.010218 +0.010101 +0.01011 +0.010173 +0.010214 +0.010222 +0.009995 +0.009861 +0.009894 +0.009952 +0.009981 +0.010014 +0.009742 +0.009598 +0.009624 +0.009662 +0.009702 +0.009718 +0.009469 +0.009366 +0.009381 +0.009422 +0.009453 +0.009454 +0.009224 +0.00911 +0.009143 +0.009161 +0.009204 +0.009223 +0.009011 +0.008907 +0.008939 +0.008971 +0.009006 +0.009022 +0.008843 +0.008738 +0.008791 +0.008802 +0.008837 +0.008867 +0.008686 +0.008591 +0.008616 +0.008667 +0.008715 +0.008754 +0.008555 +0.008457 +0.008505 +0.008555 +0.008601 +0.008637 +0.008488 +0.008384 +0.008419 +0.00848 +0.008545 +0.008579 +0.008437 +0.008362 +0.008408 +0.008483 +0.008556 +0.00864 +0.008491 +0.008427 +0.008451 +0.008509 +0.00859 +0.008647 +0.008504 +0.008435 +0.00848 +0.008561 +0.008653 +0.008709 +0.008572 +0.008485 +0.008543 +0.008594 +0.008685 +0.008745 +0.008595 +0.008521 +0.00857 +0.008651 +0.008746 +0.008811 +0.008651 +0.00857 +0.008618 +0.008688 +0.008769 +0.008828 +0.008703 +0.008606 +0.008654 +0.008758 +0.008797 +0.008872 +0.008715 +0.008648 +0.0087 +0.008784 +0.008856 +0.008919 +0.008767 +0.008705 +0.008745 +0.008818 +0.008915 +0.00897 +0.008807 +0.008742 +0.008794 +0.00886 +0.008961 +0.00902 +0.00885 +0.008782 +0.008834 +0.008914 +0.009037 +0.009077 +0.008899 +0.00881 +0.008867 +0.008953 +0.009057 +0.00909 +0.008935 +0.008873 +0.008917 +0.008999 +0.009102 +0.009163 +0.008993 +0.008907 +0.008973 +0.009037 +0.009128 +0.009195 +0.009032 +0.008954 +0.009025 +0.009108 +0.009187 +0.009242 +0.009085 +0.008997 +0.009078 +0.009141 +0.009207 +0.009263 +0.009119 +0.009039 +0.009107 +0.00921 +0.009284 +0.00933 +0.009166 +0.00909 +0.009139 +0.009222 +0.009311 +0.00938 +0.009209 +0.009145 +0.009205 +0.00928 +0.009372 +0.009443 +0.009258 +0.009173 +0.00924 +0.009319 +0.009409 +0.009468 +0.009306 +0.00923 +0.009313 +0.009363 +0.009446 +0.009516 +0.009349 +0.009282 +0.009345 +0.009415 +0.00952 +0.009567 +0.009389 +0.00932 +0.009374 +0.009461 +0.009554 +0.00962 +0.009447 +0.00937 +0.009439 +0.009499 +0.009615 +0.009675 +0.009493 +0.009411 +0.009468 +0.009565 +0.009686 +0.009696 +0.009539 +0.009463 +0.009514 +0.009622 +0.009706 +0.009751 +0.009598 +0.009525 +0.009561 +0.009647 +0.009742 +0.0098 +0.009632 +0.009567 +0.009619 +0.009722 +0.009814 +0.009851 +0.009692 +0.009611 +0.009665 +0.009741 +0.009842 +0.009918 +0.009771 +0.009678 +0.00972 +0.009789 +0.009885 +0.009954 +0.009789 +0.009694 +0.009763 +0.009838 +0.00995 +0.010022 +0.009829 +0.009768 +0.009822 +0.009888 +0.009993 +0.010068 +0.009885 +0.009803 +0.009859 +0.009957 +0.010055 +0.010126 +0.009953 +0.009879 +0.009908 +0.009987 +0.010075 +0.010159 +0.009991 +0.009888 +0.009966 +0.01007 +0.010174 +0.010218 +0.010039 +0.009936 +0.010005 +0.010101 +0.010194 +0.010263 +0.010098 +0.010025 +0.010072 +0.010165 +0.01027 +0.010296 +0.010127 +0.010042 +0.01015 +0.010196 +0.010291 +0.010363 +0.010201 +0.010141 +0.010162 +0.010255 +0.010341 +0.010402 +0.01023 +0.010154 +0.010213 +0.010292 +0.010433 +0.010482 +0.0103 +0.010225 +0.010256 +0.01034 +0.010459 +0.010521 +0.010337 +0.010243 +0.010325 +0.010412 +0.010471 +0.010486 +0.010277 +0.010141 +0.010158 +0.010224 +0.010277 +0.010275 +0.010044 +0.00993 +0.009953 +0.009973 +0.010035 +0.010028 +0.009773 +0.009657 +0.009666 +0.009699 +0.009745 +0.009753 +0.009533 +0.009404 +0.00942 +0.009467 +0.009518 +0.009463 +0.009238 +0.009121 +0.009139 +0.009176 +0.009222 +0.009218 +0.009027 +0.008897 +0.008914 +0.008954 +0.008994 +0.009003 +0.008816 +0.008707 +0.008714 +0.008765 +0.008821 +0.008851 +0.008645 +0.008535 +0.008566 +0.008606 +0.008661 +0.008688 +0.008509 +0.008424 +0.008451 +0.008481 +0.008552 +0.008568 +0.008393 +0.0083 +0.008325 +0.008388 +0.008456 +0.008503 +0.008333 +0.008248 +0.008299 +0.008369 +0.008435 +0.008493 +0.008357 +0.008275 +0.008333 +0.008387 +0.008481 +0.008531 +0.008382 +0.008319 +0.008365 +0.008437 +0.008512 +0.008581 +0.008444 +0.008388 +0.008415 +0.00847 +0.008545 +0.00861 +0.008486 +0.008404 +0.00844 +0.00851 +0.008598 +0.00866 +0.008535 +0.008447 +0.008506 +0.008557 +0.008632 +0.008708 +0.008554 +0.008476 +0.008531 +0.008615 +0.008687 +0.008746 +0.008595 +0.00854 +0.008582 +0.00866 +0.00872 +0.00879 +0.008663 +0.008582 +0.008609 +0.008681 +0.008764 +0.008832 +0.008691 +0.008607 +0.008681 +0.008735 +0.008824 +0.008868 +0.008725 +0.00866 +0.008702 +0.00878 +0.008861 +0.008926 +0.008773 +0.008703 +0.008772 +0.008834 +0.008915 +0.008954 +0.008814 +0.008739 +0.00879 +0.008872 +0.008965 +0.009034 +0.008857 +0.00878 +0.008835 +0.008916 +0.009007 +0.00905 +0.008896 +0.008826 +0.008906 +0.008948 +0.00904 +0.009102 +0.008945 +0.008873 +0.008918 +0.009019 +0.009098 +0.00915 +0.009 +0.008913 +0.008964 +0.009046 +0.009133 +0.009189 +0.009036 +0.008966 +0.009024 +0.009128 +0.009213 +0.00923 +0.00906 +0.008996 +0.009057 +0.009138 +0.009215 +0.009277 +0.009121 +0.009059 +0.009121 +0.009199 +0.009276 +0.009336 +0.009171 +0.009081 +0.009145 +0.00923 +0.009323 +0.009373 +0.009214 +0.009157 +0.009204 +0.00928 +0.009384 +0.009438 +0.009277 +0.009172 +0.009225 +0.009326 +0.009415 +0.00947 +0.009296 +0.00924 +0.009302 +0.009384 +0.009462 +0.009523 +0.009339 +0.009267 +0.009325 +0.009421 +0.009511 +0.009562 +0.009421 +0.009343 +0.009385 +0.009469 +0.009566 +0.009605 +0.009437 +0.009369 +0.009447 +0.009528 +0.009597 +0.009645 +0.009507 +0.00943 +0.009507 +0.009557 +0.009641 +0.009697 +0.009534 +0.009456 +0.00953 +0.009608 +0.00971 +0.009762 +0.00959 +0.009511 +0.009569 +0.009663 +0.009741 +0.009807 +0.009648 +0.009555 +0.009622 +0.009729 +0.009819 +0.009875 +0.009681 +0.009591 +0.00966 +0.00975 +0.009844 +0.009911 +0.009768 +0.009653 +0.009714 +0.009794 +0.009904 +0.009954 +0.009791 +0.009719 +0.009768 +0.009849 +0.009953 +0.010016 +0.009832 +0.009763 +0.009809 +0.009888 +0.010006 +0.010075 +0.009937 +0.009792 +0.009846 +0.009943 +0.010045 +0.010133 +0.009942 +0.009841 +0.009913 +0.00999 +0.010096 +0.010176 +0.009985 +0.009899 +0.009975 +0.010055 +0.010162 +0.010245 +0.010051 +0.009938 +0.01001 +0.010092 +0.010197 +0.010265 +0.010094 +0.010038 +0.010058 +0.010194 +0.010255 +0.010295 +0.010129 +0.010043 +0.010108 +0.010202 +0.010293 +0.010371 +0.010191 +0.01011 +0.01019 +0.010277 +0.010349 +0.010411 +0.010251 +0.010147 +0.010205 +0.010313 +0.010407 +0.010449 +0.010288 +0.010209 +0.010212 +0.010251 +0.0103 +0.010337 +0.010109 +0.009969 +0.010008 +0.010051 +0.010096 +0.010112 +0.009892 +0.009753 +0.009764 +0.009798 +0.00983 +0.009846 +0.009632 +0.009496 +0.009529 +0.009553 +0.009584 +0.009586 +0.00938 +0.009234 +0.009248 +0.009286 +0.009327 +0.009332 +0.009107 +0.008984 +0.00902 +0.009059 +0.00911 +0.009083 +0.008891 +0.008783 +0.008793 +0.008837 +0.008875 +0.008903 +0.008707 +0.008607 +0.008653 +0.008698 +0.00873 +0.008746 +0.008579 +0.008474 +0.008492 +0.008544 +0.008588 +0.008613 +0.008452 +0.008365 +0.008392 +0.008473 +0.008507 +0.008518 +0.008362 +0.008281 +0.008324 +0.008384 +0.008419 +0.008486 +0.008318 +0.008248 +0.008316 +0.008389 +0.008455 +0.008503 +0.008367 +0.008289 +0.008344 +0.008418 +0.008501 +0.008546 +0.008411 +0.008337 +0.008383 +0.008476 +0.008551 +0.008594 +0.008453 +0.008382 +0.008445 +0.008524 +0.008574 +0.008636 +0.008488 +0.008418 +0.008478 +0.008547 +0.008646 +0.008688 +0.008531 +0.008451 +0.008511 +0.008593 +0.008669 +0.008726 +0.008571 +0.008512 +0.008556 +0.008647 +0.00872 +0.008805 +0.008624 +0.008536 +0.008598 +0.008663 +0.008754 +0.008807 +0.008673 +0.008595 +0.008666 +0.008717 +0.008822 +0.008852 +0.00871 +0.008634 +0.008684 +0.008749 +0.008827 +0.008897 +0.008741 +0.008682 +0.008738 +0.008809 +0.008896 +0.00896 +0.008812 +0.008724 +0.008766 +0.008846 +0.008925 +0.008989 +0.008844 +0.008773 +0.008807 +0.00889 +0.008981 +0.00906 +0.008923 +0.008818 +0.008851 +0.00892 +0.009034 +0.009079 +0.008917 +0.008846 +0.0089 +0.008999 +0.009086 +0.009141 +0.008976 +0.008904 +0.008941 +0.009011 +0.009113 +0.009161 +0.009013 +0.008941 +0.008993 +0.009084 +0.009171 +0.009238 +0.00907 +0.00899 +0.009034 +0.009116 +0.009227 +0.009246 +0.009103 +0.009025 +0.00908 +0.009184 +0.009273 +0.009316 +0.00916 +0.009073 +0.009121 +0.009195 +0.009297 +0.009358 +0.009195 +0.009132 +0.009206 +0.009245 +0.009341 +0.0094 +0.00924 +0.009162 +0.009221 +0.009288 +0.009387 +0.009471 +0.0093 +0.009256 +0.009281 +0.009333 +0.009431 +0.00949 +0.009338 +0.009246 +0.009301 +0.009401 +0.009486 +0.009569 +0.009397 +0.009322 +0.009365 +0.009427 +0.009534 +0.009597 +0.009427 +0.00937 +0.009391 +0.009484 +0.009585 +0.009661 +0.009488 +0.009405 +0.009446 +0.009554 +0.009665 +0.009672 +0.009505 +0.009433 +0.009501 +0.009582 +0.00969 +0.009755 +0.009586 +0.009483 +0.009541 +0.009633 +0.009726 +0.009787 +0.009623 +0.00953 +0.009604 +0.009691 +0.009799 +0.009852 +0.009671 +0.009568 +0.009639 +0.009721 +0.009834 +0.009914 +0.009718 +0.009642 +0.009721 +0.009783 +0.009881 +0.009931 +0.009744 +0.009677 +0.009743 +0.009815 +0.009924 +0.009994 +0.009818 +0.009745 +0.009803 +0.009891 +0.009982 +0.010031 +0.009852 +0.009773 +0.009849 +0.009923 +0.010015 +0.010102 +0.009939 +0.00987 +0.009888 +0.009974 +0.01006 +0.010143 +0.009962 +0.009863 +0.009929 +0.010033 +0.01012 +0.010199 +0.010029 +0.009942 +0.01 +0.010068 +0.010164 +0.010233 +0.010066 +0.009972 +0.01003 +0.010153 +0.010245 +0.010307 +0.01013 +0.010051 +0.0101 +0.010156 +0.01026 +0.010329 +0.010165 +0.010064 +0.010153 +0.010259 +0.010347 +0.010394 +0.010214 +0.010109 +0.010183 +0.010277 +0.010381 +0.01048 +0.010232 +0.010146 +0.010205 +0.01029 +0.010354 +0.010369 +0.010158 +0.010019 +0.010063 +0.010128 +0.010207 +0.010139 +0.009928 +0.009783 +0.009815 +0.009852 +0.009884 +0.009929 +0.009663 +0.009534 +0.009537 +0.009612 +0.00962 +0.009628 +0.009425 +0.009274 +0.009302 +0.009335 +0.009367 +0.009368 +0.009166 +0.009043 +0.00906 +0.009098 +0.009137 +0.009145 +0.008969 +0.008806 +0.008833 +0.008875 +0.008915 +0.008938 +0.008743 +0.00863 +0.008662 +0.008726 +0.008766 +0.008767 +0.008597 +0.008487 +0.008525 +0.008571 +0.008636 +0.008646 +0.008478 +0.008382 +0.008418 +0.008471 +0.008526 +0.008549 +0.008376 +0.008287 +0.008323 +0.008394 +0.008449 +0.008494 +0.008314 +0.008229 +0.008272 +0.008344 +0.008417 +0.008482 +0.008308 +0.008238 +0.0083 +0.008366 +0.008443 +0.008508 +0.00838 +0.008278 +0.008321 +0.0084 +0.008493 +0.008546 +0.008411 +0.008327 +0.008381 +0.008447 +0.008534 +0.008601 +0.008436 +0.008368 +0.008417 +0.008501 +0.008594 +0.008648 +0.008483 +0.008417 +0.008473 +0.008528 +0.008619 +0.008672 +0.00852 +0.008449 +0.008506 +0.008575 +0.008656 +0.008721 +0.008572 +0.008502 +0.008591 +0.008619 +0.00869 +0.008754 +0.008611 +0.008539 +0.008589 +0.008655 +0.00875 +0.008804 +0.008655 +0.008585 +0.00865 +0.008732 +0.008805 +0.008836 +0.008692 +0.00862 +0.008674 +0.008755 +0.00883 +0.008907 +0.008737 +0.008668 +0.00872 +0.008802 +0.008882 +0.008936 +0.008776 +0.008712 +0.008772 +0.008834 +0.008915 +0.008983 +0.008831 +0.008757 +0.008808 +0.008904 +0.008973 +0.009022 +0.008881 +0.008807 +0.008876 +0.008919 +0.009006 +0.009055 +0.008925 +0.008844 +0.008899 +0.008985 +0.009067 +0.009143 +0.008951 +0.008879 +0.008936 +0.009007 +0.009099 +0.009156 +0.00902 +0.008936 +0.008999 +0.009089 +0.009151 +0.009197 +0.009051 +0.008973 +0.009023 +0.009101 +0.009198 +0.009264 +0.009117 +0.009017 +0.009081 +0.009168 +0.009254 +0.009292 +0.009139 +0.009059 +0.009115 +0.009192 +0.009281 +0.009368 +0.009188 +0.009108 +0.009179 +0.009264 +0.009335 +0.009384 +0.009237 +0.00916 +0.00921 +0.009287 +0.00938 +0.009432 +0.009281 +0.009215 +0.009283 +0.009379 +0.009431 +0.00947 +0.009325 +0.009241 +0.0093 +0.009379 +0.00946 +0.00955 +0.009382 +0.009302 +0.009366 +0.009448 +0.009539 +0.009572 +0.009416 +0.009327 +0.00939 +0.009476 +0.009556 +0.009643 +0.009482 +0.009411 +0.009454 +0.009535 +0.009606 +0.009701 +0.009514 +0.009419 +0.009481 +0.009559 +0.009678 +0.009767 +0.009557 +0.009489 +0.00954 +0.009607 +0.009707 +0.009769 +0.009602 +0.009523 +0.009584 +0.009655 +0.009786 +0.009851 +0.009663 +0.009581 +0.009645 +0.00971 +0.009799 +0.009875 +0.009703 +0.009661 +0.009704 +0.009763 +0.009845 +0.009917 +0.009748 +0.009659 +0.009737 +0.009817 +0.0099 +0.009988 +0.00982 +0.009729 +0.009784 +0.009857 +0.009959 +0.010018 +0.009859 +0.009759 +0.009818 +0.00993 +0.010023 +0.010092 +0.009919 +0.009818 +0.00986 +0.009964 +0.010073 +0.010165 +0.00994 +0.00986 +0.009906 +0.010012 +0.010118 +0.010176 +0.010024 +0.009907 +0.009971 +0.010062 +0.010176 +0.010233 +0.010045 +0.00997 +0.010019 +0.010116 +0.010221 +0.010273 +0.010098 +0.010027 +0.010073 +0.010164 +0.0103 +0.010359 +0.01017 +0.010049 +0.010124 +0.010202 +0.010314 +0.010371 +0.010202 +0.010138 +0.010186 +0.010278 +0.010361 +0.010434 +0.010245 +0.010153 +0.010208 +0.010287 +0.010386 +0.010435 +0.01022 +0.010091 +0.010114 +0.010151 +0.010206 +0.010215 +0.009983 +0.009858 +0.009904 +0.009953 +0.009946 +0.009938 +0.009706 +0.009587 +0.009586 +0.009633 +0.009674 +0.00968 +0.009463 +0.009313 +0.009352 +0.009385 +0.009415 +0.009422 +0.009199 +0.009075 +0.009088 +0.009157 +0.009154 +0.009176 +0.008982 +0.008854 +0.008878 +0.008927 +0.008972 +0.008996 +0.008787 +0.008686 +0.00868 +0.008737 +0.008787 +0.008816 +0.008641 +0.008531 +0.008568 +0.008618 +0.008681 +0.008683 +0.008515 +0.008406 +0.008437 +0.008494 +0.008553 +0.008573 +0.00841 +0.008321 +0.008344 +0.008414 +0.00848 +0.008526 +0.008339 +0.008255 +0.008302 +0.008371 +0.00847 +0.008519 +0.00835 +0.008264 +0.008346 +0.008422 +0.008488 +0.008541 +0.008397 +0.00833 +0.008366 +0.008447 +0.00852 +0.008572 +0.008437 +0.008366 +0.008414 +0.008497 +0.008575 +0.008637 +0.008487 +0.008424 +0.008458 +0.008518 +0.008613 +0.008673 +0.008541 +0.008447 +0.008502 +0.008573 +0.008687 +0.008718 +0.008565 +0.008495 +0.008545 +0.008618 +0.008709 +0.008752 +0.008597 +0.008539 +0.008576 +0.008663 +0.008732 +0.008814 +0.008666 +0.00858 +0.008642 +0.008709 +0.008772 +0.008838 +0.008696 +0.008615 +0.008672 +0.008749 +0.008832 +0.008893 +0.00875 +0.008687 +0.008733 +0.008822 +0.008875 +0.008912 +0.00877 +0.008701 +0.008764 +0.008826 +0.008928 +0.00897 +0.008831 +0.008754 +0.008796 +0.008877 +0.008962 +0.009017 +0.008876 +0.008804 +0.008852 +0.008945 +0.009026 +0.009065 +0.008913 +0.008842 +0.008891 +0.008961 +0.009051 +0.009116 +0.008967 +0.00892 +0.008932 +0.009001 +0.009101 +0.00916 +0.009008 +0.008922 +0.008974 +0.00906 +0.009145 +0.009206 +0.009051 +0.008975 +0.009022 +0.009105 +0.009204 +0.009265 +0.009091 +0.009015 +0.009073 +0.009161 +0.009228 +0.009299 +0.009134 +0.009056 +0.009118 +0.009201 +0.00932 +0.009359 +0.009178 +0.009122 +0.009149 +0.009239 +0.009323 +0.009383 +0.009232 +0.009156 +0.009206 +0.009289 +0.009395 +0.009444 +0.009273 +0.009195 +0.009256 +0.00933 +0.009429 +0.009497 +0.00932 +0.009247 +0.009305 +0.009385 +0.00948 +0.009547 +0.009382 +0.009308 +0.009353 +0.009425 +0.009506 +0.009576 +0.009436 +0.009325 +0.009381 +0.009475 +0.009583 +0.009647 +0.009447 +0.009385 +0.00944 +0.009527 +0.009621 +0.009672 +0.009506 +0.009434 +0.009484 +0.009583 +0.009681 +0.009741 +0.009549 +0.009479 +0.009535 +0.00964 +0.009738 +0.009758 +0.009588 +0.009533 +0.009597 +0.009673 +0.009775 +0.009828 +0.009639 +0.009577 +0.00962 +0.009719 +0.009797 +0.009867 +0.009729 +0.009639 +0.009693 +0.009785 +0.009864 +0.009906 +0.009748 +0.00966 +0.009722 +0.009813 +0.009917 +0.010027 +0.009814 +0.009724 +0.009782 +0.009866 +0.009972 +0.009992 +0.009838 +0.009766 +0.009821 +0.009935 +0.010016 +0.010061 +0.009897 +0.009819 +0.009869 +0.009958 +0.010057 +0.010143 +0.009942 +0.009867 +0.00995 +0.010011 +0.010106 +0.010185 +0.010032 +0.00992 +0.009973 +0.010039 +0.010162 +0.010234 +0.010056 +0.00998 +0.010027 +0.010104 +0.010217 +0.010309 +0.010082 +0.010001 +0.010067 +0.01015 +0.010271 +0.010354 +0.01017 +0.01008 +0.010132 +0.010217 +0.010341 +0.010364 +0.010187 +0.010147 +0.010167 +0.010253 +0.010406 +0.010455 +0.010249 +0.010144 +0.01022 +0.01031 +0.010401 +0.010457 +0.010266 +0.010158 +0.010194 +0.010275 +0.010319 +0.010341 +0.010132 +0.009995 +0.010005 +0.010056 +0.010115 +0.01011 +0.009903 +0.009796 +0.009825 +0.009832 +0.009868 +0.009882 +0.009664 +0.009577 +0.00955 +0.00959 +0.009634 +0.009654 +0.009446 +0.009341 +0.009351 +0.009368 +0.00942 +0.009415 +0.009219 +0.009108 +0.009119 +0.009151 +0.009195 +0.009221 +0.009024 +0.008926 +0.008943 +0.008975 +0.009049 +0.009022 +0.008839 +0.008733 +0.008759 +0.008803 +0.00885 +0.008875 +0.008706 +0.008622 +0.008635 +0.008686 +0.008728 +0.00876 +0.008569 +0.008482 +0.008517 +0.008558 +0.008615 +0.008674 +0.008487 +0.008397 +0.008455 +0.00851 +0.008576 +0.008628 +0.008473 +0.008404 +0.008446 +0.008547 +0.008611 +0.008648 +0.008502 +0.008431 +0.008488 +0.008577 +0.008647 +0.008708 +0.008562 +0.008476 +0.008534 +0.0086 +0.008691 +0.008734 +0.008593 +0.008518 +0.008583 +0.008655 +0.008729 +0.008799 +0.008655 +0.008565 +0.008619 +0.008693 +0.008785 +0.008826 +0.008685 +0.00861 +0.008696 +0.008747 +0.008822 +0.00889 +0.00871 +0.008649 +0.008696 +0.008774 +0.008848 +0.008923 +0.00877 +0.008701 +0.008752 +0.00884 +0.008912 +0.00896 +0.008815 +0.008732 +0.008792 +0.008861 +0.008956 +0.009009 +0.008857 +0.008795 +0.008838 +0.00892 +0.009007 +0.00907 +0.00892 +0.008831 +0.008869 +0.008939 +0.009036 +0.009119 +0.008943 +0.008867 +0.008916 +0.009014 +0.009092 +0.00916 +0.008982 +0.008906 +0.008966 +0.009047 +0.009137 +0.009183 +0.009032 +0.008969 +0.009026 +0.0091 +0.009187 +0.009251 +0.009061 +0.009 +0.009058 +0.009142 +0.009251 +0.009283 +0.009125 +0.009033 +0.0091 +0.009181 +0.009267 +0.00933 +0.009168 +0.009093 +0.009151 +0.009223 +0.009324 +0.009376 +0.009224 +0.00914 +0.009198 +0.009287 +0.00936 +0.009427 +0.009258 +0.009179 +0.009241 +0.00931 +0.009413 +0.009484 +0.00933 +0.009258 +0.009281 +0.009366 +0.009454 +0.009509 +0.009355 +0.009264 +0.009326 +0.00942 +0.009503 +0.009587 +0.009392 +0.00933 +0.009382 +0.009461 +0.009566 +0.009601 +0.009439 +0.009382 +0.009443 +0.009514 +0.009613 +0.009677 +0.00949 +0.009411 +0.009475 +0.009575 +0.009665 +0.009721 +0.009518 +0.009451 +0.009516 +0.009627 +0.009707 +0.00977 +0.009579 +0.00951 +0.009566 +0.009653 +0.009739 +0.009798 +0.009656 +0.009551 +0.009627 +0.009722 +0.009811 +0.009844 +0.009686 +0.009598 +0.00966 +0.009746 +0.00985 +0.009934 +0.009745 +0.009661 +0.009714 +0.009828 +0.009888 +0.009931 +0.009773 +0.009706 +0.009763 +0.009856 +0.009934 +0.010009 +0.00985 +0.009762 +0.009819 +0.009881 +0.009986 +0.010059 +0.009878 +0.009792 +0.009869 +0.009943 +0.010048 +0.01013 +0.009966 +0.009881 +0.009906 +0.009973 +0.010082 +0.010157 +0.010001 +0.009886 +0.009951 +0.010063 +0.010167 +0.010209 +0.010047 +0.009937 +0.010004 +0.010098 +0.010192 +0.010253 +0.010084 +0.009987 +0.010078 +0.010168 +0.010257 +0.010317 +0.010131 +0.010063 +0.010114 +0.010187 +0.010284 +0.010349 +0.010203 +0.010107 +0.010167 +0.01027 +0.010348 +0.010404 +0.010236 +0.010134 +0.010211 +0.010306 +0.010407 +0.010493 +0.010301 +0.0102 +0.010266 +0.010356 +0.010441 +0.010513 +0.010345 +0.010262 +0.010359 +0.010387 +0.010518 +0.01056 +0.010381 +0.010274 +0.010309 +0.010399 +0.010448 +0.010473 +0.010246 +0.010128 +0.010184 +0.010225 +0.010268 +0.010245 +0.010016 +0.009869 +0.009895 +0.009937 +0.009961 +0.009976 +0.009749 +0.009611 +0.009632 +0.009681 +0.009715 +0.009747 +0.009467 +0.009344 +0.009366 +0.009404 +0.009444 +0.009445 +0.00923 +0.009113 +0.009123 +0.009161 +0.009198 +0.009202 +0.008994 +0.008885 +0.008907 +0.008954 +0.009005 +0.009011 +0.008811 +0.008707 +0.008727 +0.008783 +0.008817 +0.008837 +0.008652 +0.008561 +0.008611 +0.008652 +0.008692 +0.008718 +0.00851 +0.008423 +0.008462 +0.008507 +0.008562 +0.008593 +0.008421 +0.008324 +0.008379 +0.008455 +0.008504 +0.008537 +0.008379 +0.0083 +0.008344 +0.00842 +0.008496 +0.00855 +0.008412 +0.008338 +0.008388 +0.008462 +0.008556 +0.008602 +0.008463 +0.00839 +0.008443 +0.008495 +0.008574 +0.008631 +0.008485 +0.008418 +0.008466 +0.00854 +0.008635 +0.00869 +0.008535 +0.008466 +0.00851 +0.008591 +0.008673 +0.008746 +0.00858 +0.008507 +0.008577 +0.008623 +0.008716 +0.008763 +0.008611 +0.008546 +0.008599 +0.008677 +0.00876 +0.008814 +0.008675 +0.008614 +0.008648 +0.008706 +0.008793 +0.008855 +0.008706 +0.008636 +0.008685 +0.008762 +0.008854 +0.008914 +0.008759 +0.008674 +0.00873 +0.008808 +0.008882 +0.008954 +0.00881 +0.008711 +0.008779 +0.008849 +0.008925 +0.009003 +0.008865 +0.008764 +0.008815 +0.008887 +0.008989 +0.009063 +0.008892 +0.008803 +0.008858 +0.008934 +0.009032 +0.009099 +0.008939 +0.008857 +0.008921 +0.00898 +0.009062 +0.009122 +0.008974 +0.008886 +0.008942 +0.009035 +0.009123 +0.009188 +0.009037 +0.008935 +0.008984 +0.009069 +0.009164 +0.009223 +0.009059 +0.008989 +0.009045 +0.009165 +0.009244 +0.009262 +0.009103 +0.009022 +0.00908 +0.009162 +0.009242 +0.009313 +0.009154 +0.009064 +0.009137 +0.009229 +0.009314 +0.009363 +0.009208 +0.009119 +0.009164 +0.009252 +0.009351 +0.009401 +0.009252 +0.009172 +0.009214 +0.009298 +0.009395 +0.009466 +0.009321 +0.009208 +0.009259 +0.009353 +0.009455 +0.009501 +0.00934 +0.009251 +0.009324 +0.009405 +0.009488 +0.009547 +0.009389 +0.009296 +0.009361 +0.009448 +0.009545 +0.009608 +0.009443 +0.009353 +0.009393 +0.009492 +0.009592 +0.009636 +0.009483 +0.009403 +0.009467 +0.009563 +0.00962 +0.009685 +0.00952 +0.009447 +0.009504 +0.009583 +0.009691 +0.009746 +0.009587 +0.00949 +0.009551 +0.009639 +0.009724 +0.009811 +0.009616 +0.009525 +0.0096 +0.009686 +0.00978 +0.00985 +0.009691 +0.009579 +0.009642 +0.009753 +0.009842 +0.009907 +0.009705 +0.009616 +0.009692 +0.009798 +0.009887 +0.009949 +0.009759 +0.009689 +0.00974 +0.00982 +0.009935 +0.009983 +0.009819 +0.009739 +0.009813 +0.009913 +0.009985 +0.010019 +0.009856 +0.009785 +0.009838 +0.009927 +0.010032 +0.010105 +0.009994 +0.00983 +0.009888 +0.009983 +0.010057 +0.010129 +0.009962 +0.009884 +0.009943 +0.010039 +0.010126 +0.010215 +0.010017 +0.009941 +0.010008 +0.010064 +0.010171 +0.010249 +0.010074 +0.009974 +0.010051 +0.010119 +0.010228 +0.010302 +0.010125 +0.010065 +0.010083 +0.010204 +0.010268 +0.010346 +0.010169 +0.010081 +0.01015 +0.010237 +0.010336 +0.010398 +0.010222 +0.010125 +0.010195 +0.010295 +0.010407 +0.010468 +0.010249 +0.010141 +0.010206 +0.01031 +0.010376 +0.010389 +0.010175 +0.010025 +0.010065 +0.01015 +0.010193 +0.010137 +0.009911 +0.00978 +0.009797 +0.009835 +0.009861 +0.009869 +0.009641 +0.00951 +0.009536 +0.009585 +0.009568 +0.009575 +0.009364 +0.009217 +0.009236 +0.009277 +0.009306 +0.009317 +0.009114 +0.008982 +0.008998 +0.009027 +0.009064 +0.00906 +0.008888 +0.00874 +0.00876 +0.008803 +0.008868 +0.008866 +0.008683 +0.008568 +0.008598 +0.008656 +0.008692 +0.008702 +0.00853 +0.00843 +0.008463 +0.008517 +0.008576 +0.008589 +0.008426 +0.008325 +0.008371 +0.008419 +0.008477 +0.008503 +0.008322 +0.008235 +0.008273 +0.008341 +0.008398 +0.008459 +0.008279 +0.008191 +0.008249 +0.008324 +0.008391 +0.008453 +0.008307 +0.008204 +0.008274 +0.008331 +0.008417 +0.008468 +0.008334 +0.008266 +0.008309 +0.008395 +0.008479 +0.008529 +0.008381 +0.008308 +0.008352 +0.008417 +0.008504 +0.008562 +0.008418 +0.008348 +0.008392 +0.008481 +0.008567 +0.00864 +0.008483 +0.008386 +0.008441 +0.008497 +0.008582 +0.008648 +0.008498 +0.008425 +0.00851 +0.008541 +0.008634 +0.008696 +0.008555 +0.008476 +0.008527 +0.008602 +0.008666 +0.008724 +0.008589 +0.008514 +0.008556 +0.008628 +0.00871 +0.008791 +0.008635 +0.00856 +0.008618 +0.008685 +0.008795 +0.008821 +0.00868 +0.008603 +0.008652 +0.008726 +0.008829 +0.008871 +0.008712 +0.00865 +0.00869 +0.008771 +0.008855 +0.008915 +0.008755 +0.008691 +0.008744 +0.008808 +0.008907 +0.008968 +0.008807 +0.008734 +0.008788 +0.008863 +0.008935 +0.009004 +0.008846 +0.008784 +0.008845 +0.008921 +0.008988 +0.009055 +0.008889 +0.008807 +0.008873 +0.008949 +0.009028 +0.009096 +0.008935 +0.008861 +0.008917 +0.009003 +0.009097 +0.009149 +0.00899 +0.008896 +0.008954 +0.009032 +0.009129 +0.009178 +0.009019 +0.008962 +0.009009 +0.009089 +0.009173 +0.009255 +0.009104 +0.009001 +0.009045 +0.009114 +0.009212 +0.009283 +0.009125 +0.009032 +0.009097 +0.009185 +0.009271 +0.009336 +0.009171 +0.009076 +0.009132 +0.00922 +0.009314 +0.009368 +0.009205 +0.009143 +0.009185 +0.00929 +0.009378 +0.009431 +0.009251 +0.009169 +0.009232 +0.009336 +0.009424 +0.009455 +0.009296 +0.009218 +0.009285 +0.009387 +0.009466 +0.009539 +0.009357 +0.00925 +0.009314 +0.009412 +0.009496 +0.009551 +0.009398 +0.009311 +0.009381 +0.009457 +0.009555 +0.009609 +0.009438 +0.009366 +0.009406 +0.009506 +0.009608 +0.009686 +0.009527 +0.009407 +0.009463 +0.009546 +0.009639 +0.00972 +0.009523 +0.00945 +0.00952 +0.009622 +0.009702 +0.009759 +0.009578 +0.009502 +0.00956 +0.00966 +0.009746 +0.009797 +0.00965 +0.00955 +0.009609 +0.00971 +0.009807 +0.009842 +0.009687 +0.009608 +0.009699 +0.009742 +0.009851 +0.009886 +0.009726 +0.00966 +0.009705 +0.00981 +0.00991 +0.009937 +0.009774 +0.00971 +0.00975 +0.009837 +0.009942 +0.009996 +0.009832 +0.009757 +0.009833 +0.009913 +0.009984 +0.010042 +0.009874 +0.009793 +0.009862 +0.009974 +0.010049 +0.010098 +0.009925 +0.009887 +0.009916 +0.009972 +0.010083 +0.010152 +0.009972 +0.009888 +0.009955 +0.010042 +0.01015 +0.010217 +0.010047 +0.009954 +0.009997 +0.010093 +0.010191 +0.01026 +0.010086 +0.009992 +0.010058 +0.010152 +0.010295 +0.010329 +0.010133 +0.010029 +0.010095 +0.010203 +0.010317 +0.010345 +0.010181 +0.010081 +0.010165 +0.010272 +0.010358 +0.010403 +0.010205 +0.010104 +0.010157 +0.010233 +0.010276 +0.010293 +0.010091 +0.009987 +0.009999 +0.010037 +0.010102 +0.010123 +0.009857 +0.00972 +0.009735 +0.009783 +0.009822 +0.009817 +0.009609 +0.009503 +0.009463 +0.009506 +0.009544 +0.009531 +0.009322 +0.009198 +0.009215 +0.009248 +0.009284 +0.009296 +0.009081 +0.00895 +0.008973 +0.009001 +0.009033 +0.009042 +0.008858 +0.00875 +0.008793 +0.008797 +0.008836 +0.008853 +0.008675 +0.008586 +0.008584 +0.00864 +0.008684 +0.008706 +0.008531 +0.008439 +0.008471 +0.008519 +0.008574 +0.0086 +0.008433 +0.008331 +0.008368 +0.008407 +0.008461 +0.008485 +0.008322 +0.00824 +0.008277 +0.00833 +0.008394 +0.008446 +0.008296 +0.008229 +0.008254 +0.008319 +0.008392 +0.008417 +0.008292 +0.008235 +0.008274 +0.008344 +0.008424 +0.008478 +0.008339 +0.008263 +0.008324 +0.008389 +0.008472 +0.008522 +0.008389 +0.008312 +0.00836 +0.00844 +0.008526 +0.008569 +0.008425 +0.008356 +0.008398 +0.008474 +0.008562 +0.00862 +0.00848 +0.008414 +0.008437 +0.008512 +0.008615 +0.008656 +0.008534 +0.008425 +0.008478 +0.008558 +0.008639 +0.008699 +0.008547 +0.008479 +0.00853 +0.008603 +0.008698 +0.008761 +0.008594 +0.008514 +0.008567 +0.008648 +0.008725 +0.008783 +0.008647 +0.008557 +0.00861 +0.008682 +0.008782 +0.008861 +0.008712 +0.008613 +0.008655 +0.008717 +0.008812 +0.008884 +0.008718 +0.008668 +0.008701 +0.008771 +0.008858 +0.008929 +0.008785 +0.008701 +0.00875 +0.008811 +0.008901 +0.008962 +0.008811 +0.008742 +0.008785 +0.008855 +0.008951 +0.009028 +0.008865 +0.008793 +0.008849 +0.008921 +0.009018 +0.009044 +0.008886 +0.008815 +0.008873 +0.008955 +0.009038 +0.009106 +0.008957 +0.008888 +0.008942 +0.008995 +0.009081 +0.009133 +0.008987 +0.008909 +0.008969 +0.009046 +0.009121 +0.009211 +0.009048 +0.008969 +0.009023 +0.009096 +0.009171 +0.009216 +0.00909 +0.009018 +0.009077 +0.009135 +0.009238 +0.009272 +0.009119 +0.009055 +0.009098 +0.009176 +0.009272 +0.00933 +0.009169 +0.009099 +0.009147 +0.009231 +0.009324 +0.009387 +0.009218 +0.009138 +0.009199 +0.009284 +0.009357 +0.00943 +0.009273 +0.009188 +0.009237 +0.009336 +0.009433 +0.009499 +0.009302 +0.009221 +0.00928 +0.009391 +0.009453 +0.009507 +0.009358 +0.009281 +0.00933 +0.009427 +0.009502 +0.009576 +0.009408 +0.009324 +0.009388 +0.009456 +0.009555 +0.009629 +0.00945 +0.009369 +0.009433 +0.009534 +0.009594 +0.009666 +0.009506 +0.009444 +0.00949 +0.009548 +0.00964 +0.009715 +0.009567 +0.009499 +0.009526 +0.009608 +0.009686 +0.009751 +0.009591 +0.009515 +0.00957 +0.009648 +0.009765 +0.009814 +0.009661 +0.009581 +0.00962 +0.009686 +0.009793 +0.009862 +0.009689 +0.009611 +0.009683 +0.009784 +0.009864 +0.009905 +0.009743 +0.009662 +0.009719 +0.009785 +0.009886 +0.009987 +0.009774 +0.009703 +0.009766 +0.009849 +0.009956 +0.010026 +0.009849 +0.009748 +0.009814 +0.009901 +0.009991 +0.010059 +0.009898 +0.009795 +0.009858 +0.009971 +0.01009 +0.010166 +0.009926 +0.009836 +0.009895 +0.010008 +0.010087 +0.010155 +0.009987 +0.009909 +0.009978 +0.010063 +0.010161 +0.010195 +0.010028 +0.00995 +0.010007 +0.010105 +0.010205 +0.010267 +0.010114 +0.010018 +0.010075 +0.010155 +0.010245 +0.010333 +0.01014 +0.010041 +0.010123 +0.01018 +0.01032 +0.010383 +0.010193 +0.010115 +0.010163 +0.01023 +0.010331 +0.010387 +0.010196 +0.010096 +0.010108 +0.010193 +0.010245 +0.010257 +0.010058 +0.009911 +0.009926 +0.009968 +0.010015 +0.010003 +0.009816 +0.00968 +0.009733 +0.009721 +0.009746 +0.009767 +0.009549 +0.009419 +0.009414 +0.009442 +0.009486 +0.009497 +0.009311 +0.00918 +0.009193 +0.009225 +0.009247 +0.009266 +0.009068 +0.008949 +0.008985 +0.008997 +0.009038 +0.009069 +0.008899 +0.008765 +0.008782 +0.008828 +0.008878 +0.008921 +0.008719 +0.008616 +0.008642 +0.008689 +0.008749 +0.008783 +0.008612 +0.008513 +0.008534 +0.008582 +0.008625 +0.008671 +0.008502 +0.008412 +0.008436 +0.008498 +0.008542 +0.00859 +0.008435 +0.008365 +0.008392 +0.008429 +0.008503 +0.008557 +0.008409 +0.008336 +0.008395 +0.00845 +0.008566 +0.008591 +0.00845 +0.008373 +0.008438 +0.008505 +0.008562 +0.008632 +0.008502 +0.008426 +0.008464 +0.008539 +0.008615 +0.008684 +0.008537 +0.008458 +0.008512 +0.008597 +0.008662 +0.008717 +0.008584 +0.008506 +0.00855 +0.008625 +0.008711 +0.008778 +0.008617 +0.008549 +0.008594 +0.008714 +0.008767 +0.008798 +0.008645 +0.008587 +0.008638 +0.008711 +0.00879 +0.008846 +0.008715 +0.008631 +0.008698 +0.008762 +0.008851 +0.008895 +0.008739 +0.00868 +0.008726 +0.008795 +0.008888 +0.008946 +0.008795 +0.008716 +0.008774 +0.008853 +0.008936 +0.008988 +0.008842 +0.008778 +0.008831 +0.008881 +0.008964 +0.009039 +0.008879 +0.00881 +0.008844 +0.00894 +0.009035 +0.009085 +0.008933 +0.008852 +0.008921 +0.00897 +0.009055 +0.009124 +0.008967 +0.008894 +0.008937 +0.009039 +0.009137 +0.009173 +0.00903 +0.00894 +0.008978 +0.009067 +0.009157 +0.009236 +0.009069 +0.008972 +0.00902 +0.009129 +0.009236 +0.009264 +0.009107 +0.009031 +0.009074 +0.009145 +0.00924 +0.009307 +0.009147 +0.009069 +0.009118 +0.009226 +0.009311 +0.00937 +0.009213 +0.009126 +0.009155 +0.009246 +0.009336 +0.009405 +0.009238 +0.009158 +0.009224 +0.009325 +0.009388 +0.009436 +0.009298 +0.009205 +0.009266 +0.009357 +0.00943 +0.009502 +0.009328 +0.009267 +0.009325 +0.009387 +0.009483 +0.009547 +0.009388 +0.009303 +0.009345 +0.009445 +0.009537 +0.009591 +0.009442 +0.009362 +0.009398 +0.009491 +0.009591 +0.009676 +0.009484 +0.009381 +0.009436 +0.009545 +0.009621 +0.009688 +0.009537 +0.009456 +0.009514 +0.009575 +0.009678 +0.009729 +0.009568 +0.009491 +0.009541 +0.009636 +0.009747 +0.009811 +0.009625 +0.009527 +0.009592 +0.009674 +0.009771 +0.009843 +0.009663 +0.009569 +0.009658 +0.009748 +0.009864 +0.009897 +0.009713 +0.009622 +0.009685 +0.009776 +0.00987 +0.009936 +0.009789 +0.009664 +0.009751 +0.00985 +0.009938 +0.009992 +0.009807 +0.00973 +0.009778 +0.009887 +0.009966 +0.01003 +0.009869 +0.009787 +0.009854 +0.009939 +0.010044 +0.010114 +0.009908 +0.009814 +0.00988 +0.009961 +0.01008 +0.01013 +0.00996 +0.009884 +0.009931 +0.010055 +0.010119 +0.010182 +0.010011 +0.009932 +0.009995 +0.010084 +0.010177 +0.010235 +0.010066 +0.009984 +0.010032 +0.010111 +0.010219 +0.010308 +0.010161 +0.010046 +0.010092 +0.01021 +0.010254 +0.010336 +0.010154 +0.010068 +0.010137 +0.010214 +0.010333 +0.010412 +0.010231 +0.01014 +0.010204 +0.01027 +0.010373 +0.010445 +0.010257 +0.010174 +0.010247 +0.01035 +0.010451 +0.01052 +0.010355 +0.01023 +0.010272 +0.010367 +0.010465 +0.010556 +0.010315 +0.010237 +0.010301 +0.010327 +0.010381 +0.010425 +0.010158 +0.010031 +0.010063 +0.010093 +0.01014 +0.010151 +0.009945 +0.009797 +0.009816 +0.009831 +0.009864 +0.009847 +0.009633 +0.009546 +0.009529 +0.00954 +0.009563 +0.009588 +0.009392 +0.009256 +0.009298 +0.009303 +0.009308 +0.009309 +0.009125 +0.009013 +0.009034 +0.00906 +0.009097 +0.009095 +0.008917 +0.008807 +0.00883 +0.008865 +0.0089 +0.008935 +0.008747 +0.008648 +0.008674 +0.00872 +0.008787 +0.008794 +0.008606 +0.008501 +0.008533 +0.008572 +0.008633 +0.00864 +0.008487 +0.008395 +0.00843 +0.008505 +0.008527 +0.008556 +0.008387 +0.008312 +0.008352 +0.008414 +0.008476 +0.008514 +0.00837 +0.008315 +0.008356 +0.008424 +0.008517 +0.008571 +0.008415 +0.008338 +0.008389 +0.008473 +0.00856 +0.008597 +0.008469 +0.008365 +0.008431 +0.008519 +0.008602 +0.008653 +0.0085 +0.008443 +0.008468 +0.008545 +0.008633 +0.008687 +0.008545 +0.008466 +0.008529 +0.008589 +0.00869 +0.008723 +0.008587 +0.008509 +0.008558 +0.008641 +0.008716 +0.008771 +0.008627 +0.008559 +0.008613 +0.00871 +0.008769 +0.008811 +0.008664 +0.008601 +0.00866 +0.008713 +0.008794 +0.008861 +0.008719 +0.008642 +0.008705 +0.008773 +0.008843 +0.008905 +0.008759 +0.008686 +0.008732 +0.008805 +0.008884 +0.008963 +0.008802 +0.008732 +0.0088 +0.008867 +0.008934 +0.008992 +0.008847 +0.008781 +0.008835 +0.008892 +0.008967 +0.009032 +0.008896 +0.008831 +0.008897 +0.008943 +0.009028 +0.009075 +0.008932 +0.008852 +0.008911 +0.008986 +0.009064 +0.009136 +0.008986 +0.008911 +0.008958 +0.009046 +0.009115 +0.009166 +0.009027 +0.008941 +0.009 +0.009076 +0.009171 +0.009251 +0.00909 +0.008982 +0.009024 +0.009119 +0.009209 +0.009262 +0.009118 +0.009046 +0.009117 +0.009174 +0.009268 +0.009308 +0.009149 +0.009075 +0.009134 +0.009207 +0.009296 +0.009374 +0.00921 +0.009131 +0.00919 +0.009279 +0.00935 +0.009392 +0.009258 +0.009167 +0.009219 +0.009314 +0.009407 +0.009484 +0.0093 +0.009214 +0.009274 +0.009361 +0.009457 +0.009508 +0.009331 +0.009281 +0.009312 +0.009399 +0.009489 +0.009561 +0.009382 +0.00931 +0.009376 +0.009449 +0.009548 +0.009606 +0.009424 +0.009349 +0.009408 +0.009508 +0.009589 +0.009646 +0.009507 +0.009418 +0.009505 +0.009535 +0.009639 +0.009669 +0.009518 +0.009447 +0.009506 +0.009592 +0.009689 +0.009745 +0.00958 +0.009509 +0.00956 +0.009644 +0.009751 +0.00978 +0.009616 +0.009549 +0.009598 +0.009688 +0.009778 +0.009843 +0.009681 +0.009606 +0.009684 +0.009766 +0.009842 +0.009873 +0.009727 +0.009619 +0.009693 +0.009785 +0.009869 +0.009938 +0.009789 +0.0097 +0.009761 +0.009857 +0.009911 +0.009982 +0.009823 +0.009727 +0.00979 +0.009884 +0.009975 +0.010063 +0.009893 +0.009796 +0.00985 +0.009922 +0.01004 +0.010124 +0.009907 +0.009822 +0.009882 +0.009994 +0.010113 +0.010146 +0.009971 +0.009898 +0.00993 +0.010022 +0.010132 +0.010182 +0.010023 +0.009934 +0.00999 +0.010108 +0.010201 +0.010257 +0.010068 +0.009977 +0.010035 +0.010126 +0.010247 +0.010316 +0.010142 +0.010026 +0.010092 +0.01018 +0.010306 +0.010355 +0.010185 +0.010071 +0.010134 +0.010229 +0.010336 +0.010401 +0.010233 +0.010148 +0.010212 +0.010302 +0.010357 +0.010425 +0.010237 +0.010135 +0.010181 +0.010219 +0.010277 +0.01033 +0.010112 +0.01001 +0.009986 +0.009991 +0.010052 +0.010045 +0.00983 +0.009701 +0.009704 +0.009778 +0.009776 +0.00979 +0.00958 +0.009447 +0.009428 +0.00947 +0.009495 +0.00951 +0.009305 +0.009187 +0.009187 +0.009223 +0.009267 +0.00927 +0.009076 +0.008975 +0.008976 +0.009036 +0.009084 +0.009054 +0.008874 +0.008781 +0.008784 +0.008827 +0.008865 +0.008893 +0.008722 +0.008612 +0.008641 +0.00868 +0.008733 +0.008754 +0.008587 +0.008508 +0.008526 +0.008555 +0.008602 +0.008644 +0.008469 +0.00838 +0.008407 +0.008454 +0.008504 +0.008553 +0.008412 +0.008311 +0.008376 +0.008397 +0.008453 +0.00851 +0.008364 +0.008288 +0.008345 +0.008401 +0.008481 +0.008553 +0.008404 +0.008343 +0.008386 +0.00847 +0.00853 +0.008581 +0.008439 +0.008371 +0.008423 +0.008493 +0.008578 +0.008634 +0.008504 +0.008423 +0.008477 +0.008545 +0.008624 +0.008671 +0.00853 +0.008462 +0.008527 +0.008572 +0.008655 +0.008727 +0.008557 +0.008531 +0.008552 +0.008611 +0.008694 +0.008759 +0.008606 +0.008541 +0.008596 +0.008663 +0.008744 +0.008819 +0.008677 +0.008592 +0.008641 +0.008717 +0.008784 +0.008841 +0.0087 +0.008631 +0.008681 +0.008751 +0.008835 +0.008898 +0.008778 +0.008695 +0.008723 +0.0088 +0.008861 +0.008929 +0.008788 +0.008715 +0.008786 +0.008834 +0.008918 +0.008977 +0.008831 +0.008764 +0.008806 +0.00888 +0.008968 +0.00903 +0.008883 +0.008798 +0.008862 +0.008935 +0.009028 +0.009075 +0.008922 +0.008845 +0.008895 +0.008983 +0.00908 +0.009137 +0.008961 +0.008879 +0.008935 +0.00902 +0.009129 +0.00916 +0.009005 +0.008942 +0.009002 +0.009061 +0.009153 +0.009209 +0.009055 +0.008982 +0.00905 +0.009121 +0.009211 +0.009247 +0.009107 +0.009021 +0.009077 +0.009166 +0.009241 +0.009303 +0.009161 +0.009077 +0.009165 +0.009218 +0.009309 +0.009338 +0.00918 +0.009113 +0.009164 +0.009246 +0.009338 +0.009405 +0.009231 +0.009159 +0.009239 +0.009283 +0.009388 +0.009449 +0.009287 +0.00921 +0.009267 +0.009351 +0.009449 +0.009498 +0.009334 +0.009247 +0.009307 +0.009402 +0.009489 +0.009571 +0.009384 +0.009299 +0.009345 +0.009457 +0.009544 +0.009581 +0.009415 +0.009342 +0.009398 +0.009484 +0.009577 +0.009644 +0.009479 +0.009406 +0.00947 +0.009541 +0.009628 +0.009687 +0.009516 +0.009436 +0.009498 +0.009581 +0.009664 +0.009751 +0.009575 +0.009534 +0.009567 +0.009637 +0.009721 +0.009767 +0.009618 +0.009551 +0.009582 +0.009674 +0.009771 +0.00983 +0.00966 +0.009587 +0.009641 +0.009728 +0.009828 +0.009888 +0.009716 +0.00964 +0.009696 +0.009781 +0.00987 +0.00993 +0.009762 +0.009692 +0.009745 +0.009858 +0.009919 +0.009973 +0.009799 +0.009735 +0.009813 +0.009861 +0.009972 +0.010064 +0.009845 +0.009772 +0.009832 +0.009929 +0.010021 +0.01011 +0.009913 +0.009835 +0.009887 +0.00997 +0.010068 +0.010137 +0.00996 +0.009864 +0.009944 +0.010029 +0.010188 +0.010218 +0.010003 +0.009907 +0.009968 +0.010072 +0.010159 +0.010233 +0.010065 +0.009959 +0.010037 +0.010134 +0.010222 +0.010292 +0.010115 +0.010032 +0.010082 +0.010183 +0.010284 +0.010357 +0.010159 +0.010069 +0.010133 +0.010239 +0.010344 +0.010418 +0.0102 +0.010132 +0.010191 +0.010286 +0.010378 +0.010424 +0.010246 +0.010148 +0.010207 +0.010289 +0.010357 +0.010371 +0.010161 +0.01002 +0.010047 +0.01011 +0.010112 +0.010128 +0.009893 +0.00975 +0.009772 +0.009812 +0.009839 +0.00985 +0.009624 +0.009519 +0.009513 +0.009533 +0.009584 +0.009593 +0.009366 +0.009227 +0.009253 +0.009305 +0.009326 +0.009334 +0.009127 +0.00901 +0.009027 +0.009062 +0.009113 +0.009121 +0.008921 +0.008797 +0.00884 +0.008891 +0.008913 +0.008942 +0.00875 +0.008644 +0.00867 +0.008727 +0.00878 +0.008815 +0.008605 +0.008503 +0.008555 +0.008606 +0.008657 +0.008665 +0.008489 +0.008391 +0.008437 +0.00848 +0.008538 +0.008565 +0.00839 +0.008312 +0.00835 +0.008424 +0.008463 +0.0085 +0.008326 +0.008257 +0.008297 +0.008375 +0.008449 +0.008502 +0.008361 +0.008284 +0.008362 +0.008405 +0.008515 +0.008552 +0.008397 +0.008309 +0.008365 +0.008439 +0.008528 +0.008578 +0.008438 +0.008385 +0.008412 +0.008496 +0.008579 +0.008639 +0.008485 +0.008412 +0.008453 +0.008531 +0.008603 +0.008674 +0.008521 +0.008457 +0.008515 +0.008568 +0.008675 +0.008722 +0.00857 +0.008501 +0.008552 +0.008626 +0.008728 +0.008738 +0.008593 +0.008529 +0.008587 +0.008662 +0.008729 +0.008791 +0.008654 +0.008585 +0.008642 +0.008691 +0.008792 +0.008843 +0.008697 +0.008622 +0.008679 +0.008756 +0.008823 +0.008896 +0.00874 +0.008667 +0.00871 +0.0088 +0.008877 +0.008934 +0.00879 +0.008716 +0.008802 +0.00885 +0.008919 +0.008981 +0.008812 +0.008742 +0.008797 +0.008868 +0.008956 +0.009025 +0.008866 +0.008795 +0.00886 +0.00894 +0.009021 +0.00906 +0.008911 +0.008836 +0.008884 +0.008968 +0.009052 +0.009103 +0.00896 +0.008893 +0.008949 +0.009025 +0.009111 +0.009173 +0.009021 +0.008926 +0.008968 +0.009044 +0.009136 +0.009223 +0.009045 +0.00897 +0.009032 +0.009119 +0.009191 +0.009252 +0.009093 +0.00901 +0.009064 +0.009145 +0.009238 +0.009287 +0.009143 +0.009083 +0.009124 +0.009211 +0.009302 +0.009356 +0.009181 +0.009098 +0.00916 +0.009246 +0.00935 +0.009388 +0.009212 +0.009142 +0.009207 +0.009297 +0.009403 +0.009425 +0.009285 +0.009198 +0.009244 +0.009335 +0.009427 +0.009483 +0.009324 +0.009249 +0.009307 +0.009383 +0.009483 +0.009525 +0.009372 +0.009301 +0.009358 +0.009445 +0.009525 +0.009577 +0.009445 +0.009341 +0.009392 +0.009465 +0.00956 +0.009635 +0.009453 +0.009376 +0.009456 +0.009567 +0.009609 +0.009669 +0.009514 +0.009434 +0.009484 +0.00958 +0.009651 +0.009731 +0.009581 +0.009493 +0.009545 +0.009637 +0.009711 +0.009777 +0.009599 +0.009526 +0.009606 +0.009681 +0.009759 +0.009824 +0.009665 +0.009586 +0.009638 +0.009703 +0.009809 +0.009873 +0.009702 +0.009641 +0.009676 +0.009751 +0.00986 +0.009935 +0.009762 +0.009666 +0.009727 +0.009812 +0.009924 +0.009984 +0.009826 +0.009729 +0.009765 +0.009865 +0.010001 +0.010013 +0.009841 +0.00978 +0.009818 +0.009915 +0.009998 +0.010076 +0.009928 +0.009801 +0.009872 +0.009968 +0.010063 +0.010126 +0.00995 +0.009867 +0.009923 +0.010044 +0.010133 +0.010187 +0.009985 +0.009917 +0.009969 +0.010058 +0.010166 +0.010258 +0.010048 +0.009949 +0.010034 +0.010119 +0.010261 +0.010243 +0.010087 +0.010012 +0.010065 +0.010159 +0.010263 +0.010342 +0.010163 +0.010085 +0.010126 +0.010208 +0.010316 +0.010377 +0.010192 +0.010115 +0.010185 +0.010243 +0.010373 +0.010429 +0.010244 +0.010092 +0.010102 +0.010152 +0.010206 +0.010227 +0.010023 +0.009916 +0.009891 +0.009936 +0.009983 +0.009978 +0.009776 +0.009627 +0.009634 +0.009673 +0.009712 +0.009739 +0.009519 +0.009383 +0.00937 +0.009404 +0.00944 +0.009454 +0.009242 +0.009128 +0.00914 +0.009207 +0.009209 +0.009215 +0.009019 +0.008896 +0.008918 +0.00894 +0.008987 +0.009034 +0.008831 +0.008708 +0.008741 +0.008798 +0.008829 +0.008861 +0.008687 +0.008587 +0.00859 +0.008646 +0.008695 +0.008726 +0.008559 +0.008467 +0.008487 +0.008522 +0.008575 +0.008615 +0.008453 +0.008369 +0.008412 +0.00842 +0.008471 +0.008516 +0.008369 +0.008284 +0.00832 +0.008375 +0.008444 +0.008509 +0.008359 +0.008293 +0.008332 +0.008396 +0.008482 +0.008538 +0.008403 +0.008327 +0.008388 +0.008455 +0.00852 +0.008582 +0.00843 +0.008363 +0.008416 +0.008484 +0.008562 +0.008621 +0.008485 +0.008433 +0.008465 +0.008543 +0.008638 +0.008646 +0.008511 +0.008446 +0.008515 +0.008564 +0.00865 +0.008697 +0.008558 +0.008499 +0.00856 +0.008629 +0.008699 +0.008752 +0.008598 +0.008531 +0.008579 +0.008656 +0.008739 +0.008795 +0.008651 +0.008583 +0.008636 +0.008708 +0.008793 +0.008854 +0.00871 +0.008615 +0.008666 +0.008731 +0.008818 +0.008896 +0.008739 +0.008656 +0.008702 +0.008791 +0.008866 +0.008935 +0.008784 +0.00871 +0.008755 +0.00883 +0.008928 +0.008986 +0.008839 +0.008743 +0.0088 +0.00888 +0.008958 +0.009034 +0.008865 +0.008787 +0.008867 +0.008919 +0.009031 +0.009079 +0.008917 +0.008833 +0.008879 +0.008959 +0.009055 +0.009103 +0.008975 +0.008877 +0.008933 +0.009007 +0.009108 +0.009172 +0.009008 +0.008922 +0.00898 +0.009053 +0.009145 +0.009199 +0.009053 +0.008967 +0.009024 +0.009109 +0.009196 +0.009261 +0.009104 +0.009025 +0.009091 +0.009143 +0.009229 +0.009286 +0.009133 +0.009059 +0.009113 +0.0092 +0.009296 +0.009364 +0.009187 +0.009101 +0.009165 +0.009241 +0.009322 +0.009397 +0.009225 +0.00914 +0.00921 +0.009312 +0.009391 +0.009441 +0.009284 +0.009189 +0.009247 +0.009334 +0.009423 +0.009518 +0.009328 +0.009246 +0.009282 +0.009367 +0.009474 +0.009528 +0.009365 +0.009291 +0.009346 +0.00943 +0.009537 +0.00959 +0.009421 +0.009322 +0.009402 +0.009476 +0.009558 +0.009634 +0.009462 +0.00938 +0.009448 +0.009525 +0.009634 +0.00969 +0.00951 +0.009445 +0.0095 +0.009563 +0.009651 +0.009738 +0.009554 +0.009471 +0.009529 +0.00963 +0.009723 +0.009767 +0.009603 +0.009531 +0.009576 +0.009666 +0.009773 +0.009822 +0.009651 +0.009588 +0.00965 +0.009729 +0.009832 +0.009857 +0.009699 +0.009614 +0.009677 +0.009785 +0.009868 +0.009912 +0.009749 +0.009675 +0.00974 +0.009847 +0.009883 +0.00996 +0.009794 +0.00971 +0.009776 +0.009861 +0.009953 +0.010031 +0.009861 +0.009765 +0.009816 +0.009906 +0.010006 +0.010079 +0.009913 +0.009833 +0.009879 +0.009956 +0.010067 +0.010154 +0.009934 +0.009846 +0.009907 +0.010008 +0.01011 +0.010171 +0.010034 +0.009918 +0.009958 +0.010054 +0.010163 +0.010224 +0.010045 +0.00996 +0.010028 +0.010119 +0.010238 +0.010273 +0.010096 +0.010013 +0.010058 +0.010157 +0.010271 +0.010344 +0.010187 +0.010041 +0.010121 +0.010218 +0.010329 +0.010358 +0.010186 +0.010115 +0.010166 +0.01026 +0.010358 +0.010415 +0.01024 +0.010144 +0.010188 +0.010234 +0.010269 +0.01029 +0.010084 +0.009941 +0.009966 +0.01001 +0.010065 +0.010076 +0.009844 +0.009746 +0.009739 +0.009762 +0.009745 +0.009757 +0.009558 +0.009429 +0.00946 +0.009491 +0.009506 +0.009513 +0.009317 +0.009196 +0.009209 +0.009239 +0.009275 +0.009286 +0.009084 +0.008984 +0.009 +0.009043 +0.009056 +0.009069 +0.00888 +0.008777 +0.008809 +0.008845 +0.008906 +0.008889 +0.008719 +0.008626 +0.008681 +0.008706 +0.008735 +0.008748 +0.008583 +0.008504 +0.008525 +0.008575 +0.008626 +0.00865 +0.008489 +0.008397 +0.008438 +0.008484 +0.008535 +0.008553 +0.008403 +0.008313 +0.008364 +0.008412 +0.008469 +0.008518 +0.008367 +0.0083 +0.008347 +0.008441 +0.008502 +0.008543 +0.008401 +0.00832 +0.008368 +0.008447 +0.008551 +0.008576 +0.008429 +0.008356 +0.008408 +0.008498 +0.008592 +0.008649 +0.008491 +0.00842 +0.008474 +0.00852 +0.008609 +0.008671 +0.008523 +0.008452 +0.008503 +0.008582 +0.008664 +0.008736 +0.008579 +0.008518 +0.008566 +0.008637 +0.008684 +0.008743 +0.008602 +0.008533 +0.008593 +0.008657 +0.008737 +0.008807 +0.008674 +0.008594 +0.008643 +0.008717 +0.008787 +0.008839 +0.00869 +0.008627 +0.008674 +0.008748 +0.008842 +0.00889 +0.008751 +0.008682 +0.008733 +0.0088 +0.008879 +0.00893 +0.008787 +0.008736 +0.008775 +0.008834 +0.008911 +0.008974 +0.008833 +0.008765 +0.008814 +0.008892 +0.008973 +0.00902 +0.008874 +0.008795 +0.008855 +0.008936 +0.009012 +0.009078 +0.008909 +0.008853 +0.008907 +0.008987 +0.009092 +0.009129 +0.008955 +0.008872 +0.008932 +0.009014 +0.009101 +0.009188 +0.009001 +0.008936 +0.009003 +0.009069 +0.00916 +0.009214 +0.00904 +0.008969 +0.009023 +0.009098 +0.009193 +0.009259 +0.009114 +0.009024 +0.009086 +0.009168 +0.009253 +0.009307 +0.00914 +0.009054 +0.009112 +0.009193 +0.009291 +0.009346 +0.009184 +0.009131 +0.0092 +0.009278 +0.009341 +0.009389 +0.009217 +0.009147 +0.009226 +0.009282 +0.009377 +0.009439 +0.009277 +0.009219 +0.009277 +0.009361 +0.00944 +0.009488 +0.009321 +0.009236 +0.009299 +0.009392 +0.009481 +0.009544 +0.009378 +0.009308 +0.009361 +0.009448 +0.009539 +0.009606 +0.009416 +0.009332 +0.009384 +0.009478 +0.009575 +0.009626 +0.009483 +0.0094 +0.009462 +0.009532 +0.009629 +0.009675 +0.009509 +0.00943 +0.009499 +0.00957 +0.009698 +0.009729 +0.009578 +0.009497 +0.009544 +0.00964 +0.009714 +0.009768 +0.009613 +0.009553 +0.009604 +0.009663 +0.009747 +0.009837 +0.00968 +0.009586 +0.009644 +0.009732 +0.00982 +0.009871 +0.009691 +0.009631 +0.009686 +0.009761 +0.009886 +0.009943 +0.009769 +0.009695 +0.009741 +0.009814 +0.009915 +0.009969 +0.009802 +0.00972 +0.009784 +0.009911 +0.00999 +0.010051 +0.009853 +0.009773 +0.009824 +0.009904 +0.010009 +0.010089 +0.009896 +0.009812 +0.009898 +0.009988 +0.010077 +0.010143 +0.009955 +0.009858 +0.009926 +0.010013 +0.010126 +0.010188 +0.009999 +0.009927 +0.009996 +0.010089 +0.010222 +0.010228 +0.010034 +0.009959 +0.010045 +0.01011 +0.010207 +0.010282 +0.010108 +0.010018 +0.010068 +0.01017 +0.010258 +0.010343 +0.010169 +0.010066 +0.010131 +0.010244 +0.010317 +0.010395 +0.010204 +0.010099 +0.01016 +0.010247 +0.010339 +0.010383 +0.01012 +0.009997 +0.010046 +0.010087 +0.010142 +0.01015 +0.009915 +0.009792 +0.009814 +0.009851 +0.009893 +0.009894 +0.00968 +0.009567 +0.009601 +0.009611 +0.009641 +0.009648 +0.009431 +0.009296 +0.009327 +0.009353 +0.009395 +0.009416 +0.009202 +0.009111 +0.009113 +0.009143 +0.009164 +0.009179 +0.008986 +0.008866 +0.008896 +0.008959 +0.008977 +0.009017 +0.008807 +0.008697 +0.008723 +0.00878 +0.008812 +0.008845 +0.008648 +0.008552 +0.008586 +0.008642 +0.008687 +0.008713 +0.008555 +0.008444 +0.008479 +0.008535 +0.008591 +0.008618 +0.008454 +0.008338 +0.008378 +0.00844 +0.00851 +0.008562 +0.008393 +0.008313 +0.008361 +0.008445 +0.008534 +0.008569 +0.008426 +0.008358 +0.008404 +0.00847 +0.00855 +0.008618 +0.008475 +0.008397 +0.008455 +0.008521 +0.008599 +0.008659 +0.008517 +0.008432 +0.008487 +0.008566 +0.00865 +0.008745 +0.008565 +0.008487 +0.008527 +0.008591 +0.008681 +0.008745 +0.008609 +0.008513 +0.008574 +0.008645 +0.008723 +0.008783 +0.008665 +0.008567 +0.008603 +0.008687 +0.00877 +0.008832 +0.008676 +0.008612 +0.008656 +0.008729 +0.008834 +0.008887 +0.008729 +0.008658 +0.008701 +0.008775 +0.008883 +0.008906 +0.008762 +0.008681 +0.008736 +0.008826 +0.008907 +0.008976 +0.008821 +0.008753 +0.008794 +0.008848 +0.008942 +0.009 +0.008852 +0.008792 +0.008832 +0.008896 +0.008991 +0.00906 +0.008907 +0.008827 +0.008874 +0.008947 +0.009036 +0.009096 +0.008948 +0.008878 +0.008959 +0.008992 +0.009076 +0.00913 +0.008987 +0.008918 +0.008971 +0.009046 +0.009122 +0.009191 +0.009025 +0.008957 +0.009025 +0.009084 +0.009171 +0.009231 +0.009085 +0.009002 +0.009058 +0.009134 +0.009228 +0.009273 +0.00914 +0.00906 +0.009105 +0.009187 +0.009294 +0.009339 +0.009181 +0.009081 +0.009135 +0.009215 +0.009323 +0.009363 +0.009212 +0.009126 +0.009217 +0.009266 +0.009352 +0.009426 +0.009257 +0.009187 +0.009238 +0.009316 +0.009411 +0.00946 +0.009315 +0.009232 +0.009279 +0.009371 +0.009456 +0.009518 +0.009364 +0.009283 +0.00937 +0.009445 +0.009511 +0.009534 +0.009379 +0.009319 +0.009371 +0.009455 +0.009554 +0.009611 +0.009452 +0.009372 +0.009442 +0.009499 +0.009604 +0.009653 +0.009486 +0.009415 +0.009475 +0.009564 +0.009651 +0.009707 +0.009549 +0.00947 +0.009526 +0.009624 +0.009703 +0.009776 +0.009572 +0.009501 +0.009571 +0.009637 +0.009739 +0.009817 +0.009645 +0.009574 +0.009617 +0.009697 +0.009796 +0.009874 +0.009674 +0.0096 +0.009649 +0.00973 +0.009859 +0.009926 +0.009747 +0.00966 +0.009723 +0.009777 +0.009886 +0.009956 +0.009807 +0.009711 +0.009753 +0.009832 +0.009935 +0.010013 +0.009841 +0.009741 +0.009802 +0.009888 +0.009992 +0.010058 +0.009886 +0.009802 +0.009849 +0.009969 +0.010036 +0.010105 +0.009941 +0.009841 +0.009903 +0.010005 +0.010105 +0.010177 +0.009987 +0.00991 +0.009977 +0.010033 +0.010139 +0.010191 +0.01003 +0.009941 +0.010005 +0.01013 +0.010215 +0.010236 +0.010076 +0.009991 +0.010052 +0.010145 +0.010247 +0.010306 +0.010148 +0.010069 +0.010124 +0.010204 +0.010286 +0.010348 +0.01018 +0.0101 +0.010209 +0.010228 +0.01031 +0.010383 +0.010177 +0.01008 +0.010083 +0.010124 +0.010177 +0.010218 +0.009992 +0.009865 +0.009858 +0.009914 +0.009974 +0.009969 +0.009754 +0.009597 +0.009604 +0.009649 +0.009671 +0.009691 +0.009484 +0.009342 +0.009347 +0.009391 +0.009437 +0.009479 +0.009245 +0.009105 +0.009116 +0.00915 +0.009198 +0.009216 +0.009029 +0.008894 +0.008914 +0.008944 +0.009016 +0.009014 +0.008825 +0.008715 +0.008734 +0.008783 +0.008824 +0.008856 +0.008698 +0.008585 +0.008596 +0.008636 +0.008703 +0.008723 +0.008556 +0.008459 +0.008485 +0.008549 +0.008609 +0.008628 +0.008472 +0.008371 +0.008382 +0.008441 +0.008496 +0.008544 +0.008392 +0.008317 +0.008347 +0.008407 +0.008486 +0.008556 +0.00842 +0.008341 +0.008393 +0.008453 +0.008517 +0.008591 +0.008443 +0.008366 +0.008422 +0.008494 +0.008578 +0.008625 +0.008488 +0.008419 +0.008472 +0.008563 +0.008613 +0.008664 +0.008523 +0.008455 +0.008527 +0.008575 +0.008666 +0.00871 +0.008572 +0.008505 +0.008551 +0.008619 +0.008702 +0.008751 +0.008611 +0.008545 +0.008591 +0.008659 +0.008756 +0.008826 +0.008655 +0.008578 +0.008631 +0.008706 +0.008787 +0.00886 +0.008698 +0.008632 +0.008696 +0.008759 +0.008841 +0.008893 +0.008744 +0.008666 +0.008718 +0.008809 +0.008876 +0.008938 +0.008779 +0.008706 +0.008765 +0.008843 +0.008935 +0.008992 +0.008847 +0.008748 +0.008803 +0.008884 +0.008961 +0.009025 +0.00887 +0.008809 +0.008866 +0.008937 +0.009011 +0.009082 +0.008951 +0.008846 +0.008888 +0.008966 +0.009042 +0.009119 +0.008964 +0.008882 +0.00895 +0.009043 +0.009114 +0.00916 +0.00901 +0.008933 +0.008978 +0.009057 +0.009143 +0.00921 +0.009058 +0.008988 +0.009037 +0.009124 +0.009192 +0.009266 +0.009099 +0.009022 +0.009085 +0.009163 +0.009265 +0.00931 +0.009136 +0.009053 +0.00912 +0.009224 +0.009279 +0.009349 +0.009197 +0.009107 +0.009165 +0.009247 +0.009335 +0.009396 +0.009241 +0.009178 +0.009222 +0.009303 +0.009396 +0.009439 +0.009278 +0.00921 +0.009261 +0.009329 +0.009426 +0.009497 +0.009351 +0.009291 +0.009314 +0.009392 +0.009494 +0.009533 +0.009375 +0.009288 +0.009341 +0.009434 +0.009515 +0.009578 +0.009444 +0.009353 +0.00942 +0.009499 +0.009592 +0.00962 +0.009475 +0.009394 +0.009436 +0.009521 +0.009634 +0.009679 +0.009517 +0.009434 +0.009503 +0.009622 +0.009671 +0.009733 +0.009568 +0.009482 +0.009546 +0.009654 +0.009708 +0.00978 +0.009609 +0.009531 +0.009592 +0.009683 +0.009767 +0.009837 +0.009678 +0.009578 +0.009652 +0.009735 +0.009809 +0.009883 +0.009716 +0.00963 +0.009693 +0.009778 +0.009877 +0.009972 +0.009762 +0.009669 +0.009751 +0.00981 +0.009923 +0.009985 +0.009809 +0.009745 +0.009769 +0.009863 +0.009979 +0.010028 +0.009862 +0.009803 +0.009831 +0.009916 +0.01003 +0.010077 +0.009905 +0.00982 +0.009883 +0.009978 +0.01008 +0.010159 +0.010006 +0.00988 +0.009909 +0.010007 +0.010109 +0.010186 +0.009996 +0.009912 +0.009993 +0.010093 +0.01019 +0.01025 +0.010041 +0.009962 +0.010033 +0.010114 +0.010223 +0.010297 +0.010106 +0.010015 +0.010087 +0.010172 +0.010275 +0.010343 +0.010167 +0.010109 +0.01013 +0.010232 +0.010353 +0.010385 +0.010171 +0.010085 +0.010143 +0.010226 +0.01028 +0.010297 +0.010093 +0.009941 +0.009986 +0.010062 +0.010085 +0.010091 +0.00986 +0.009722 +0.00975 +0.009792 +0.009813 +0.00983 +0.009621 +0.009485 +0.0095 +0.009543 +0.009574 +0.009568 +0.009337 +0.009225 +0.009227 +0.009269 +0.009304 +0.009315 +0.009115 +0.008999 +0.009008 +0.009044 +0.009076 +0.009085 +0.008889 +0.008769 +0.008815 +0.008834 +0.008886 +0.008915 +0.008734 +0.00863 +0.00865 +0.008711 +0.008734 +0.008745 +0.008571 +0.008474 +0.008523 +0.00857 +0.008601 +0.00861 +0.00845 +0.008362 +0.008396 +0.00844 +0.008498 +0.008522 +0.008365 +0.008276 +0.00832 +0.008389 +0.008457 +0.008484 +0.008327 +0.008268 +0.008309 +0.008383 +0.008458 +0.008519 +0.008371 +0.008296 +0.008342 +0.008416 +0.008512 +0.008584 +0.008413 +0.008336 +0.008407 +0.008464 +0.008539 +0.008584 +0.008449 +0.008373 +0.008434 +0.008519 +0.008585 +0.00865 +0.008501 +0.008419 +0.008469 +0.008552 +0.008637 +0.008688 +0.008538 +0.008469 +0.008514 +0.008576 +0.008675 +0.008736 +0.008587 +0.008519 +0.008574 +0.008638 +0.0087 +0.008773 +0.00863 +0.008573 +0.008608 +0.008673 +0.008741 +0.008811 +0.008666 +0.008608 +0.008655 +0.008727 +0.008814 +0.008863 +0.008701 +0.008633 +0.00869 +0.008756 +0.008838 +0.008906 +0.00876 +0.008684 +0.008741 +0.008831 +0.008894 +0.008939 +0.008797 +0.008726 +0.008773 +0.008841 +0.008939 +0.008995 +0.008864 +0.008782 +0.008821 +0.0089 +0.008986 +0.009035 +0.008875 +0.008805 +0.008856 +0.008936 +0.009017 +0.009084 +0.008933 +0.008852 +0.00892 +0.008999 +0.009089 +0.009121 +0.008971 +0.008907 +0.008949 +0.009026 +0.009111 +0.009176 +0.009016 +0.008945 +0.009015 +0.0091 +0.009203 +0.009217 +0.009047 +0.00899 +0.009026 +0.009123 +0.009192 +0.009266 +0.009113 +0.009025 +0.009097 +0.009175 +0.009268 +0.009321 +0.009147 +0.009087 +0.009122 +0.009211 +0.009309 +0.009364 +0.009195 +0.009118 +0.009193 +0.00928 +0.009351 +0.009418 +0.009249 +0.009186 +0.009216 +0.009302 +0.009393 +0.009447 +0.0093 +0.009203 +0.009277 +0.009365 +0.009447 +0.009507 +0.009326 +0.00926 +0.009317 +0.009397 +0.009506 +0.009534 +0.009392 +0.009313 +0.009362 +0.009443 +0.009533 +0.009605 +0.00943 +0.009368 +0.009432 +0.009515 +0.009601 +0.009645 +0.00947 +0.009394 +0.009454 +0.009546 +0.009633 +0.00972 +0.009518 +0.009441 +0.009498 +0.009595 +0.009677 +0.009752 +0.009588 +0.00949 +0.009562 +0.009636 +0.009732 +0.009788 +0.009624 +0.009551 +0.009593 +0.009686 +0.009801 +0.009866 +0.009696 +0.009583 +0.009636 +0.009734 +0.009825 +0.009891 +0.009723 +0.009639 +0.0097 +0.009799 +0.009873 +0.00994 +0.009766 +0.009688 +0.009767 +0.009823 +0.009925 +0.010004 +0.009832 +0.00973 +0.009801 +0.00988 +0.009974 +0.010044 +0.009876 +0.009825 +0.00984 +0.009949 +0.010009 +0.010087 +0.009937 +0.00984 +0.009883 +0.009983 +0.010071 +0.010138 +0.009972 +0.009885 +0.009939 +0.010047 +0.010134 +0.010208 +0.01004 +0.009931 +0.00998 +0.010091 +0.010171 +0.010241 +0.010062 +0.009987 +0.010082 +0.010122 +0.010243 +0.010305 +0.01013 +0.01002 +0.010081 +0.010175 +0.010285 +0.010336 +0.010171 +0.010086 +0.010148 +0.010254 +0.010375 +0.010396 +0.010202 +0.010112 +0.010176 +0.010254 +0.010345 +0.010365 +0.010149 +0.010025 +0.010068 +0.010143 +0.010174 +0.010133 +0.00992 +0.009774 +0.00981 +0.009859 +0.009881 +0.009909 +0.009689 +0.009563 +0.009579 +0.009619 +0.009647 +0.00967 +0.009449 +0.009318 +0.009323 +0.009375 +0.009399 +0.00942 +0.009212 +0.009075 +0.00909 +0.009134 +0.009174 +0.009213 +0.008999 +0.008861 +0.00888 +0.008938 +0.008965 +0.008995 +0.008794 +0.008691 +0.008732 +0.008751 +0.0088 +0.008831 +0.008655 +0.008538 +0.008572 +0.008635 +0.008681 +0.008688 +0.008521 +0.008428 +0.008451 +0.008503 +0.008561 +0.008592 +0.008423 +0.008327 +0.008376 +0.008433 +0.008519 +0.008531 +0.008359 +0.008269 +0.008322 +0.00839 +0.00848 +0.008522 +0.008377 +0.008308 +0.008367 +0.008443 +0.008521 +0.008581 +0.008421 +0.008353 +0.0084 +0.008477 +0.008552 +0.008605 +0.008468 +0.008389 +0.008458 +0.008517 +0.008608 +0.008664 +0.008515 +0.008429 +0.008487 +0.008563 +0.008663 +0.008687 +0.008551 +0.008488 +0.008524 +0.008615 +0.008681 +0.008744 +0.008596 +0.008528 +0.00856 +0.008634 +0.008723 +0.008782 +0.00864 +0.008567 +0.008611 +0.008695 +0.008779 +0.008839 +0.008684 +0.008603 +0.00866 +0.008735 +0.008805 +0.008872 +0.008719 +0.008657 +0.008716 +0.008791 +0.008869 +0.008916 +0.00877 +0.008712 +0.008779 +0.008799 +0.008889 +0.008952 +0.008804 +0.008725 +0.008789 +0.008865 +0.008948 +0.009008 +0.008859 +0.008786 +0.008831 +0.008898 +0.009004 +0.00905 +0.008903 +0.008827 +0.008881 +0.008954 +0.009039 +0.009111 +0.008954 +0.008892 +0.008915 +0.008994 +0.009076 +0.009143 +0.009 +0.008922 +0.008997 +0.009048 +0.009118 +0.00918 +0.009031 +0.008959 +0.009011 +0.009086 +0.009183 +0.009247 +0.009083 +0.009013 +0.009072 +0.009129 +0.009214 +0.009287 +0.009122 +0.009042 +0.009096 +0.009193 +0.0093 +0.009347 +0.009182 +0.009096 +0.009154 +0.00923 +0.009309 +0.009368 +0.009211 +0.009139 +0.009186 +0.00927 +0.009371 +0.009435 +0.009278 +0.009203 +0.009256 +0.009311 +0.009401 +0.00949 +0.009294 +0.009224 +0.009287 +0.009355 +0.00946 +0.009538 +0.009373 +0.009319 +0.00935 +0.00942 +0.009484 +0.009552 +0.0094 +0.009311 +0.009373 +0.009456 +0.009566 +0.009634 +0.00946 +0.009391 +0.009424 +0.009509 +0.009604 +0.009652 +0.009495 +0.009418 +0.009467 +0.009554 +0.009643 +0.009717 +0.009552 +0.009473 +0.009525 +0.009635 +0.009723 +0.009756 +0.009598 +0.009541 +0.009558 +0.009648 +0.009735 +0.009815 +0.009646 +0.009553 +0.009627 +0.009713 +0.009818 +0.009861 +0.009713 +0.009597 +0.009663 +0.009758 +0.009846 +0.009911 +0.009738 +0.009656 +0.009715 +0.009823 +0.009925 +0.010007 +0.009777 +0.009697 +0.009754 +0.009843 +0.009956 +0.010017 +0.009826 +0.009773 +0.009823 +0.009919 +0.010018 +0.010057 +0.009873 +0.009804 +0.009865 +0.009943 +0.010043 +0.01011 +0.009954 +0.009853 +0.009923 +0.010011 +0.010093 +0.010163 +0.010014 +0.009908 +0.009956 +0.010039 +0.010146 +0.010245 +0.010042 +0.009959 +0.010026 +0.010083 +0.0102 +0.010263 +0.010085 +0.009998 +0.010064 +0.01015 +0.010277 +0.010351 +0.010144 +0.010052 +0.010093 +0.010196 +0.010295 +0.010367 +0.010192 +0.010145 +0.010174 +0.010257 +0.010357 +0.01042 +0.010241 +0.010125 +0.010187 +0.010287 +0.010371 +0.01043 +0.010211 +0.01009 +0.010111 +0.010154 +0.010228 +0.010238 +0.010017 +0.009891 +0.009897 +0.009909 +0.009993 +0.009974 +0.009752 +0.009625 +0.009638 +0.009685 +0.009721 +0.009684 +0.009474 +0.009353 +0.009372 +0.009405 +0.009426 +0.009453 +0.009225 +0.009101 +0.009118 +0.009152 +0.009184 +0.009207 +0.00901 +0.008886 +0.00891 +0.00896 +0.00898 +0.009003 +0.008817 +0.008705 +0.008723 +0.008758 +0.008819 +0.008846 +0.008689 +0.008553 +0.008586 +0.008629 +0.008692 +0.0087 +0.008528 +0.008428 +0.008456 +0.00852 +0.00856 +0.008595 +0.008434 +0.008343 +0.008379 +0.008449 +0.008505 +0.00854 +0.00837 +0.00829 +0.008339 +0.008401 +0.00848 +0.008545 +0.008404 +0.008325 +0.008375 +0.00844 +0.008537 +0.008613 +0.008454 +0.008356 +0.008399 +0.008479 +0.008566 +0.008626 +0.008475 +0.008404 +0.008459 +0.00853 +0.008612 +0.008677 +0.008537 +0.008447 +0.008496 +0.008569 +0.008666 +0.008728 +0.008559 +0.008484 +0.008533 +0.008609 +0.008691 +0.008762 +0.008616 +0.00854 +0.008576 +0.008672 +0.00875 +0.008811 +0.008639 +0.00857 +0.008617 +0.008693 +0.008785 +0.008836 +0.0087 +0.00863 +0.008682 +0.008735 +0.008818 +0.008889 +0.008732 +0.00867 +0.008708 +0.008786 +0.008868 +0.008939 +0.008802 +0.008712 +0.008764 +0.008823 +0.008907 +0.008964 +0.008825 +0.00875 +0.008799 +0.008898 +0.008977 +0.009007 +0.008858 +0.008799 +0.008861 +0.008929 +0.009 +0.009062 +0.008914 +0.00884 +0.008882 +0.008962 +0.009044 +0.009115 +0.008956 +0.00889 +0.00894 +0.00901 +0.009093 +0.009156 +0.009 +0.008923 +0.008984 +0.009043 +0.009144 +0.009212 +0.009058 +0.009009 +0.009026 +0.009104 +0.009191 +0.009235 +0.009089 +0.008999 +0.009069 +0.009145 +0.009238 +0.009301 +0.009135 +0.009069 +0.009109 +0.009189 +0.009276 +0.009338 +0.009178 +0.009104 +0.009169 +0.009238 +0.009325 +0.0094 +0.009223 +0.009155 +0.009208 +0.009296 +0.009398 +0.009422 +0.009268 +0.009185 +0.009251 +0.009341 +0.009415 +0.009485 +0.009317 +0.009253 +0.009301 +0.009367 +0.009468 +0.009538 +0.009374 +0.009308 +0.009337 +0.00941 +0.009514 +0.009571 +0.009416 +0.009339 +0.009402 +0.009462 +0.00956 +0.009634 +0.009472 +0.00941 +0.00943 +0.009499 +0.009617 +0.009679 +0.009517 +0.009432 +0.009488 +0.009563 +0.009652 +0.009715 +0.009554 +0.009466 +0.00953 +0.009611 +0.009731 +0.009784 +0.009604 +0.009531 +0.009556 +0.009655 +0.009761 +0.009806 +0.009637 +0.009564 +0.009636 +0.009763 +0.009809 +0.009874 +0.009699 +0.009601 +0.009674 +0.009743 +0.009848 +0.009931 +0.009754 +0.009654 +0.009733 +0.009818 +0.009925 +0.009977 +0.00978 +0.009698 +0.009767 +0.009853 +0.00994 +0.010016 +0.009838 +0.00977 +0.009837 +0.009928 +0.010053 +0.010051 +0.009899 +0.009784 +0.009854 +0.009963 +0.010038 +0.010108 +0.009955 +0.00988 +0.009926 +0.010025 +0.010094 +0.010148 +0.009988 +0.009901 +0.009962 +0.010055 +0.010154 +0.010233 +0.010064 +0.00997 +0.010016 +0.010093 +0.010203 +0.010304 +0.010087 +0.009998 +0.010049 +0.010149 +0.010286 +0.010335 +0.010146 +0.010062 +0.010097 +0.0102 +0.010313 +0.010376 +0.0102 +0.0101 +0.010145 +0.010257 +0.010358 +0.010384 +0.010168 +0.010049 +0.010073 +0.010109 +0.010173 +0.010218 +0.010005 +0.009836 +0.009859 +0.009893 +0.009945 +0.009964 +0.009704 +0.009569 +0.009587 +0.00962 +0.009668 +0.009682 +0.009464 +0.009353 +0.009358 +0.009386 +0.009426 +0.009418 +0.009219 +0.00909 +0.009102 +0.009148 +0.00918 +0.009203 +0.009008 +0.008877 +0.008923 +0.008939 +0.008978 +0.009 +0.00881 +0.008696 +0.008717 +0.008762 +0.008811 +0.008868 +0.008657 +0.00854 +0.008569 +0.008618 +0.008671 +0.008701 +0.008529 +0.008414 +0.008445 +0.008497 +0.008556 +0.008573 +0.008424 +0.008324 +0.008343 +0.008395 +0.008458 +0.008507 +0.008346 +0.008278 +0.008298 +0.008357 +0.008427 +0.008498 +0.00835 +0.008285 +0.008337 +0.008381 +0.008461 +0.008532 +0.008388 +0.008328 +0.008364 +0.008426 +0.008508 +0.008573 +0.008439 +0.008354 +0.008417 +0.008485 +0.008551 +0.008616 +0.008465 +0.008405 +0.008446 +0.008512 +0.008596 +0.00867 +0.008523 +0.00847 +0.008507 +0.00856 +0.008645 +0.008689 +0.008551 +0.008484 +0.00853 +0.008592 +0.008693 +0.008742 +0.008597 +0.008527 +0.008601 +0.008647 +0.008735 +0.008789 +0.008634 +0.008561 +0.008618 +0.008693 +0.008767 +0.008833 +0.008686 +0.008616 +0.008664 +0.008728 +0.008837 +0.0089 +0.008742 +0.008644 +0.00869 +0.008778 +0.008856 +0.008952 +0.008759 +0.008692 +0.008744 +0.008818 +0.008904 +0.008977 +0.008818 +0.008743 +0.008795 +0.008866 +0.008954 +0.009008 +0.008855 +0.008791 +0.008832 +0.008907 +0.008996 +0.009077 +0.008906 +0.008833 +0.008878 +0.008958 +0.009075 +0.009094 +0.008945 +0.008865 +0.008913 +0.009009 +0.009106 +0.009179 +0.008991 +0.008922 +0.008959 +0.009031 +0.009135 +0.009183 +0.009039 +0.008962 +0.009016 +0.009092 +0.009197 +0.009257 +0.009086 +0.00901 +0.009058 +0.009123 +0.009218 +0.009278 +0.009131 +0.009071 +0.009117 +0.009186 +0.009286 +0.009336 +0.009181 +0.009106 +0.009143 +0.009215 +0.009315 +0.00939 +0.009204 +0.00914 +0.009188 +0.009293 +0.009383 +0.009438 +0.009272 +0.00919 +0.009242 +0.009321 +0.009396 +0.009469 +0.00931 +0.009217 +0.009287 +0.009394 +0.0095 +0.009547 +0.009362 +0.00928 +0.009312 +0.009407 +0.009494 +0.009563 +0.009404 +0.009326 +0.009375 +0.009484 +0.0096 +0.009616 +0.009453 +0.009374 +0.009416 +0.009504 +0.009601 +0.00967 +0.009494 +0.009417 +0.009486 +0.009579 +0.00966 +0.009724 +0.00956 +0.009485 +0.009519 +0.009601 +0.009703 +0.009762 +0.009588 +0.009498 +0.009579 +0.009669 +0.009751 +0.009814 +0.009642 +0.009553 +0.009623 +0.009703 +0.009795 +0.009858 +0.009686 +0.009627 +0.009669 +0.009761 +0.009864 +0.009907 +0.009723 +0.009652 +0.009722 +0.009838 +0.009886 +0.009943 +0.009801 +0.00972 +0.009796 +0.00985 +0.009946 +0.009991 +0.009822 +0.009747 +0.009815 +0.009895 +0.009986 +0.010082 +0.009899 +0.009813 +0.00988 +0.009949 +0.010032 +0.010111 +0.009925 +0.009845 +0.009915 +0.010002 +0.010154 +0.010165 +0.009993 +0.009903 +0.009961 +0.010032 +0.010137 +0.010211 +0.01003 +0.009943 +0.010001 +0.010124 +0.010224 +0.010275 +0.010096 +0.009992 +0.010071 +0.010149 +0.010238 +0.010306 +0.010142 +0.010036 +0.010128 +0.010227 +0.010334 +0.010395 +0.010174 +0.010076 +0.010149 +0.010259 +0.010351 +0.010398 +0.010211 +0.010111 +0.010191 +0.010219 +0.010267 +0.010287 +0.010065 +0.009931 +0.009958 +0.009996 +0.010034 +0.010037 +0.009808 +0.009664 +0.009688 +0.0097 +0.009734 +0.00976 +0.009534 +0.00944 +0.009454 +0.009445 +0.009462 +0.009475 +0.009279 +0.00916 +0.009169 +0.009204 +0.009231 +0.009245 +0.009046 +0.008946 +0.008946 +0.008992 +0.009016 +0.009031 +0.008837 +0.008739 +0.008768 +0.008794 +0.008838 +0.008866 +0.00868 +0.008583 +0.008616 +0.008668 +0.008714 +0.008747 +0.008538 +0.008447 +0.008499 +0.008526 +0.008574 +0.008592 +0.008425 +0.008341 +0.008382 +0.008426 +0.008474 +0.008505 +0.008359 +0.008251 +0.008292 +0.00835 +0.008403 +0.008448 +0.008302 +0.008234 +0.008278 +0.008345 +0.008413 +0.008493 +0.008349 +0.008279 +0.008308 +0.008381 +0.008478 +0.008533 +0.008366 +0.008291 +0.008342 +0.008425 +0.008518 +0.008567 +0.008423 +0.008353 +0.00841 +0.008455 +0.008546 +0.008606 +0.008464 +0.008392 +0.008433 +0.008517 +0.008591 +0.008661 +0.008497 +0.008441 +0.008485 +0.008555 +0.008626 +0.008685 +0.008544 +0.008475 +0.00853 +0.008601 +0.00869 +0.008732 +0.008591 +0.008521 +0.008571 +0.008648 +0.008717 +0.008767 +0.008645 +0.008553 +0.008601 +0.008674 +0.008762 +0.008825 +0.008672 +0.008612 +0.008665 +0.008736 +0.008818 +0.008852 +0.008716 +0.008639 +0.008692 +0.008769 +0.008851 +0.008912 +0.008759 +0.008702 +0.008765 +0.008841 +0.008896 +0.008937 +0.008791 +0.008724 +0.008784 +0.008851 +0.008937 +0.009 +0.008849 +0.008776 +0.008836 +0.008923 +0.008988 +0.009048 +0.008879 +0.008808 +0.008865 +0.008942 +0.00904 +0.009113 +0.008924 +0.008861 +0.008927 +0.009003 +0.009082 +0.009146 +0.008991 +0.008917 +0.008939 +0.009048 +0.009109 +0.009173 +0.009036 +0.008953 +0.008991 +0.009081 +0.009156 +0.009227 +0.009069 +0.009002 +0.009036 +0.00912 +0.009224 +0.009274 +0.009129 +0.009053 +0.009085 +0.00917 +0.009263 +0.009331 +0.009169 +0.009078 +0.009131 +0.009218 +0.009338 +0.009367 +0.00921 +0.009123 +0.009185 +0.009275 +0.009348 +0.009407 +0.009256 +0.009175 +0.009219 +0.009308 +0.009409 +0.009458 +0.009314 +0.009226 +0.009274 +0.009358 +0.009449 +0.009514 +0.009348 +0.00926 +0.009334 +0.009394 +0.009492 +0.009562 +0.009415 +0.009338 +0.009369 +0.009445 +0.009539 +0.009602 +0.009446 +0.009373 +0.009408 +0.009502 +0.009602 +0.009655 +0.009494 +0.009397 +0.009454 +0.009547 +0.009643 +0.009702 +0.009525 +0.009455 +0.00951 +0.009606 +0.009722 +0.009765 +0.009567 +0.009498 +0.009556 +0.009674 +0.009735 +0.009787 +0.009622 +0.009541 +0.009625 +0.0097 +0.009798 +0.009853 +0.009664 +0.009593 +0.009652 +0.009735 +0.009829 +0.009888 +0.009734 +0.009651 +0.009733 +0.00983 +0.009875 +0.009932 +0.00978 +0.009683 +0.009746 +0.009837 +0.009937 +0.010027 +0.009839 +0.009735 +0.009796 +0.009895 +0.009974 +0.010035 +0.009871 +0.009791 +0.009847 +0.009927 +0.010056 +0.010097 +0.009939 +0.00986 +0.009894 +0.00997 +0.010088 +0.010136 +0.009977 +0.009893 +0.009941 +0.010039 +0.010146 +0.010233 +0.010068 +0.009931 +0.009972 +0.010099 +0.010171 +0.010241 +0.010064 +0.009978 +0.01007 +0.010153 +0.010248 +0.010316 +0.010113 +0.010021 +0.010094 +0.010181 +0.010284 +0.010362 +0.010165 +0.010107 +0.010165 +0.010258 +0.010332 +0.010372 +0.010212 +0.010108 +0.010128 +0.010171 +0.010238 +0.010296 +0.010081 +0.009926 +0.00996 +0.01001 +0.010061 +0.010056 +0.009838 +0.009703 +0.009732 +0.009758 +0.00979 +0.009813 +0.009576 +0.009462 +0.009462 +0.009492 +0.009533 +0.009533 +0.009323 +0.009211 +0.00922 +0.009278 +0.009285 +0.009287 +0.009068 +0.008964 +0.008986 +0.009011 +0.009063 +0.009059 +0.008867 +0.008765 +0.008804 +0.008845 +0.008881 +0.008891 +0.008705 +0.008612 +0.008644 +0.008682 +0.008739 +0.008759 +0.008591 +0.008504 +0.008541 +0.00859 +0.00864 +0.008677 +0.008509 +0.008426 +0.008447 +0.008486 +0.008531 +0.008572 +0.008431 +0.008351 +0.008403 +0.00846 +0.008528 +0.008574 +0.008428 +0.008353 +0.008406 +0.008462 +0.008549 +0.008607 +0.008468 +0.008392 +0.008432 +0.008537 +0.008613 +0.00865 +0.008506 +0.00844 +0.008474 +0.008548 +0.008632 +0.008693 +0.008562 +0.00849 +0.008539 +0.008584 +0.008684 +0.008737 +0.0086 +0.008528 +0.008578 +0.008629 +0.008718 +0.008766 +0.008629 +0.008566 +0.008604 +0.008675 +0.008773 +0.00884 +0.008681 +0.008607 +0.008667 +0.008738 +0.008799 +0.008871 +0.008714 +0.008638 +0.008694 +0.008776 +0.00885 +0.008934 +0.00877 +0.008684 +0.008729 +0.008825 +0.008879 +0.008963 +0.008807 +0.008732 +0.008804 +0.008867 +0.008949 +0.009004 +0.00885 +0.008775 +0.008818 +0.0089 +0.008986 +0.009039 +0.0089 +0.008825 +0.008886 +0.008957 +0.009051 +0.009101 +0.008929 +0.008857 +0.008914 +0.009004 +0.009095 +0.009127 +0.008977 +0.008912 +0.008965 +0.009072 +0.009125 +0.009173 +0.00902 +0.008943 +0.008999 +0.009083 +0.009169 +0.009221 +0.009076 +0.009022 +0.009052 +0.009138 +0.009228 +0.009275 +0.009113 +0.009037 +0.009097 +0.009167 +0.009255 +0.009328 +0.009175 +0.009125 +0.009154 +0.009217 +0.009307 +0.009359 +0.009208 +0.009126 +0.00918 +0.009264 +0.009352 +0.009407 +0.009276 +0.009189 +0.009241 +0.009324 +0.009421 +0.009454 +0.009315 +0.009225 +0.009271 +0.009351 +0.009449 +0.009504 +0.009339 +0.009257 +0.00933 +0.009425 +0.009542 +0.009544 +0.009392 +0.009313 +0.009364 +0.009452 +0.009548 +0.0096 +0.009446 +0.009356 +0.009412 +0.009514 +0.009589 +0.009653 +0.009495 +0.009412 +0.009484 +0.009556 +0.009642 +0.009721 +0.009526 +0.009457 +0.009504 +0.009588 +0.009685 +0.00976 +0.009617 +0.009509 +0.009588 +0.009644 +0.009723 +0.009788 +0.009626 +0.009545 +0.009611 +0.009677 +0.009786 +0.009878 +0.009689 +0.009604 +0.009666 +0.009738 +0.009834 +0.009891 +0.009735 +0.009644 +0.009691 +0.009801 +0.00989 +0.009965 +0.009789 +0.009714 +0.009769 +0.009817 +0.009921 +0.009986 +0.009852 +0.00973 +0.009784 +0.009902 +0.01001 +0.010063 +0.009886 +0.009777 +0.009834 +0.009934 +0.010038 +0.010089 +0.009915 +0.009847 +0.00991 +0.010008 +0.010105 +0.010154 +0.00996 +0.009881 +0.009949 +0.010064 +0.010128 +0.010189 +0.010016 +0.009939 +0.009993 +0.010118 +0.010177 +0.010245 +0.01007 +0.009995 +0.010053 +0.010162 +0.010231 +0.010308 +0.010119 +0.01003 +0.010105 +0.010186 +0.010279 +0.010368 +0.010172 +0.010092 +0.010177 +0.010264 +0.010362 +0.010389 +0.010212 +0.010133 +0.010197 +0.010284 +0.010396 +0.010489 +0.01027 +0.010172 +0.010226 +0.01029 +0.010368 +0.010402 +0.010163 +0.010036 +0.010097 +0.010142 +0.01019 +0.010194 +0.009971 +0.009821 +0.009832 +0.00987 +0.009916 +0.009938 +0.009711 +0.009579 +0.009576 +0.009624 +0.009663 +0.009651 +0.009436 +0.009298 +0.009316 +0.009371 +0.009393 +0.009399 +0.009208 +0.009063 +0.009082 +0.009126 +0.009168 +0.009182 +0.008971 +0.00887 +0.008867 +0.008916 +0.008962 +0.00898 +0.008787 +0.008681 +0.008712 +0.008763 +0.008833 +0.008823 +0.008638 +0.008535 +0.00857 +0.008634 +0.008677 +0.008706 +0.008528 +0.008455 +0.008461 +0.00854 +0.008578 +0.008607 +0.008436 +0.00835 +0.008395 +0.008453 +0.008525 +0.008563 +0.008415 +0.008335 +0.008397 +0.008455 +0.008538 +0.00859 +0.008449 +0.008381 +0.008422 +0.008514 +0.008585 +0.008633 +0.00849 +0.008421 +0.008454 +0.008546 +0.008613 +0.008683 +0.008542 +0.008457 +0.008509 +0.008583 +0.008671 +0.008725 +0.008573 +0.008503 +0.008567 +0.008651 +0.008716 +0.008767 +0.008607 +0.008537 +0.008593 +0.008673 +0.008752 +0.008803 +0.008657 +0.00859 +0.008665 +0.008735 +0.008804 +0.008851 +0.008691 +0.008626 +0.008679 +0.008749 +0.008836 +0.00891 +0.008738 +0.008668 +0.008731 +0.008817 +0.008902 +0.008951 +0.008822 +0.008702 +0.00876 +0.008835 +0.008929 +0.00898 +0.008831 +0.008765 +0.00881 +0.008893 +0.008994 +0.009048 +0.008905 +0.008817 +0.008843 +0.00892 +0.009012 +0.009067 +0.008923 +0.008838 +0.008904 +0.008991 +0.009067 +0.009143 +0.008981 +0.008906 +0.008942 +0.009013 +0.009114 +0.009164 +0.009036 +0.008933 +0.008988 +0.009068 +0.009155 +0.009231 +0.009071 +0.008987 +0.009024 +0.009112 +0.009233 +0.009267 +0.009097 +0.009024 +0.009074 +0.009159 +0.009252 +0.009311 +0.009165 +0.009094 +0.009129 +0.009194 +0.009287 +0.009345 +0.009194 +0.009114 +0.009177 +0.009251 +0.009341 +0.009425 +0.009249 +0.00917 +0.009206 +0.00929 +0.009387 +0.009448 +0.009295 +0.009229 +0.00928 +0.009364 +0.009431 +0.009504 +0.00934 +0.009257 +0.009295 +0.00938 +0.009494 +0.009536 +0.009379 +0.009308 +0.009362 +0.009436 +0.009536 +0.009611 +0.009442 +0.009335 +0.009403 +0.009476 +0.009579 +0.009649 +0.009468 +0.009391 +0.009464 +0.009549 +0.009677 +0.009693 +0.009527 +0.009425 +0.009513 +0.009576 +0.009662 +0.009726 +0.009574 +0.009496 +0.009545 +0.009635 +0.009751 +0.009799 +0.009604 +0.009535 +0.009588 +0.009678 +0.00978 +0.00983 +0.009666 +0.009592 +0.00964 +0.009741 +0.009844 +0.009897 +0.009736 +0.009619 +0.009676 +0.009776 +0.009874 +0.009933 +0.00976 +0.009666 +0.009765 +0.009836 +0.009917 +0.009989 +0.009816 +0.009725 +0.0098 +0.00988 +0.009966 +0.01004 +0.009861 +0.009787 +0.009848 +0.009939 +0.010002 +0.010079 +0.009943 +0.009855 +0.00989 +0.009956 +0.01006 +0.01015 +0.00998 +0.009882 +0.009924 +0.010024 +0.010119 +0.010176 +0.010016 +0.009923 +0.009979 +0.010085 +0.010201 +0.010242 +0.010108 +0.009959 +0.010021 +0.010122 +0.01022 +0.010286 +0.010112 +0.010026 +0.010137 +0.010212 +0.010273 +0.010335 +0.010137 +0.010069 +0.01012 +0.010228 +0.010344 +0.010387 +0.010207 +0.010146 +0.0102 +0.010292 +0.010399 +0.010424 +0.010242 +0.010155 +0.010219 +0.010283 +0.010357 +0.010383 +0.010172 +0.010035 +0.010079 +0.01014 +0.010161 +0.010167 +0.009922 +0.009767 +0.009806 +0.009835 +0.009887 +0.009889 +0.009651 +0.009508 +0.009535 +0.00956 +0.009599 +0.0096 +0.00938 +0.009255 +0.009257 +0.009299 +0.009327 +0.009324 +0.00912 +0.008982 +0.008995 +0.009042 +0.009079 +0.009104 +0.008901 +0.00877 +0.008783 +0.008815 +0.008862 +0.008885 +0.008692 +0.008571 +0.008612 +0.008663 +0.008709 +0.008734 +0.008568 +0.008452 +0.008497 +0.00855 +0.008575 +0.008597 +0.008429 +0.008336 +0.008369 +0.00842 +0.008474 +0.008517 +0.00835 +0.008264 +0.008294 +0.008362 +0.008427 +0.008455 +0.008282 +0.008202 +0.008255 +0.00833 +0.008411 +0.008462 +0.008331 +0.008247 +0.008295 +0.008363 +0.008442 +0.008502 +0.008362 +0.008284 +0.008335 +0.008408 +0.008496 +0.008566 +0.008406 +0.008334 +0.008372 +0.008448 +0.008529 +0.008599 +0.008446 +0.008366 +0.008425 +0.008502 +0.008599 +0.008652 +0.00849 +0.008419 +0.008463 +0.008528 +0.008618 +0.008663 +0.008524 +0.008446 +0.008537 +0.008574 +0.008653 +0.008723 +0.008568 +0.008509 +0.00854 +0.008626 +0.008699 +0.008758 +0.008624 +0.00854 +0.008582 +0.008666 +0.008753 +0.008806 +0.008663 +0.008592 +0.008654 +0.008723 +0.008806 +0.008859 +0.008688 +0.008621 +0.008673 +0.008755 +0.008838 +0.008889 +0.008764 +0.008676 +0.008725 +0.008789 +0.008879 +0.008931 +0.008783 +0.008713 +0.008769 +0.008832 +0.008929 +0.008995 +0.008838 +0.008773 +0.008816 +0.008896 +0.008953 +0.009029 +0.008881 +0.008806 +0.008876 +0.008921 +0.009005 +0.009069 +0.00892 +0.008867 +0.008913 +0.008981 +0.009059 +0.009114 +0.00895 +0.008879 +0.008945 +0.009031 +0.0091 +0.009168 +0.009011 +0.008941 +0.009 +0.009089 +0.009142 +0.009208 +0.009059 +0.008979 +0.009024 +0.009102 +0.009198 +0.009284 +0.009119 +0.009034 +0.009085 +0.009171 +0.00924 +0.009311 +0.009139 +0.009058 +0.009118 +0.009201 +0.009282 +0.009345 +0.009203 +0.009131 +0.009187 +0.009299 +0.009317 +0.009391 +0.009236 +0.009149 +0.009207 +0.009293 +0.009383 +0.009449 +0.009302 +0.009235 +0.009299 +0.00937 +0.009411 +0.009476 +0.009326 +0.009242 +0.009301 +0.009389 +0.009503 +0.009546 +0.009382 +0.009316 +0.009374 +0.00944 +0.009518 +0.00958 +0.009425 +0.009337 +0.009402 +0.009483 +0.009589 +0.009659 +0.009492 +0.0094 +0.009453 +0.009522 +0.00962 +0.009726 +0.009511 +0.009432 +0.009486 +0.009581 +0.009696 +0.009745 +0.009576 +0.009499 +0.009538 +0.009617 +0.009714 +0.00978 +0.009612 +0.009547 +0.009589 +0.009682 +0.009766 +0.009839 +0.009662 +0.00957 +0.009649 +0.009723 +0.009824 +0.009891 +0.009726 +0.00967 +0.009687 +0.009766 +0.009867 +0.009927 +0.009767 +0.009678 +0.009744 +0.009829 +0.009929 +0.009989 +0.009835 +0.009712 +0.009782 +0.009872 +0.009968 +0.010036 +0.009862 +0.009781 +0.009837 +0.009947 +0.01004 +0.010093 +0.009902 +0.009828 +0.009915 +0.009976 +0.010064 +0.010121 +0.009974 +0.009888 +0.009941 +0.010031 +0.010133 +0.010166 +0.010001 +0.009926 +0.009979 +0.010077 +0.010172 +0.010243 +0.010079 +0.009989 +0.010046 +0.01013 +0.010213 +0.010289 +0.010112 +0.01002 +0.010085 +0.010209 +0.010275 +0.010329 +0.010124 +0.010036 +0.010083 +0.010142 +0.010149 +0.010174 +0.009984 +0.009861 +0.00989 +0.009914 +0.009967 +0.009962 +0.009765 +0.009619 +0.009633 +0.009676 +0.009698 +0.009711 +0.009504 +0.009391 +0.009379 +0.00941 +0.009446 +0.009472 +0.009261 +0.009122 +0.009132 +0.009161 +0.009204 +0.009212 +0.009031 +0.008939 +0.008936 +0.008942 +0.008984 +0.009003 +0.008817 +0.008714 +0.008736 +0.008776 +0.008815 +0.00884 +0.008669 +0.008583 +0.008605 +0.008635 +0.008684 +0.008701 +0.008538 +0.008443 +0.008478 +0.008525 +0.008583 +0.008587 +0.00842 +0.008331 +0.008373 +0.008423 +0.008457 +0.008491 +0.008333 +0.008258 +0.008291 +0.008361 +0.008396 +0.008449 +0.008309 +0.008253 +0.008304 +0.008366 +0.008434 +0.008503 +0.008351 +0.008274 +0.00832 +0.008393 +0.00848 +0.008529 +0.008388 +0.008313 +0.008387 +0.008472 +0.008546 +0.008576 +0.008449 +0.008347 +0.008398 +0.00848 +0.008553 +0.008616 +0.008469 +0.008406 +0.008446 +0.008537 +0.008625 +0.008679 +0.008533 +0.008445 +0.008489 +0.008556 +0.008637 +0.008701 +0.008557 +0.008482 +0.008533 +0.008629 +0.008698 +0.00876 +0.008609 +0.008543 +0.008605 +0.008658 +0.008718 +0.008784 +0.008634 +0.008573 +0.008641 +0.008689 +0.008784 +0.008845 +0.008698 +0.008622 +0.008668 +0.008737 +0.008817 +0.00887 +0.008732 +0.008662 +0.008704 +0.008794 +0.00888 +0.00893 +0.008768 +0.008695 +0.008752 +0.008819 +0.008908 +0.008974 +0.008824 +0.008774 +0.008803 +0.008879 +0.008969 +0.009014 +0.008854 +0.008786 +0.008852 +0.008909 +0.008998 +0.00906 +0.008895 +0.008829 +0.008888 +0.008962 +0.009064 +0.009129 +0.008942 +0.008872 +0.008926 +0.009001 +0.009088 +0.00915 +0.008997 +0.008914 +0.008974 +0.009059 +0.009167 +0.009241 +0.009037 +0.008958 +0.009008 +0.009084 +0.009179 +0.009236 +0.009086 +0.009 +0.009091 +0.009133 +0.009242 +0.009305 +0.009138 +0.009043 +0.009106 +0.009178 +0.009268 +0.009331 +0.009188 +0.009084 +0.009153 +0.009247 +0.009341 +0.009397 +0.00924 +0.009145 +0.009219 +0.009282 +0.009383 +0.009413 +0.00925 +0.009178 +0.009244 +0.00934 +0.009437 +0.009489 +0.00932 +0.009231 +0.009287 +0.009366 +0.009454 +0.009525 +0.009362 +0.00928 +0.00934 +0.009444 +0.009527 +0.009586 +0.009415 +0.009321 +0.009376 +0.00947 +0.009561 +0.009652 +0.009447 +0.009364 +0.009445 +0.009545 +0.009623 +0.009668 +0.009491 +0.009424 +0.009468 +0.009555 +0.00966 +0.009713 +0.009549 +0.009471 +0.009548 +0.009626 +0.009717 +0.009785 +0.009588 +0.009509 +0.009578 +0.009655 +0.009755 +0.009826 +0.009649 +0.009608 +0.009636 +0.00971 +0.009807 +0.009846 +0.009705 +0.009613 +0.009666 +0.009761 +0.009843 +0.00991 +0.009751 +0.009674 +0.009728 +0.009813 +0.009918 +0.009949 +0.009788 +0.009713 +0.009767 +0.009854 +0.009955 +0.010022 +0.009863 +0.009776 +0.009845 +0.009934 +0.009979 +0.010045 +0.009879 +0.009809 +0.009858 +0.009954 +0.010082 +0.010121 +0.009952 +0.009863 +0.009927 +0.009993 +0.010086 +0.010166 +0.009983 +0.009907 +0.009984 +0.010056 +0.010148 +0.010224 +0.010038 +0.009957 +0.01002 +0.010121 +0.010251 +0.010251 +0.010067 +0.010015 +0.010074 +0.010145 +0.010259 +0.010325 +0.010136 +0.010062 +0.010102 +0.010185 +0.01028 +0.010343 +0.010147 +0.010005 +0.010035 +0.010081 +0.010144 +0.010182 +0.009952 +0.009824 +0.009845 +0.00987 +0.009916 +0.009951 +0.009733 +0.009584 +0.009556 +0.009596 +0.00964 +0.009666 +0.009427 +0.009308 +0.009323 +0.009337 +0.009406 +0.009408 +0.009181 +0.009053 +0.00907 +0.009109 +0.009132 +0.009161 +0.008965 +0.008842 +0.008853 +0.00889 +0.008948 +0.008965 +0.008754 +0.008655 +0.008666 +0.008738 +0.008768 +0.008787 +0.008624 +0.008504 +0.008527 +0.008577 +0.008626 +0.008675 +0.008506 +0.008387 +0.008408 +0.00846 +0.008526 +0.008554 +0.008389 +0.008291 +0.008324 +0.008371 +0.008434 +0.008484 +0.008332 +0.008242 +0.008273 +0.008334 +0.008402 +0.00846 +0.008319 +0.008246 +0.00829 +0.008385 +0.008433 +0.008492 +0.008349 +0.008289 +0.008347 +0.008412 +0.008471 +0.008532 +0.008397 +0.008321 +0.008375 +0.008442 +0.008526 +0.008572 +0.008432 +0.008397 +0.008425 +0.008497 +0.008565 +0.008625 +0.008484 +0.008424 +0.008456 +0.00853 +0.008608 +0.008662 +0.008527 +0.008446 +0.008524 +0.008594 +0.008656 +0.0087 +0.008556 +0.008488 +0.008554 +0.008609 +0.008713 +0.008749 +0.008606 +0.00854 +0.008584 +0.008667 +0.008749 +0.008805 +0.008643 +0.008582 +0.008624 +0.008701 +0.008791 +0.008835 +0.008698 +0.008629 +0.008675 +0.008752 +0.008841 +0.008906 +0.008748 +0.008697 +0.008714 +0.00878 +0.008859 +0.008926 +0.008781 +0.008705 +0.008755 +0.008833 +0.008931 +0.008996 +0.008831 +0.008745 +0.008801 +0.008863 +0.008961 +0.009029 +0.00887 +0.008794 +0.00885 +0.008929 +0.009017 +0.009081 +0.008933 +0.008833 +0.008883 +0.008964 +0.00905 +0.009141 +0.008964 +0.008885 +0.008928 +0.009018 +0.009114 +0.009168 +0.009006 +0.008911 +0.008976 +0.009048 +0.009139 +0.009238 +0.009041 +0.008961 +0.009026 +0.00912 +0.009211 +0.009257 +0.009103 +0.009013 +0.009052 +0.009146 +0.009235 +0.009297 +0.009137 +0.009059 +0.009131 +0.009241 +0.00929 +0.009339 +0.009182 +0.009092 +0.009161 +0.009229 +0.009325 +0.009391 +0.009225 +0.009145 +0.009215 +0.009308 +0.009389 +0.009456 +0.009285 +0.009178 +0.009241 +0.009332 +0.009432 +0.009482 +0.009319 +0.009248 +0.009309 +0.009394 +0.009493 +0.009553 +0.009391 +0.009266 +0.009326 +0.009418 +0.009511 +0.009589 +0.009425 +0.009332 +0.009409 +0.009484 +0.009577 +0.009639 +0.009453 +0.00937 +0.009433 +0.009521 +0.009604 +0.00968 +0.009519 +0.009427 +0.009506 +0.00959 +0.009688 +0.009723 +0.009548 +0.009475 +0.009561 +0.009614 +0.009704 +0.009758 +0.009606 +0.009548 +0.00959 +0.009677 +0.009804 +0.0098 +0.00964 +0.00957 +0.00962 +0.009711 +0.009815 +0.009871 +0.009715 +0.009644 +0.009697 +0.00978 +0.00985 +0.009925 +0.009747 +0.009668 +0.009733 +0.009828 +0.009956 +0.00997 +0.009805 +0.009722 +0.009784 +0.009866 +0.009946 +0.010019 +0.009853 +0.009768 +0.009824 +0.009912 +0.010013 +0.010095 +0.009923 +0.009825 +0.009878 +0.009966 +0.010064 +0.01012 +0.009953 +0.00986 +0.009916 +0.010025 +0.010139 +0.010228 +0.010002 +0.009915 +0.009959 +0.010052 +0.010168 +0.01022 +0.010045 +0.009964 +0.010026 +0.010135 +0.01025 +0.010293 +0.010091 +0.010012 +0.01006 +0.010163 +0.010271 +0.010328 +0.010167 +0.01007 +0.010118 +0.0102 +0.010303 +0.010382 +0.010178 +0.010051 +0.010076 +0.010134 +0.010202 +0.010226 +0.010013 +0.009863 +0.009878 +0.00992 +0.009964 +0.00996 +0.009756 +0.009626 +0.00963 +0.009662 +0.00968 +0.009694 +0.009478 +0.009332 +0.009341 +0.009367 +0.009409 +0.009416 +0.009207 +0.009096 +0.009111 +0.009161 +0.009155 +0.009164 +0.008965 +0.008845 +0.008868 +0.0089 +0.008925 +0.008943 +0.008771 +0.008661 +0.008688 +0.008729 +0.008764 +0.008785 +0.008625 +0.008515 +0.00854 +0.008575 +0.008634 +0.008665 +0.008504 +0.008404 +0.008448 +0.008489 +0.008545 +0.008583 +0.008423 +0.008337 +0.00836 +0.008388 +0.008441 +0.008492 +0.008341 +0.00826 +0.008288 +0.00835 +0.008452 +0.008485 +0.008328 +0.008262 +0.008303 +0.008362 +0.008459 +0.0085 +0.008374 +0.008291 +0.008337 +0.008413 +0.008499 +0.008561 +0.008412 +0.008343 +0.008401 +0.008455 +0.008531 +0.008591 +0.00845 +0.008402 +0.008436 +0.008497 +0.00857 +0.008638 +0.008499 +0.008434 +0.00849 +0.008553 +0.008627 +0.008664 +0.008557 +0.008451 +0.008511 +0.008577 +0.008664 +0.008716 +0.008585 +0.008515 +0.008558 +0.00864 +0.008717 +0.008764 +0.008613 +0.00855 +0.008594 +0.008667 +0.008751 +0.008818 +0.008681 +0.008615 +0.008648 +0.008717 +0.008811 +0.008853 +0.0087 +0.008627 +0.008679 +0.008758 +0.008839 +0.008897 +0.008739 +0.008713 +0.008734 +0.00881 +0.008896 +0.008953 +0.008789 +0.00871 +0.008768 +0.008846 +0.008923 +0.008994 +0.008839 +0.008756 +0.008832 +0.008907 +0.008993 +0.009072 +0.008879 +0.008798 +0.008844 +0.008936 +0.009028 +0.009066 +0.008929 +0.00884 +0.008905 +0.008978 +0.009065 +0.009137 +0.008969 +0.008896 +0.008944 +0.009027 +0.009101 +0.009173 +0.009037 +0.008933 +0.008994 +0.009075 +0.009166 +0.00923 +0.009065 +0.008983 +0.009045 +0.009138 +0.009198 +0.009256 +0.009119 +0.009026 +0.00908 +0.009176 +0.009254 +0.009309 +0.009146 +0.009064 +0.009127 +0.009214 +0.009298 +0.009363 +0.009216 +0.009118 +0.009169 +0.009254 +0.009342 +0.009399 +0.009247 +0.009165 +0.009218 +0.009299 +0.009404 +0.009478 +0.00933 +0.009196 +0.009247 +0.009347 +0.00943 +0.009504 +0.009324 +0.009253 +0.009335 +0.0094 +0.009486 +0.009547 +0.009401 +0.009291 +0.009343 +0.009444 +0.009532 +0.009587 +0.00943 +0.009342 +0.009412 +0.009498 +0.009603 +0.009656 +0.009487 +0.00939 +0.00947 +0.009535 +0.00962 +0.00968 +0.009521 +0.009443 +0.009496 +0.009596 +0.009687 +0.009749 +0.009561 +0.009505 +0.009537 +0.009619 +0.009717 +0.009789 +0.009608 +0.009538 +0.009611 +0.009696 +0.009793 +0.00984 +0.009655 +0.009576 +0.009639 +0.009729 +0.009859 +0.009875 +0.009707 +0.009625 +0.00971 +0.009798 +0.009886 +0.009911 +0.009766 +0.009671 +0.009735 +0.009829 +0.009924 +0.009999 +0.009802 +0.009743 +0.0098 +0.009875 +0.009969 +0.010023 +0.009861 +0.009784 +0.009831 +0.009913 +0.010029 +0.010109 +0.009945 +0.009832 +0.009886 +0.009964 +0.010068 +0.01015 +0.009946 +0.009873 +0.009932 +0.01002 +0.010129 +0.010208 +0.010024 +0.00993 +0.009979 +0.01006 +0.010159 +0.010242 +0.010056 +0.009965 +0.010048 +0.010119 +0.01025 +0.010313 +0.010141 +0.010024 +0.010071 +0.01016 +0.010278 +0.010343 +0.010145 +0.010082 +0.010163 +0.010219 +0.010312 +0.010362 +0.010156 +0.010042 +0.010078 +0.01012 +0.010186 +0.010189 +0.009973 +0.009851 +0.009857 +0.009883 +0.00993 +0.009941 +0.009716 +0.009577 +0.009609 +0.009615 +0.009644 +0.009673 +0.009453 +0.009323 +0.00931 +0.009342 +0.009388 +0.00943 +0.009191 +0.009068 +0.009083 +0.009133 +0.009164 +0.009191 +0.008983 +0.008868 +0.008867 +0.008915 +0.00895 +0.008975 +0.008801 +0.008681 +0.008697 +0.008754 +0.008811 +0.008845 +0.008674 +0.008547 +0.008572 +0.0086 +0.008654 +0.008697 +0.00852 +0.008426 +0.00846 +0.008512 +0.008544 +0.008588 +0.008441 +0.008327 +0.008356 +0.008405 +0.008467 +0.008515 +0.008357 +0.008279 +0.008317 +0.008372 +0.008444 +0.008492 +0.008353 +0.008278 +0.008322 +0.008401 +0.008476 +0.008553 +0.008397 +0.008317 +0.008375 +0.008466 +0.008516 +0.008579 +0.008415 +0.008354 +0.008411 +0.008481 +0.008555 +0.008615 +0.008471 +0.008409 +0.008453 +0.008528 +0.00862 +0.008666 +0.008515 +0.008443 +0.008496 +0.008586 +0.008637 +0.008704 +0.008563 +0.008478 +0.008536 +0.008609 +0.008725 +0.008765 +0.0086 +0.008532 +0.008565 +0.008643 +0.008738 +0.008798 +0.008637 +0.008566 +0.008616 +0.008703 +0.008785 +0.008851 +0.008687 +0.00862 +0.008657 +0.008737 +0.008816 +0.008878 +0.008734 +0.008655 +0.008698 +0.00878 +0.00888 +0.008931 +0.008775 +0.008714 +0.008752 +0.008854 +0.008917 +0.008964 +0.008809 +0.008722 +0.008793 +0.008871 +0.008948 +0.00903 +0.008875 +0.008781 +0.008835 +0.008921 +0.009006 +0.009068 +0.008916 +0.008855 +0.008875 +0.008959 +0.009049 +0.009103 +0.008949 +0.008873 +0.008927 +0.009008 +0.009093 +0.009162 +0.009011 +0.008967 +0.008973 +0.009036 +0.009132 +0.009181 +0.009048 +0.008959 +0.009009 +0.009103 +0.009193 +0.009269 +0.009094 +0.009026 +0.009049 +0.009129 +0.009222 +0.009291 +0.00913 +0.009051 +0.009108 +0.009194 +0.009271 +0.009363 +0.009195 +0.009109 +0.009139 +0.009225 +0.009345 +0.009395 +0.009215 +0.009148 +0.009188 +0.009285 +0.009374 +0.009448 +0.009283 +0.009201 +0.009235 +0.009312 +0.009422 +0.009484 +0.00931 +0.009234 +0.009295 +0.00938 +0.00947 +0.009551 +0.00936 +0.009283 +0.009336 +0.009411 +0.009509 +0.009572 +0.009418 +0.00936 +0.009397 +0.009469 +0.009575 +0.009629 +0.00949 +0.009363 +0.009416 +0.009514 +0.009598 +0.00967 +0.009501 +0.009426 +0.009476 +0.009573 +0.009688 +0.009722 +0.009555 +0.009469 +0.009519 +0.009614 +0.009706 +0.009762 +0.009598 +0.009522 +0.009589 +0.009705 +0.009784 +0.009823 +0.009635 +0.009559 +0.009624 +0.009702 +0.009828 +0.009848 +0.009708 +0.009629 +0.009685 +0.009774 +0.009856 +0.009905 +0.009747 +0.009666 +0.009715 +0.009808 +0.009898 +0.009961 +0.009825 +0.009724 +0.009785 +0.009879 +0.009961 +0.010042 +0.009828 +0.009747 +0.009814 +0.009908 +0.010006 +0.010063 +0.009892 +0.009845 +0.009874 +0.009946 +0.010061 +0.010121 +0.00994 +0.009866 +0.009924 +0.01 +0.010122 +0.010169 +0.009994 +0.009912 +0.009955 +0.010056 +0.010166 +0.010239 +0.010087 +0.009969 +0.010012 +0.010102 +0.010206 +0.010281 +0.010089 +0.009998 +0.010078 +0.010163 +0.010294 +0.010317 +0.010146 +0.010055 +0.010117 +0.0102 +0.010273 +0.010346 +0.010126 +0.010022 +0.010034 +0.010084 +0.010163 +0.010181 +0.009955 +0.009824 +0.009864 +0.009898 +0.009926 +0.009896 +0.009695 +0.009576 +0.009568 +0.009612 +0.009669 +0.009664 +0.009449 +0.009335 +0.009332 +0.009364 +0.009397 +0.009434 +0.009219 +0.009085 +0.0091 +0.009133 +0.009175 +0.009193 +0.008988 +0.008864 +0.008877 +0.008926 +0.008963 +0.009016 +0.008796 +0.008682 +0.008702 +0.008762 +0.008812 +0.008832 +0.00864 +0.008537 +0.008569 +0.008605 +0.008664 +0.008697 +0.008525 +0.008418 +0.008449 +0.008512 +0.008555 +0.008587 +0.008419 +0.008314 +0.008347 +0.008392 +0.008459 +0.008505 +0.008351 +0.008258 +0.008303 +0.008371 +0.008486 +0.008521 +0.008364 +0.008291 +0.008325 +0.008396 +0.008483 +0.008559 +0.008383 +0.008326 +0.008374 +0.008441 +0.008525 +0.008591 +0.008437 +0.008368 +0.008421 +0.008491 +0.008571 +0.008626 +0.008488 +0.008415 +0.008468 +0.008534 +0.008615 +0.008682 +0.008542 +0.008448 +0.008507 +0.008581 +0.00868 +0.008709 +0.008565 +0.00849 +0.008543 +0.008636 +0.008698 +0.008749 +0.008609 +0.008564 +0.008581 +0.008653 +0.00874 +0.008798 +0.008647 +0.008588 +0.008649 +0.008711 +0.008795 +0.008843 +0.008695 +0.00862 +0.008673 +0.008752 +0.008827 +0.008884 +0.008744 +0.008674 +0.008755 +0.008825 +0.008883 +0.008916 +0.008771 +0.0087 +0.008758 +0.008833 +0.008912 +0.008978 +0.008825 +0.00876 +0.008826 +0.008897 +0.008968 +0.009026 +0.008863 +0.008794 +0.008844 +0.008916 +0.009018 +0.009067 +0.008906 +0.008836 +0.008897 +0.008985 +0.009069 +0.009133 +0.008988 +0.008903 +0.008933 +0.009002 +0.009115 +0.009147 +0.008998 +0.008918 +0.008972 +0.009077 +0.009156 +0.009212 +0.009058 +0.008984 +0.009016 +0.0091 +0.009196 +0.009249 +0.009091 +0.009018 +0.009066 +0.009163 +0.009253 +0.00932 +0.009141 +0.009071 +0.009116 +0.009204 +0.009317 +0.009345 +0.009178 +0.009092 +0.009156 +0.00927 +0.009344 +0.009398 +0.009243 +0.009162 +0.009202 +0.009286 +0.009382 +0.009433 +0.009277 +0.00922 +0.009247 +0.009348 +0.009436 +0.009503 +0.00933 +0.009242 +0.009312 +0.009376 +0.009472 +0.009541 +0.009382 +0.009314 +0.009357 +0.009445 +0.00953 +0.009591 +0.009431 +0.009327 +0.009422 +0.009474 +0.00957 +0.00962 +0.009472 +0.009379 +0.009442 +0.009527 +0.009631 +0.009688 +0.009513 +0.009447 +0.009488 +0.009579 +0.00968 +0.00974 +0.009563 +0.009486 +0.009548 +0.009664 +0.009733 +0.009771 +0.009603 +0.009529 +0.009588 +0.009683 +0.009785 +0.009852 +0.009681 +0.009567 +0.00963 +0.009723 +0.009812 +0.009875 +0.009705 +0.009628 +0.009691 +0.009803 +0.00988 +0.009923 +0.009756 +0.009659 +0.009736 +0.009824 +0.009922 +0.010015 +0.009813 +0.009743 +0.009789 +0.009871 +0.009975 +0.010011 +0.009849 +0.009774 +0.009822 +0.009916 +0.010026 +0.010095 +0.009923 +0.009839 +0.00989 +0.009961 +0.01006 +0.010117 +0.009948 +0.009881 +0.009928 +0.010012 +0.010143 +0.010209 +0.010055 +0.009923 +0.009978 +0.010047 +0.010176 +0.010215 +0.010041 +0.009973 +0.010048 +0.010103 +0.010213 +0.010287 +0.010103 +0.010011 +0.010082 +0.010168 +0.010273 +0.010347 +0.010179 +0.010069 +0.010127 +0.010213 +0.010322 +0.010395 +0.010242 +0.010135 +0.010165 +0.010247 +0.010335 +0.010408 +0.010188 +0.010037 +0.010057 +0.010103 +0.010173 +0.010171 +0.009949 +0.00982 +0.009861 +0.009886 +0.009937 +0.009941 +0.009722 +0.009591 +0.009598 +0.009632 +0.009676 +0.009683 +0.009462 +0.009331 +0.009348 +0.009404 +0.009436 +0.009416 +0.009203 +0.009089 +0.009104 +0.009141 +0.009212 +0.009197 +0.009 +0.00887 +0.008885 +0.008935 +0.008981 +0.009 +0.008805 +0.008708 +0.00872 +0.008763 +0.008818 +0.008849 +0.00867 +0.008557 +0.008584 +0.008628 +0.008689 +0.008709 +0.008541 +0.008448 +0.00848 +0.008501 +0.008556 +0.008595 +0.008425 +0.008349 +0.008368 +0.008412 +0.008478 +0.008544 +0.008358 +0.008272 +0.008316 +0.008385 +0.008453 +0.008517 +0.008394 +0.0083 +0.008354 +0.008423 +0.008499 +0.008546 +0.008406 +0.008337 +0.008394 +0.008459 +0.00853 +0.008598 +0.008463 +0.008417 +0.008456 +0.008516 +0.008574 +0.00862 +0.008484 +0.008423 +0.008468 +0.008542 +0.008621 +0.008684 +0.00855 +0.00847 +0.008516 +0.00858 +0.008662 +0.008725 +0.008579 +0.008507 +0.008556 +0.008634 +0.008712 +0.008783 +0.008626 +0.008551 +0.0086 +0.008669 +0.008756 +0.008823 +0.008695 +0.008596 +0.008638 +0.008713 +0.008819 +0.008856 +0.008716 +0.008633 +0.008675 +0.008761 +0.008836 +0.008899 +0.008752 +0.008683 +0.008726 +0.008801 +0.008894 +0.008953 +0.008813 +0.008736 +0.008772 +0.008837 +0.00893 +0.008993 +0.008844 +0.008759 +0.008806 +0.0089 +0.009002 +0.009063 +0.008889 +0.008815 +0.008854 +0.008929 +0.009034 +0.009095 +0.00892 +0.008846 +0.0089 +0.008973 +0.009071 +0.009154 +0.00898 +0.008904 +0.008959 +0.009015 +0.009109 +0.009171 +0.009023 +0.00894 +0.008985 +0.009084 +0.009158 +0.009219 +0.009072 +0.008991 +0.009065 +0.009136 +0.009198 +0.009245 +0.009107 +0.00903 +0.009083 +0.009176 +0.009283 +0.009314 +0.009151 +0.009067 +0.009131 +0.009215 +0.009294 +0.009355 +0.009209 +0.009115 +0.009181 +0.009273 +0.009355 +0.009393 +0.009248 +0.009169 +0.00922 +0.009303 +0.009401 +0.00947 +0.009321 +0.009215 +0.009262 +0.009353 +0.009442 +0.009487 +0.00933 +0.009257 +0.009323 +0.0094 +0.009484 +0.009549 +0.009393 +0.009312 +0.009368 +0.009457 +0.009525 +0.009582 +0.00944 +0.009349 +0.009408 +0.009496 +0.009582 +0.009644 +0.00948 +0.009406 +0.009488 +0.009539 +0.009633 +0.009692 +0.009518 +0.009448 +0.009507 +0.009589 +0.009677 +0.009738 +0.00957 +0.009492 +0.009551 +0.009624 +0.009731 +0.009801 +0.009629 +0.009544 +0.009621 +0.009672 +0.009779 +0.009849 +0.009673 +0.00958 +0.009641 +0.009733 +0.009868 +0.009892 +0.009725 +0.009638 +0.009696 +0.009784 +0.009851 +0.00993 +0.009772 +0.009673 +0.009731 +0.00983 +0.009932 +0.010011 +0.009832 +0.009731 +0.00978 +0.009872 +0.009982 +0.01005 +0.009858 +0.009775 +0.00983 +0.00993 +0.010055 +0.01012 +0.009926 +0.009814 +0.009873 +0.009971 +0.010083 +0.010137 +0.009977 +0.009882 +0.009938 +0.010042 +0.010146 +0.010183 +0.010004 +0.009925 +0.009988 +0.010075 +0.010174 +0.010241 +0.01007 +0.009983 +0.010061 +0.010153 +0.010217 +0.01029 +0.010138 +0.010032 +0.010079 +0.010159 +0.010275 +0.010352 +0.010179 +0.010088 +0.01018 +0.010219 +0.010305 +0.010373 +0.010179 +0.010088 +0.01013 +0.010188 +0.010247 +0.010282 +0.010079 +0.009951 +0.009938 +0.009991 +0.010033 +0.010025 +0.009828 +0.009688 +0.009753 +0.009724 +0.009764 +0.009772 +0.009559 +0.009439 +0.009423 +0.009459 +0.009496 +0.009503 +0.009302 +0.009178 +0.009185 +0.009228 +0.009263 +0.009277 +0.009068 +0.008943 +0.008969 +0.008997 +0.009021 +0.009039 +0.008855 +0.008739 +0.008763 +0.008822 +0.008858 +0.008898 +0.008692 +0.008583 +0.008608 +0.008643 +0.008692 +0.008712 +0.008549 +0.008453 +0.008484 +0.008524 +0.008582 +0.008614 +0.008457 +0.008354 +0.00838 +0.008425 +0.008471 +0.008509 +0.00836 +0.00828 +0.008315 +0.008361 +0.008429 +0.008496 +0.00834 +0.008268 +0.008328 +0.008383 +0.00848 +0.008522 +0.008359 +0.008297 +0.008344 +0.008421 +0.008521 +0.008539 +0.008421 +0.008351 +0.008404 +0.008472 +0.008548 +0.008604 +0.008443 +0.008378 +0.008432 +0.008511 +0.008582 +0.008646 +0.008491 +0.008448 +0.00851 +0.008556 +0.008639 +0.008684 +0.008533 +0.008471 +0.008522 +0.008597 +0.00869 +0.008718 +0.008574 +0.008499 +0.008574 +0.008649 +0.008728 +0.008799 +0.008627 +0.008547 +0.008595 +0.008671 +0.008752 +0.008815 +0.008669 +0.008593 +0.008664 +0.008732 +0.008823 +0.008871 +0.008726 +0.008644 +0.008681 +0.008765 +0.00884 +0.008905 +0.008759 +0.008697 +0.00875 +0.008838 +0.008898 +0.008949 +0.008804 +0.008733 +0.008789 +0.008833 +0.008932 +0.008997 +0.008872 +0.008771 +0.008832 +0.008901 +0.008972 +0.00904 +0.008893 +0.008813 +0.00887 +0.008932 +0.00904 +0.009089 +0.008933 +0.008866 +0.008926 +0.008989 +0.009075 +0.009149 +0.009 +0.008915 +0.008966 +0.009017 +0.009105 +0.009176 +0.009029 +0.008946 +0.009016 +0.00909 +0.009164 +0.009225 +0.00908 +0.008993 +0.009045 +0.009111 +0.009213 +0.009279 +0.009116 +0.009041 +0.009118 +0.009181 +0.00926 +0.00932 +0.00916 +0.009079 +0.009142 +0.009213 +0.009337 +0.009349 +0.009203 +0.00913 +0.009227 +0.009269 +0.009342 +0.00941 +0.009245 +0.009169 +0.009218 +0.009304 +0.009408 +0.009466 +0.009308 +0.009243 +0.00931 +0.009347 +0.009445 +0.009508 +0.009341 +0.009256 +0.009323 +0.009398 +0.009491 +0.009573 +0.009411 +0.009358 +0.009383 +0.00943 +0.009529 +0.009592 +0.009439 +0.009356 +0.009404 +0.009508 +0.009591 +0.009666 +0.009513 +0.00942 +0.009452 +0.009537 +0.009639 +0.009691 +0.009527 +0.009455 +0.009511 +0.009596 +0.009707 +0.009761 +0.009598 +0.009527 +0.00955 +0.009668 +0.009713 +0.009784 +0.009617 +0.00953 +0.009614 +0.009689 +0.009808 +0.009889 +0.009674 +0.009579 +0.009647 +0.009732 +0.009832 +0.009902 +0.009728 +0.009638 +0.009712 +0.009801 +0.009893 +0.009952 +0.00977 +0.009696 +0.009743 +0.009844 +0.009948 +0.01003 +0.009807 +0.009725 +0.009803 +0.009901 +0.009989 +0.010031 +0.009868 +0.009791 +0.009854 +0.009931 +0.010036 +0.010087 +0.009931 +0.009858 +0.009917 +0.009996 +0.010081 +0.010136 +0.009966 +0.009892 +0.009939 +0.010027 +0.01015 +0.010214 +0.010069 +0.009942 +0.009994 +0.010068 +0.010178 +0.010241 +0.010068 +0.009984 +0.010048 +0.010128 +0.010239 +0.010326 +0.010129 +0.010058 +0.010108 +0.010159 +0.010255 +0.010319 +0.010115 +0.010005 +0.010044 +0.010075 +0.010162 +0.010199 +0.009972 +0.009837 +0.009844 +0.009921 +0.00994 +0.009975 +0.009709 +0.009585 +0.009604 +0.009656 +0.009694 +0.009715 +0.009482 +0.009341 +0.009372 +0.009388 +0.009422 +0.00944 +0.009233 +0.009103 +0.009112 +0.009153 +0.009184 +0.009208 +0.009011 +0.008894 +0.008899 +0.00893 +0.008987 +0.009024 +0.008834 +0.008686 +0.008699 +0.008747 +0.008816 +0.008822 +0.008634 +0.00853 +0.008556 +0.008596 +0.008646 +0.008684 +0.008521 +0.008407 +0.008422 +0.008467 +0.008531 +0.008553 +0.008392 +0.008298 +0.008325 +0.008371 +0.008438 +0.008504 +0.008341 +0.00825 +0.008276 +0.008341 +0.008416 +0.008494 +0.008321 +0.008254 +0.008289 +0.008369 +0.008451 +0.008509 +0.008401 +0.008296 +0.008353 +0.008412 +0.008482 +0.008545 +0.008408 +0.008335 +0.008382 +0.008451 +0.008533 +0.008595 +0.008448 +0.008394 +0.008439 +0.008507 +0.008572 +0.008639 +0.008491 +0.008418 +0.008468 +0.008548 +0.00864 +0.008696 +0.008532 +0.008451 +0.00851 +0.008578 +0.008673 +0.008713 +0.008575 +0.008503 +0.008551 +0.008636 +0.008705 +0.008767 +0.008623 +0.008545 +0.008603 +0.008685 +0.008756 +0.00881 +0.008679 +0.008589 +0.008633 +0.008707 +0.008796 +0.008853 +0.008699 +0.008631 +0.008685 +0.008794 +0.008864 +0.008915 +0.008733 +0.008659 +0.008716 +0.008795 +0.008878 +0.008935 +0.008793 +0.008716 +0.008783 +0.008858 +0.00894 +0.009003 +0.008832 +0.008752 +0.008806 +0.00888 +0.00897 +0.00904 +0.008875 +0.008802 +0.008875 +0.008948 +0.009027 +0.009076 +0.008936 +0.008857 +0.008913 +0.00897 +0.009049 +0.009144 +0.00896 +0.008891 +0.008938 +0.009014 +0.009114 +0.009173 +0.009013 +0.008931 +0.008997 +0.009082 +0.009155 +0.009224 +0.009071 +0.008974 +0.009031 +0.009108 +0.009201 +0.009264 +0.009114 +0.00903 +0.009083 +0.00917 +0.009261 +0.009342 +0.009161 +0.009065 +0.009109 +0.009202 +0.009299 +0.009372 +0.009191 +0.00911 +0.009186 +0.009253 +0.009344 +0.009413 +0.009249 +0.009155 +0.009219 +0.009296 +0.009387 +0.009451 +0.009302 +0.009211 +0.00927 +0.009357 +0.009453 +0.009511 +0.009345 +0.00925 +0.009335 +0.009385 +0.009473 +0.009532 +0.009373 +0.009311 +0.009349 +0.009431 +0.009562 +0.009611 +0.009426 +0.009345 +0.009408 +0.009486 +0.009577 +0.009649 +0.009462 +0.009425 +0.009451 +0.009537 +0.009645 +0.009696 +0.009525 +0.00944 +0.009493 +0.009585 +0.009709 +0.009747 +0.009556 +0.009477 +0.009542 +0.009649 +0.009733 +0.009774 +0.009621 +0.00954 +0.009606 +0.009675 +0.009778 +0.009825 +0.009675 +0.009605 +0.009649 +0.009731 +0.009827 +0.009869 +0.009707 +0.009638 +0.009694 +0.009783 +0.009865 +0.009945 +0.00982 +0.009695 +0.009752 +0.009814 +0.009906 +0.009968 +0.009788 +0.009739 +0.00979 +0.009864 +0.009978 +0.010072 +0.009879 +0.009782 +0.009846 +0.009905 +0.010007 +0.010091 +0.009904 +0.009825 +0.009885 +0.009975 +0.010096 +0.010158 +0.009964 +0.009876 +0.009933 +0.010035 +0.010159 +0.010182 +0.010004 +0.009902 +0.009981 +0.010089 +0.010184 +0.010245 +0.010057 +0.009961 +0.010034 +0.010123 +0.010221 +0.010285 +0.01011 +0.010028 +0.0101 +0.0102 +0.010292 +0.010334 +0.010155 +0.010063 +0.010136 +0.010219 +0.010333 +0.010376 +0.010182 +0.010077 +0.010106 +0.010173 +0.010192 +0.010211 +0.010008 +0.009889 +0.009899 +0.009937 +0.009984 +0.010046 +0.009776 +0.009646 +0.009657 +0.009668 +0.009714 +0.00973 +0.009513 +0.009392 +0.009393 +0.009411 +0.009471 +0.009493 +0.009304 +0.009141 +0.009151 +0.009158 +0.009207 +0.009223 +0.009054 +0.008896 +0.008918 +0.00894 +0.00899 +0.009018 +0.008836 +0.008712 +0.008732 +0.008749 +0.008805 +0.008823 +0.008644 +0.008556 +0.008569 +0.008612 +0.008657 +0.008706 +0.008527 +0.008429 +0.008458 +0.008495 +0.00855 +0.008569 +0.008397 +0.008303 +0.008344 +0.008379 +0.008436 +0.008477 +0.008326 +0.008252 +0.008286 +0.008334 +0.008387 +0.00845 +0.008301 +0.008239 +0.008286 +0.008351 +0.008425 +0.008497 +0.008337 +0.008278 +0.00832 +0.008402 +0.008467 +0.008531 +0.008379 +0.008311 +0.008375 +0.008443 +0.00854 +0.008589 +0.008423 +0.008342 +0.0084 +0.008476 +0.008558 +0.008603 +0.008466 +0.008399 +0.008449 +0.008543 +0.008598 +0.008656 +0.008511 +0.008441 +0.008486 +0.008564 +0.008639 +0.008699 +0.008558 +0.008482 +0.008535 +0.008601 +0.008692 +0.008737 +0.008597 +0.008524 +0.008589 +0.008667 +0.008736 +0.008784 +0.008634 +0.008564 +0.008616 +0.008685 +0.008777 +0.008817 +0.008687 +0.008613 +0.008665 +0.008733 +0.008826 +0.008896 +0.008725 +0.008656 +0.008707 +0.008772 +0.008858 +0.008932 +0.008768 +0.008701 +0.008744 +0.008837 +0.008899 +0.008965 +0.008821 +0.00875 +0.008815 +0.008861 +0.008943 +0.009007 +0.008857 +0.008813 +0.008851 +0.008914 +0.008984 +0.009053 +0.008895 +0.008824 +0.008881 +0.008948 +0.009035 +0.009107 +0.008949 +0.008872 +0.00894 +0.009024 +0.00909 +0.009146 +0.008988 +0.008912 +0.008959 +0.009046 +0.00913 +0.009208 +0.009062 +0.008954 +0.009027 +0.009102 +0.009169 +0.009239 +0.009079 +0.009015 +0.009049 +0.009123 +0.009211 +0.009286 +0.009137 +0.009051 +0.009121 +0.009206 +0.009265 +0.009325 +0.009173 +0.00909 +0.009151 +0.009226 +0.009317 +0.009388 +0.009216 +0.009148 +0.009214 +0.009323 +0.009355 +0.009412 +0.009247 +0.009176 +0.009243 +0.009316 +0.009403 +0.009483 +0.009331 +0.009238 +0.009298 +0.009383 +0.009444 +0.009512 +0.009359 +0.009269 +0.009328 +0.009414 +0.009501 +0.009581 +0.009403 +0.009339 +0.009392 +0.009454 +0.009556 +0.009634 +0.009474 +0.009372 +0.009417 +0.009495 +0.009597 +0.009663 +0.009496 +0.00943 +0.009481 +0.009553 +0.009643 +0.009733 +0.009539 +0.009459 +0.009526 +0.009598 +0.009697 +0.009766 +0.00961 +0.009532 +0.009584 +0.009648 +0.009737 +0.009804 +0.009647 +0.009572 +0.009642 +0.009692 +0.009796 +0.009872 +0.009718 +0.009611 +0.009648 +0.009736 +0.009846 +0.009896 +0.009736 +0.009661 +0.009696 +0.00981 +0.009912 +0.009974 +0.009796 +0.009706 +0.009753 +0.009853 +0.009941 +0.010005 +0.009837 +0.009749 +0.009819 +0.009941 +0.010021 +0.010058 +0.009879 +0.009785 +0.009865 +0.009972 +0.010024 +0.010101 +0.009915 +0.009851 +0.00992 +0.010016 +0.010115 +0.010167 +0.009982 +0.009894 +0.009958 +0.010047 +0.010152 +0.010205 +0.010037 +0.009952 +0.010025 +0.010122 +0.010239 +0.010243 +0.010073 +0.009987 +0.010049 +0.010156 +0.010237 +0.010318 +0.010181 +0.010061 +0.010114 +0.010196 +0.010286 +0.010357 +0.010193 +0.010093 +0.010156 +0.010247 +0.010332 +0.010416 +0.010214 +0.010095 +0.010107 +0.010172 +0.01022 +0.010243 +0.010031 +0.009935 +0.009911 +0.009945 +0.01002 +0.010006 +0.009789 +0.009627 +0.009637 +0.009711 +0.009718 +0.009729 +0.009518 +0.009394 +0.009408 +0.009444 +0.009483 +0.009492 +0.009278 +0.009158 +0.009154 +0.00919 +0.009239 +0.00925 +0.009053 +0.008945 +0.008965 +0.009008 +0.009062 +0.009032 +0.008834 +0.008735 +0.008755 +0.008799 +0.008842 +0.008873 +0.008699 +0.008597 +0.008641 +0.008665 +0.008728 +0.00875 +0.008571 +0.008462 +0.008493 +0.00853 +0.008594 +0.00863 +0.008455 +0.008361 +0.008399 +0.008457 +0.008507 +0.008546 +0.008388 +0.008306 +0.008348 +0.008401 +0.008448 +0.008513 +0.008366 +0.008298 +0.00835 +0.008403 +0.008489 +0.008534 +0.008416 +0.008334 +0.00839 +0.008456 +0.008539 +0.008597 +0.00845 +0.008387 +0.00844 +0.008495 +0.008588 +0.008639 +0.008487 +0.008418 +0.008476 +0.008544 +0.008612 +0.008683 +0.008543 +0.008469 +0.008543 +0.008608 +0.008662 +0.008708 +0.008566 +0.008523 +0.008546 +0.00862 +0.008705 +0.00876 +0.008619 +0.008546 +0.008596 +0.008674 +0.008759 +0.00881 +0.008666 +0.008588 +0.008637 +0.008715 +0.008798 +0.008858 +0.008702 +0.008634 +0.008687 +0.008752 +0.008854 +0.008915 +0.008759 +0.008694 +0.008713 +0.008787 +0.008877 +0.008939 +0.008792 +0.008715 +0.008773 +0.008848 +0.008937 +0.008983 +0.008839 +0.008761 +0.008806 +0.00889 +0.008975 +0.009042 +0.008882 +0.008804 +0.008859 +0.00893 +0.00903 +0.009086 +0.008941 +0.00885 +0.008889 +0.00898 +0.009091 +0.009147 +0.008959 +0.008881 +0.008931 +0.009014 +0.009126 +0.009163 +0.009019 +0.008939 +0.008989 +0.00906 +0.009153 +0.009221 +0.009056 +0.00898 +0.009031 +0.009112 +0.009212 +0.009279 +0.009125 +0.009019 +0.009071 +0.00916 +0.009245 +0.009306 +0.009151 +0.009079 +0.009139 +0.009221 +0.009322 +0.009326 +0.009182 +0.009105 +0.009164 +0.009254 +0.009344 +0.009408 +0.009255 +0.009169 +0.00922 +0.009286 +0.009381 +0.00945 +0.009286 +0.009202 +0.009277 +0.009357 +0.009437 +0.0095 +0.009359 +0.00925 +0.009304 +0.009384 +0.009483 +0.009572 +0.009383 +0.009291 +0.009361 +0.009468 +0.009532 +0.009589 +0.009437 +0.009336 +0.009397 +0.009477 +0.009574 +0.009643 +0.009474 +0.009391 +0.009459 +0.009538 +0.009634 +0.009718 +0.009527 +0.009435 +0.009492 +0.009586 +0.009678 +0.009732 +0.009569 +0.009506 +0.009573 +0.00962 +0.009724 +0.009794 +0.009606 +0.00957 +0.00958 +0.009674 +0.009773 +0.009817 +0.009655 +0.009592 +0.009637 +0.00973 +0.009853 +0.009893 +0.009725 +0.009629 +0.009688 +0.009782 +0.009873 +0.00994 +0.009753 +0.009678 +0.009741 +0.009873 +0.009941 +0.009984 +0.009786 +0.009722 +0.009794 +0.009864 +0.009969 +0.010062 +0.009872 +0.009778 +0.009843 +0.009931 +0.010032 +0.010069 +0.009904 +0.00982 +0.00989 +0.009978 +0.010067 +0.010144 +0.009982 +0.009889 +0.009941 +0.010041 +0.010142 +0.010203 +0.009986 +0.009909 +0.009977 +0.010084 +0.010166 +0.010235 +0.010074 +0.009973 +0.010035 +0.010132 +0.010234 +0.010293 +0.010117 +0.01003 +0.010085 +0.010187 +0.01028 +0.010349 +0.010169 +0.010073 +0.010135 +0.010231 +0.010342 +0.010439 +0.010226 +0.010129 +0.010187 +0.010269 +0.010383 +0.010439 +0.010261 +0.010176 +0.010221 +0.010312 +0.010415 +0.010442 +0.010229 +0.010112 +0.010127 +0.01017 +0.010235 +0.010239 +0.010013 +0.009883 +0.009909 +0.009926 +0.009978 +0.009972 +0.009759 +0.009593 +0.009593 +0.009643 +0.009726 +0.00969 +0.009467 +0.009354 +0.009374 +0.009399 +0.009443 +0.009451 +0.009222 +0.009088 +0.009106 +0.009141 +0.009185 +0.009214 +0.009018 +0.008874 +0.00888 +0.008924 +0.008973 +0.008996 +0.0088 +0.008686 +0.008714 +0.008772 +0.008803 +0.008828 +0.008642 +0.008528 +0.008555 +0.008593 +0.008676 +0.008674 +0.008506 +0.008395 +0.008422 +0.00848 +0.008534 +0.00858 +0.008404 +0.008312 +0.008324 +0.008378 +0.008448 +0.008492 +0.008324 +0.008231 +0.008274 +0.008331 +0.008433 +0.008479 +0.008337 +0.008253 +0.008327 +0.008379 +0.008429 +0.008488 +0.008357 +0.008289 +0.008342 +0.008399 +0.008481 +0.008561 +0.008436 +0.008336 +0.008387 +0.00846 +0.008531 +0.008577 +0.008441 +0.008382 +0.00841 +0.008494 +0.008592 +0.008643 +0.008516 +0.008416 +0.00847 +0.008534 +0.008601 +0.008669 +0.008529 +0.008469 +0.00853 +0.008601 +0.008659 +0.008705 +0.008562 +0.008503 +0.008543 +0.008618 +0.008699 +0.008762 +0.008614 +0.008541 +0.008602 +0.008664 +0.008762 +0.008804 +0.008663 +0.008584 +0.008635 +0.008715 +0.008798 +0.008846 +0.008708 +0.008639 +0.008676 +0.00875 +0.008846 +0.008915 +0.008763 +0.008687 +0.008718 +0.008795 +0.008895 +0.008934 +0.00879 +0.00871 +0.008762 +0.008855 +0.008935 +0.008995 +0.008826 +0.008762 +0.008812 +0.008884 +0.008969 +0.009039 +0.008878 +0.008817 +0.00886 +0.008943 +0.009027 +0.009086 +0.008931 +0.008839 +0.008897 +0.008977 +0.009081 +0.009146 +0.008961 +0.008881 +0.008953 +0.009029 +0.009126 +0.009194 +0.009002 +0.008925 +0.008981 +0.009062 +0.009154 +0.009218 +0.009056 +0.008976 +0.009054 +0.009123 +0.009215 +0.009274 +0.00911 +0.009007 +0.009071 +0.009163 +0.009242 +0.009305 +0.009157 +0.009073 +0.009175 +0.009219 +0.009296 +0.009348 +0.009187 +0.009109 +0.009161 +0.009243 +0.009366 +0.0094 +0.009234 +0.009161 +0.009238 +0.009304 +0.009392 +0.009458 +0.009288 +0.009198 +0.009253 +0.009345 +0.009435 +0.009497 +0.009354 +0.00926 +0.009298 +0.009396 +0.009485 +0.009588 +0.009372 +0.00929 +0.009337 +0.009436 +0.009552 +0.009599 +0.009432 +0.009351 +0.009391 +0.00949 +0.009577 +0.009644 +0.009478 +0.009381 +0.009454 +0.009546 +0.009643 +0.009703 +0.009534 +0.009422 +0.009493 +0.009576 +0.00967 +0.009736 +0.009568 +0.009518 +0.009553 +0.009624 +0.009733 +0.009773 +0.009616 +0.009524 +0.009588 +0.009678 +0.009771 +0.009855 +0.009657 +0.009586 +0.009639 +0.009721 +0.009841 +0.009878 +0.00972 +0.009636 +0.009686 +0.00977 +0.009895 +0.009925 +0.009752 +0.009679 +0.009744 +0.009866 +0.009916 +0.009967 +0.009802 +0.009735 +0.009801 +0.009879 +0.009964 +0.01003 +0.009851 +0.009775 +0.009842 +0.009915 +0.01001 +0.010101 +0.009904 +0.009827 +0.009911 +0.009991 +0.010063 +0.010126 +0.009957 +0.009871 +0.009928 +0.010021 +0.01016 +0.010183 +0.010004 +0.009931 +0.00998 +0.010076 +0.010167 +0.010241 +0.010059 +0.009963 +0.010024 +0.010123 +0.010223 +0.01029 +0.010145 +0.010034 +0.010076 +0.010157 +0.010261 +0.01034 +0.010163 +0.010078 +0.010131 +0.010228 +0.010346 +0.010446 +0.010188 +0.010106 +0.010174 +0.010278 +0.010361 +0.010411 +0.01024 +0.010172 +0.010177 +0.010258 +0.010312 +0.010306 +0.010103 +0.009962 +0.009986 +0.010054 +0.010064 +0.010074 +0.009846 +0.009719 +0.009742 +0.009771 +0.009775 +0.009775 +0.009561 +0.00947 +0.009465 +0.009465 +0.009494 +0.009503 +0.009317 +0.009184 +0.009196 +0.009206 +0.009242 +0.009239 +0.009042 +0.008936 +0.00894 +0.008966 +0.009004 +0.009023 +0.008838 +0.008724 +0.008753 +0.00879 +0.008799 +0.008822 +0.008655 +0.00855 +0.00858 +0.008625 +0.008673 +0.008729 +0.008535 +0.008445 +0.008453 +0.008502 +0.008531 +0.008564 +0.0084 +0.008312 +0.008356 +0.008395 +0.008443 +0.008485 +0.008347 +0.008249 +0.008292 +0.008341 +0.008415 +0.008444 +0.008294 +0.00823 +0.008284 +0.008347 +0.008419 +0.008502 +0.008341 +0.008267 +0.008317 +0.008395 +0.008473 +0.008555 +0.008374 +0.008306 +0.008366 +0.008439 +0.00852 +0.008564 +0.00842 +0.00835 +0.008401 +0.008476 +0.008555 +0.008617 +0.008477 +0.008392 +0.008452 +0.008521 +0.0086 +0.008665 +0.008501 +0.008449 +0.008485 +0.008557 +0.008638 +0.008705 +0.008557 +0.00848 +0.008531 +0.008604 +0.008713 +0.008751 +0.008602 +0.008521 +0.00858 +0.008633 +0.008729 +0.008782 +0.008635 +0.008568 +0.00861 +0.008681 +0.008776 +0.008836 +0.008693 +0.008615 +0.008668 +0.008757 +0.008803 +0.008863 +0.008727 +0.008644 +0.0087 +0.008771 +0.008859 +0.008916 +0.008773 +0.008721 +0.008764 +0.008854 +0.00889 +0.008952 +0.008797 +0.008732 +0.008788 +0.008856 +0.008943 +0.00901 +0.008868 +0.008795 +0.008844 +0.008925 +0.008996 +0.009042 +0.008897 +0.008828 +0.00888 +0.008942 +0.009057 +0.009091 +0.008941 +0.008875 +0.008934 +0.009006 +0.009088 +0.00914 +0.008995 +0.008938 +0.008983 +0.009034 +0.009111 +0.009183 +0.009037 +0.008956 +0.00902 +0.009096 +0.00919 +0.009225 +0.009075 +0.009001 +0.009061 +0.009128 +0.009221 +0.009283 +0.009125 +0.009052 +0.009122 +0.009191 +0.009273 +0.009328 +0.009166 +0.009089 +0.009142 +0.00923 +0.009341 +0.00938 +0.009211 +0.009151 +0.009207 +0.009286 +0.00936 +0.009406 +0.009257 +0.009188 +0.009237 +0.00931 +0.009413 +0.009475 +0.009305 +0.009243 +0.009305 +0.009387 +0.009448 +0.009515 +0.009341 +0.009261 +0.00934 +0.009411 +0.009495 +0.009575 +0.009411 +0.009376 +0.009386 +0.009475 +0.009537 +0.009603 +0.009458 +0.009357 +0.009417 +0.009503 +0.009608 +0.00968 +0.009506 +0.009432 +0.009487 +0.009546 +0.009665 +0.009693 +0.009537 +0.009466 +0.00952 +0.009596 +0.009713 +0.009772 +0.009602 +0.009522 +0.009581 +0.009668 +0.009746 +0.009793 +0.009624 +0.009557 +0.009623 +0.009698 +0.009793 +0.009863 +0.009693 +0.009602 +0.009656 +0.009757 +0.009844 +0.009906 +0.009749 +0.009665 +0.009727 +0.009804 +0.009884 +0.009952 +0.009793 +0.009696 +0.009757 +0.009857 +0.009954 +0.010051 +0.009836 +0.009737 +0.009805 +0.009894 +0.009993 +0.01005 +0.009875 +0.009812 +0.009874 +0.009945 +0.010053 +0.010115 +0.009926 +0.009849 +0.00991 +0.009993 +0.010098 +0.010156 +0.009985 +0.009909 +0.009955 +0.010045 +0.010163 +0.010214 +0.010069 +0.009943 +0.010017 +0.010084 +0.010181 +0.010254 +0.01008 +0.010007 +0.010055 +0.010149 +0.010256 +0.01032 +0.010129 +0.010052 +0.010101 +0.010196 +0.01031 +0.010357 +0.010183 +0.010097 +0.010157 +0.010249 +0.010363 +0.010408 +0.010235 +0.010135 +0.010203 +0.010322 +0.010385 +0.010377 +0.010187 +0.010056 +0.010091 +0.010136 +0.010196 +0.010227 +0.00999 +0.009878 +0.009894 +0.00992 +0.009975 +0.009978 +0.009738 +0.009624 +0.009626 +0.009677 +0.009729 +0.00972 +0.009504 +0.009374 +0.00939 +0.009429 +0.009474 +0.009487 +0.009279 +0.009164 +0.009148 +0.009194 +0.009231 +0.009243 +0.009054 +0.008933 +0.008944 +0.008997 +0.00904 +0.009057 +0.008885 +0.008751 +0.008768 +0.008821 +0.008872 +0.008885 +0.008709 +0.008601 +0.008634 +0.008678 +0.008734 +0.008768 +0.008573 +0.008473 +0.008503 +0.008561 +0.008617 +0.008664 +0.008459 +0.008371 +0.008402 +0.008473 +0.008518 +0.008561 +0.008404 +0.008312 +0.008366 +0.008425 +0.008506 +0.008568 +0.00842 +0.008341 +0.008398 +0.008456 +0.008545 +0.008612 +0.008469 +0.008395 +0.008428 +0.008506 +0.008583 +0.008656 +0.008501 +0.008421 +0.008481 +0.008548 +0.008659 +0.008704 +0.008553 +0.008481 +0.008509 +0.008581 +0.00869 +0.008743 +0.008585 +0.008506 +0.008557 +0.008623 +0.008722 +0.008793 +0.008633 +0.008562 +0.008613 +0.00868 +0.008749 +0.008804 +0.008668 +0.008597 +0.008648 +0.008713 +0.008814 +0.008873 +0.008727 +0.008652 +0.008704 +0.008787 +0.008855 +0.008902 +0.008746 +0.008682 +0.00873 +0.008806 +0.008885 +0.00897 +0.008811 +0.008728 +0.008776 +0.008855 +0.008926 +0.009 +0.008846 +0.008779 +0.008819 +0.008908 +0.008977 +0.009043 +0.008889 +0.008815 +0.008875 +0.008943 +0.009023 +0.009096 +0.008944 +0.00888 +0.008929 +0.00899 +0.009077 +0.009119 +0.008971 +0.008903 +0.00895 +0.009034 +0.009131 +0.009183 +0.00904 +0.008944 +0.008996 +0.00909 +0.009171 +0.009225 +0.00906 +0.008979 +0.009055 +0.009133 +0.009215 +0.009267 +0.009125 +0.009032 +0.009084 +0.009187 +0.009278 +0.009336 +0.009154 +0.009102 +0.009134 +0.009215 +0.009303 +0.009351 +0.009204 +0.009126 +0.009174 +0.009268 +0.009373 +0.009407 +0.00925 +0.009168 +0.00924 +0.009315 +0.00939 +0.009458 +0.009292 +0.009214 +0.009266 +0.009381 +0.009449 +0.009497 +0.009354 +0.009278 +0.009358 +0.009389 +0.009487 +0.009537 +0.009415 +0.009315 +0.009363 +0.009455 +0.009549 +0.009589 +0.009429 +0.009366 +0.009409 +0.00949 +0.009588 +0.009636 +0.009499 +0.009408 +0.009468 +0.009547 +0.009638 +0.009695 +0.009522 +0.009448 +0.009507 +0.009597 +0.009717 +0.009754 +0.009589 +0.0095 +0.00956 +0.009651 +0.009721 +0.009795 +0.009618 +0.009527 +0.0096 +0.009693 +0.009784 +0.009861 +0.009687 +0.009607 +0.009657 +0.009724 +0.00983 +0.009881 +0.009717 +0.009646 +0.009705 +0.009774 +0.009906 +0.009972 +0.009814 +0.009687 +0.009747 +0.009808 +0.009918 +0.009989 +0.009815 +0.009737 +0.009841 +0.009865 +0.00997 +0.010046 +0.00986 +0.009783 +0.009847 +0.009927 +0.010034 +0.010104 +0.009923 +0.009848 +0.009884 +0.00998 +0.010079 +0.010142 +0.009986 +0.009916 +0.009977 +0.010028 +0.010116 +0.010194 +0.010035 +0.009915 +0.009989 +0.010086 +0.010177 +0.010254 +0.010071 +0.009978 +0.01004 +0.01014 +0.010243 +0.010305 +0.010117 +0.010026 +0.010102 +0.010191 +0.010282 +0.010344 +0.010182 +0.010096 +0.010179 +0.010241 +0.010352 +0.010407 +0.010201 +0.010118 +0.010187 +0.010288 +0.010377 +0.010426 +0.010258 +0.010149 +0.010215 +0.010269 +0.010299 +0.010325 +0.010107 +0.009969 +0.009994 +0.010041 +0.010067 +0.010092 +0.009877 +0.009755 +0.009758 +0.009791 +0.009834 +0.009797 +0.009574 +0.009452 +0.009501 +0.009493 +0.009511 +0.009528 +0.009325 +0.009203 +0.009212 +0.009244 +0.009274 +0.009285 +0.009081 +0.008974 +0.00899 +0.009034 +0.009047 +0.009066 +0.008874 +0.008769 +0.0088 +0.008833 +0.008863 +0.008892 +0.008713 +0.008634 +0.008656 +0.008696 +0.008723 +0.00875 +0.008582 +0.0085 +0.008547 +0.008568 +0.008619 +0.008643 +0.008483 +0.008391 +0.008433 +0.008478 +0.008535 +0.008548 +0.008403 +0.008307 +0.00835 +0.008407 +0.00846 +0.008503 +0.008356 +0.008297 +0.008339 +0.008402 +0.008476 +0.008556 +0.00841 +0.008338 +0.008362 +0.008425 +0.008512 +0.008575 +0.008428 +0.008354 +0.008408 +0.008487 +0.008584 +0.008643 +0.008476 +0.008391 +0.008452 +0.008518 +0.008599 +0.008667 +0.008512 +0.008436 +0.00851 +0.008574 +0.008663 +0.008725 +0.008562 +0.008498 +0.008524 +0.008602 +0.008691 +0.008752 +0.008626 +0.00857 +0.008563 +0.008637 +0.008727 +0.008799 +0.008651 +0.008576 +0.008627 +0.008691 +0.008772 +0.008835 +0.008682 +0.008615 +0.008672 +0.00873 +0.008822 +0.008887 +0.008743 +0.008668 +0.008715 +0.008777 +0.008864 +0.00893 +0.008772 +0.008704 +0.008751 +0.008833 +0.008931 +0.009003 +0.008807 +0.00874 +0.008804 +0.008861 +0.008955 +0.009009 +0.008852 +0.008788 +0.008846 +0.008916 +0.009002 +0.009086 +0.008901 +0.008829 +0.008881 +0.008963 +0.009048 +0.009106 +0.008957 +0.008874 +0.008928 +0.009013 +0.009101 +0.009164 +0.008989 +0.008922 +0.008984 +0.009072 +0.009132 +0.00919 +0.009031 +0.008969 +0.009039 +0.009088 +0.009182 +0.009263 +0.00909 +0.009002 +0.009059 +0.009145 +0.009227 +0.009298 +0.009137 +0.009052 +0.009107 +0.009184 +0.009281 +0.009363 +0.009182 +0.009104 +0.009155 +0.009237 +0.009332 +0.009391 +0.009253 +0.009144 +0.009195 +0.009276 +0.009368 +0.009457 +0.009261 +0.009185 +0.009255 +0.009336 +0.009423 +0.009479 +0.009316 +0.00924 +0.009289 +0.009381 +0.009474 +0.009558 +0.009358 +0.009279 +0.009353 +0.009427 +0.009516 +0.009578 +0.009408 +0.009337 +0.0094 +0.009493 +0.009568 +0.009617 +0.009446 +0.009377 +0.00944 +0.009511 +0.009612 +0.009672 +0.009511 +0.009422 +0.009503 +0.009579 +0.009668 +0.009718 +0.00955 +0.009473 +0.009528 +0.009644 +0.00969 +0.009746 +0.009609 +0.009525 +0.009595 +0.009688 +0.009786 +0.009853 +0.009644 +0.009552 +0.009607 +0.009698 +0.009799 +0.009856 +0.009702 +0.009639 +0.009694 +0.009769 +0.009876 +0.009896 +0.009736 +0.009663 +0.00971 +0.009802 +0.009902 +0.009981 +0.009821 +0.009721 +0.009783 +0.009861 +0.009944 +0.010016 +0.009872 +0.009764 +0.009811 +0.009901 +0.010028 +0.010083 +0.009892 +0.009809 +0.009876 +0.009954 +0.010041 +0.010116 +0.009938 +0.009864 +0.009917 +0.009998 +0.010128 +0.010185 +0.010016 +0.009902 +0.009956 +0.010056 +0.010153 +0.010218 +0.010049 +0.00998 +0.010032 +0.010119 +0.010212 +0.010274 +0.010107 +0.009998 +0.010057 +0.01016 +0.010253 +0.010319 +0.010146 +0.010064 +0.010135 +0.010231 +0.010319 +0.010374 +0.010189 +0.010101 +0.010163 +0.010264 +0.010369 +0.010417 +0.010264 +0.010168 +0.01024 +0.010268 +0.01033 +0.010327 +0.010128 +0.009994 +0.010031 +0.010109 +0.010125 +0.010137 +0.009901 +0.009752 +0.00978 +0.009807 +0.009852 +0.009858 +0.009644 +0.009519 +0.009516 +0.009544 +0.009573 +0.00957 +0.009376 +0.00923 +0.00926 +0.009302 +0.009349 +0.009375 +0.00914 +0.009018 +0.009028 +0.00906 +0.009116 +0.009113 +0.008921 +0.008811 +0.008854 +0.008866 +0.008915 +0.008946 +0.008747 +0.00864 +0.008667 +0.008711 +0.00876 +0.008771 +0.008607 +0.0085 +0.008535 +0.008577 +0.008646 +0.008663 +0.008497 +0.008397 +0.008435 +0.008497 +0.008541 +0.00856 +0.008371 +0.008287 +0.008328 +0.008396 +0.008444 +0.008485 +0.008338 +0.008254 +0.008298 +0.008374 +0.008441 +0.0085 +0.008351 +0.008286 +0.008341 +0.008397 +0.008494 +0.008538 +0.008407 +0.008322 +0.008368 +0.008443 +0.008531 +0.008583 +0.008435 +0.008372 +0.008416 +0.008512 +0.008575 +0.008633 +0.008476 +0.008416 +0.008451 +0.008523 +0.008604 +0.00867 +0.008525 +0.008452 +0.008515 +0.008569 +0.008669 +0.008722 +0.008565 +0.00848 +0.008542 +0.008614 +0.00869 +0.008756 +0.008601 +0.008527 +0.008581 +0.008664 +0.008749 +0.008805 +0.008662 +0.008586 +0.008642 +0.008707 +0.008773 +0.008829 +0.008688 +0.008616 +0.008671 +0.008733 +0.008828 +0.00891 +0.008743 +0.00867 +0.008719 +0.008785 +0.008864 +0.008925 +0.008799 +0.008705 +0.00875 +0.008819 +0.008919 +0.008988 +0.008827 +0.008758 +0.008797 +0.008867 +0.008963 +0.009027 +0.008882 +0.008812 +0.008831 +0.008912 +0.008992 +0.009062 +0.008922 +0.008831 +0.008891 +0.00897 +0.009047 +0.009112 +0.008958 +0.008887 +0.008932 +0.009021 +0.009092 +0.009162 +0.00901 +0.008938 +0.008976 +0.009043 +0.009132 +0.009205 +0.009049 +0.008968 +0.009027 +0.009123 +0.009212 +0.009259 +0.009085 +0.009003 +0.009057 +0.009137 +0.009228 +0.009291 +0.00914 +0.009076 +0.009135 +0.009201 +0.009275 +0.009338 +0.009179 +0.009104 +0.009155 +0.009229 +0.009318 +0.009397 +0.009235 +0.009159 +0.009219 +0.009304 +0.009364 +0.009433 +0.009278 +0.009205 +0.009264 +0.009317 +0.009434 +0.009457 +0.009316 +0.009244 +0.009298 +0.009394 +0.009469 +0.00953 +0.009364 +0.009293 +0.00934 +0.009433 +0.009507 +0.009578 +0.009412 +0.009333 +0.009403 +0.009471 +0.009563 +0.009639 +0.009461 +0.009376 +0.009442 +0.009539 +0.009637 +0.009671 +0.009497 +0.00942 +0.009485 +0.009565 +0.009659 +0.009726 +0.009584 +0.009483 +0.00954 +0.009607 +0.009709 +0.009772 +0.009621 +0.009521 +0.009568 +0.009656 +0.009762 +0.009831 +0.009668 +0.009592 +0.009627 +0.009712 +0.009794 +0.009873 +0.009737 +0.009613 +0.009659 +0.009755 +0.009857 +0.009945 +0.009767 +0.00967 +0.009732 +0.0098 +0.009901 +0.009975 +0.009791 +0.009707 +0.009774 +0.009876 +0.009974 +0.010032 +0.009858 +0.009753 +0.009815 +0.009912 +0.009999 +0.010073 +0.009911 +0.009842 +0.009885 +0.009968 +0.010059 +0.010109 +0.009939 +0.009854 +0.009911 +0.01003 +0.010115 +0.010161 +0.009998 +0.009917 +0.00999 +0.010071 +0.010175 +0.010208 +0.010046 +0.009963 +0.010009 +0.010107 +0.01021 +0.010273 +0.010098 +0.010029 +0.010118 +0.010187 +0.010258 +0.010298 +0.01013 +0.010062 +0.010108 +0.010212 +0.010324 +0.01038 +0.010215 +0.010128 +0.010151 +0.010231 +0.01034 +0.010379 +0.010181 +0.010066 +0.010084 +0.010144 +0.010223 +0.010254 +0.010023 +0.009866 +0.009894 +0.009921 +0.009975 +0.010012 +0.009793 +0.009611 +0.009613 +0.009676 +0.009734 +0.00972 +0.00948 +0.009365 +0.009372 +0.009408 +0.009444 +0.009465 +0.009248 +0.009117 +0.009137 +0.009176 +0.009218 +0.009232 +0.009015 +0.008901 +0.008909 +0.00896 +0.008986 +0.009013 +0.008835 +0.008721 +0.008764 +0.008806 +0.00884 +0.008856 +0.00867 +0.00857 +0.008603 +0.008638 +0.008695 +0.008737 +0.00856 +0.008452 +0.008481 +0.008545 +0.008624 +0.008623 +0.008457 +0.00835 +0.008385 +0.008434 +0.008505 +0.008542 +0.008386 +0.008294 +0.008347 +0.008398 +0.008487 +0.008547 +0.008407 +0.008331 +0.008374 +0.008431 +0.008495 +0.008568 +0.008422 +0.008368 +0.008395 +0.00847 +0.008556 +0.008632 +0.008479 +0.008405 +0.008457 +0.008515 +0.008594 +0.008657 +0.00853 +0.008429 +0.008491 +0.008551 +0.008645 +0.00872 +0.008566 +0.00849 +0.008542 +0.008596 +0.0087 +0.008737 +0.008598 +0.008545 +0.008566 +0.008642 +0.008719 +0.00878 +0.008647 +0.008577 +0.008631 +0.00871 +0.008783 +0.008826 +0.008669 +0.008606 +0.008656 +0.008726 +0.008812 +0.008883 +0.008717 +0.008667 +0.008718 +0.008789 +0.008865 +0.008928 +0.008772 +0.008695 +0.008741 +0.008816 +0.00891 +0.008985 +0.008826 +0.008733 +0.008791 +0.008873 +0.008954 +0.009027 +0.008852 +0.00877 +0.008839 +0.00893 +0.008991 +0.009046 +0.008904 +0.008813 +0.008884 +0.008973 +0.009047 +0.009107 +0.008952 +0.008866 +0.008914 +0.008996 +0.009093 +0.009138 +0.008983 +0.008904 +0.008985 +0.009089 +0.009166 +0.009185 +0.009036 +0.008959 +0.008997 +0.009093 +0.009165 +0.009237 +0.009095 +0.009018 +0.009047 +0.009157 +0.009219 +0.009281 +0.009124 +0.009051 +0.009084 +0.009181 +0.00928 +0.009334 +0.009186 +0.00911 +0.009145 +0.009225 +0.009319 +0.00939 +0.00923 +0.009166 +0.00918 +0.009268 +0.009365 +0.009421 +0.009287 +0.009185 +0.009237 +0.009326 +0.009414 +0.00947 +0.009311 +0.009232 +0.009299 +0.009367 +0.009463 +0.009507 +0.009372 +0.009288 +0.00933 +0.009418 +0.009507 +0.009568 +0.009401 +0.00931 +0.009387 +0.009483 +0.009569 +0.009617 +0.009453 +0.009363 +0.009435 +0.009532 +0.009599 +0.009659 +0.009502 +0.009406 +0.009465 +0.009561 +0.009644 +0.009728 +0.009556 +0.009482 +0.009514 +0.009607 +0.0097 +0.009758 +0.009588 +0.009517 +0.009562 +0.009651 +0.009771 +0.009844 +0.009653 +0.009551 +0.009608 +0.009696 +0.009798 +0.009862 +0.009685 +0.009617 +0.009675 +0.009753 +0.009851 +0.009902 +0.009731 +0.009653 +0.009725 +0.009796 +0.009893 +0.009971 +0.009784 +0.009701 +0.009763 +0.009864 +0.009939 +0.010008 +0.009831 +0.009785 +0.009842 +0.009894 +0.009982 +0.010056 +0.009898 +0.009793 +0.009875 +0.00995 +0.010047 +0.010103 +0.009937 +0.00986 +0.009906 +0.010004 +0.010097 +0.010163 +0.009989 +0.009916 +0.009958 +0.010048 +0.010151 +0.010213 +0.010036 +0.009957 +0.010039 +0.010102 +0.010196 +0.010257 +0.010103 +0.010026 +0.010043 +0.010145 +0.010254 +0.010308 +0.010134 +0.01005 +0.01013 +0.010195 +0.010318 +0.010389 +0.010179 +0.010099 +0.010139 +0.01022 +0.010342 +0.010364 +0.010148 +0.010036 +0.010046 +0.010107 +0.010171 +0.010194 +0.009919 +0.009786 +0.009801 +0.009816 +0.009866 +0.009857 +0.009642 +0.009525 +0.009513 +0.009543 +0.009582 +0.009587 +0.009353 +0.009221 +0.009235 +0.009266 +0.009298 +0.009322 +0.009117 +0.008994 +0.009007 +0.009035 +0.009061 +0.009072 +0.008887 +0.008774 +0.008796 +0.008803 +0.008863 +0.008867 +0.008694 +0.008596 +0.008592 +0.008634 +0.00869 +0.008716 +0.008541 +0.00844 +0.008467 +0.008511 +0.008577 +0.008597 +0.00843 +0.008322 +0.00835 +0.008399 +0.008461 +0.008493 +0.008319 +0.008225 +0.008254 +0.0083 +0.008368 +0.008419 +0.008251 +0.008174 +0.008182 +0.008243 +0.008321 +0.008363 +0.008221 +0.008152 +0.008186 +0.00829 +0.008341 +0.008396 +0.008274 +0.008198 +0.00825 +0.008309 +0.008378 +0.008437 +0.008306 +0.008231 +0.008281 +0.008351 +0.008429 +0.008485 +0.008344 +0.008284 +0.008336 +0.008407 +0.008468 +0.008526 +0.008384 +0.008336 +0.00839 +0.008431 +0.008508 +0.008557 +0.00842 +0.008357 +0.008414 +0.008483 +0.00857 +0.00864 +0.008452 +0.00839 +0.008452 +0.008513 +0.008591 +0.008657 +0.008512 +0.008436 +0.008494 +0.008579 +0.008654 +0.008704 +0.008546 +0.008486 +0.008525 +0.008607 +0.008684 +0.008749 +0.00862 +0.00854 +0.008589 +0.008643 +0.008732 +0.008791 +0.008649 +0.008557 +0.008611 +0.008689 +0.008772 +0.008822 +0.008688 +0.008611 +0.008664 +0.008745 +0.008828 +0.008889 +0.008732 +0.00865 +0.008704 +0.008769 +0.008864 +0.00892 +0.008775 +0.008692 +0.008739 +0.008846 +0.008921 +0.009004 +0.008822 +0.00874 +0.008797 +0.008867 +0.008939 +0.008996 +0.008851 +0.008782 +0.008848 +0.008899 +0.00899 +0.009056 +0.008901 +0.008828 +0.008876 +0.008957 +0.009041 +0.009104 +0.008955 +0.008871 +0.008934 +0.008992 +0.009094 +0.009144 +0.00899 +0.008913 +0.008973 +0.00908 +0.009134 +0.009191 +0.009036 +0.008946 +0.009032 +0.009117 +0.009163 +0.009232 +0.00908 +0.008998 +0.009062 +0.00914 +0.009224 +0.009288 +0.009133 +0.009055 +0.009111 +0.009178 +0.009275 +0.009321 +0.009176 +0.009102 +0.009156 +0.009227 +0.009322 +0.009389 +0.009251 +0.00915 +0.009198 +0.009286 +0.009347 +0.009421 +0.009262 +0.009182 +0.00927 +0.009324 +0.009407 +0.009468 +0.009315 +0.009232 +0.009289 +0.00938 +0.009448 +0.009522 +0.009361 +0.009286 +0.009334 +0.009405 +0.009521 +0.009564 +0.009409 +0.009342 +0.009401 +0.009507 +0.00955 +0.009602 +0.009441 +0.00936 +0.009429 +0.009501 +0.009597 +0.009673 +0.009506 +0.009436 +0.009487 +0.009552 +0.009649 +0.00971 +0.009547 +0.009461 +0.009514 +0.009617 +0.009702 +0.009765 +0.009619 +0.009522 +0.009561 +0.00965 +0.009755 +0.00984 +0.009636 +0.009551 +0.009627 +0.009697 +0.009822 +0.009864 +0.009695 +0.009621 +0.009651 +0.009743 +0.009843 +0.009909 +0.009735 +0.009652 +0.009714 +0.009822 +0.009908 +0.009975 +0.009788 +0.009695 +0.00977 +0.009834 +0.009944 +0.010023 +0.00985 +0.009774 +0.009817 +0.009918 +0.009998 +0.010089 +0.009865 +0.00978 +0.009852 +0.009943 +0.010042 +0.010114 +0.009953 +0.009838 +0.009909 +0.010005 +0.010085 +0.01016 +0.00999 +0.009905 +0.009962 +0.010056 +0.010157 +0.010218 +0.010022 +0.009938 +0.010012 +0.010046 +0.010119 +0.010134 +0.009932 +0.009802 +0.009871 +0.009909 +0.009954 +0.009948 +0.00973 +0.009598 +0.009625 +0.009663 +0.009695 +0.009714 +0.009488 +0.009352 +0.009379 +0.009439 +0.009447 +0.009446 +0.009243 +0.009117 +0.009136 +0.009179 +0.009211 +0.009246 +0.009034 +0.008892 +0.008917 +0.008961 +0.008995 +0.009014 +0.008819 +0.00873 +0.008738 +0.008776 +0.00882 +0.008845 +0.008669 +0.008557 +0.008591 +0.008659 +0.008696 +0.008708 +0.008533 +0.008443 +0.008467 +0.008524 +0.008575 +0.008605 +0.008434 +0.008328 +0.008384 +0.008437 +0.008515 +0.008508 +0.008333 +0.008247 +0.008299 +0.008361 +0.008434 +0.008467 +0.008317 +0.008248 +0.008308 +0.008406 +0.008458 +0.008515 +0.008364 +0.008284 +0.008332 +0.008412 +0.008485 +0.008545 +0.008408 +0.008327 +0.008383 +0.008461 +0.008551 +0.008609 +0.008454 +0.008367 +0.008427 +0.0085 +0.008603 +0.008641 +0.008497 +0.008423 +0.008449 +0.008531 +0.008609 +0.008679 +0.008532 +0.008466 +0.008509 +0.008579 +0.00867 +0.00873 +0.008559 +0.008503 +0.008559 +0.008624 +0.008709 +0.008774 +0.008623 +0.008542 +0.008597 +0.008668 +0.008756 +0.008816 +0.008653 +0.008596 +0.008642 +0.00874 +0.008806 +0.008846 +0.008712 +0.008647 +0.008676 +0.008745 +0.008827 +0.008892 +0.008752 +0.00866 +0.008722 +0.008799 +0.008885 +0.008952 +0.008796 +0.008728 +0.008764 +0.008838 +0.00893 +0.008988 +0.008836 +0.008757 +0.008823 +0.008894 +0.008975 +0.009033 +0.008882 +0.008832 +0.008858 +0.008927 +0.009005 +0.009065 +0.008924 +0.008852 +0.008922 +0.008975 +0.00907 +0.009118 +0.008969 +0.008898 +0.008937 +0.009013 +0.009104 +0.00918 +0.009012 +0.008934 +0.008991 +0.009076 +0.009154 +0.009225 +0.009061 +0.008989 +0.009022 +0.009113 +0.009204 +0.009292 +0.009102 +0.009022 +0.009069 +0.009164 +0.009255 +0.009313 +0.009162 +0.009087 +0.00912 +0.0092 +0.009282 +0.009355 +0.009188 +0.009109 +0.00918 +0.009246 +0.009346 +0.009414 +0.00926 +0.009163 +0.009207 +0.0093 +0.009385 +0.009443 +0.009292 +0.009206 +0.009294 +0.00938 +0.009437 +0.009491 +0.009331 +0.009256 +0.009295 +0.00938 +0.009479 +0.009538 +0.009376 +0.009312 +0.009376 +0.009436 +0.009522 +0.009598 +0.009419 +0.009345 +0.009406 +0.009479 +0.009588 +0.009642 +0.00948 +0.009414 +0.009442 +0.00954 +0.009645 +0.009711 +0.009521 +0.009442 +0.009493 +0.009571 +0.009669 +0.009736 +0.009573 +0.009504 +0.009534 +0.009621 +0.009731 +0.009784 +0.009618 +0.009539 +0.009599 +0.009678 +0.009777 +0.009844 +0.009686 +0.009577 +0.00965 +0.009724 +0.009816 +0.00988 +0.009734 +0.009651 +0.009691 +0.009766 +0.009867 +0.009973 +0.00976 +0.009669 +0.009732 +0.009817 +0.009916 +0.009985 +0.00981 +0.009732 +0.009795 +0.00988 +0.009988 +0.010032 +0.009868 +0.009778 +0.009829 +0.009932 +0.010025 +0.010082 +0.009916 +0.009834 +0.009922 +0.009986 +0.010073 +0.010113 +0.009955 +0.009876 +0.009934 +0.010046 +0.010114 +0.010182 +0.010016 +0.009943 +0.009987 +0.010063 +0.010179 +0.010232 +0.01006 +0.00999 +0.010027 +0.010116 +0.010239 +0.010313 +0.010125 +0.010041 +0.010097 +0.010203 +0.010258 +0.01032 +0.010153 +0.010073 +0.010141 +0.010217 +0.010316 +0.01041 +0.01017 +0.010049 +0.010081 +0.010132 +0.010192 +0.010206 +0.009997 +0.009872 +0.009887 +0.009936 +0.00998 +0.009964 +0.009745 +0.009597 +0.009612 +0.009639 +0.009684 +0.00974 +0.00947 +0.009338 +0.009357 +0.00939 +0.009435 +0.009428 +0.009213 +0.009094 +0.009107 +0.009147 +0.009171 +0.009196 +0.008987 +0.008877 +0.008905 +0.008934 +0.008978 +0.008989 +0.008798 +0.008694 +0.008704 +0.008743 +0.008793 +0.008823 +0.008646 +0.008558 +0.008593 +0.008656 +0.008681 +0.008713 +0.008533 +0.008427 +0.00846 +0.008503 +0.008562 +0.008601 +0.008444 +0.008338 +0.008377 +0.008433 +0.008504 +0.008533 +0.008377 +0.008294 +0.008321 +0.008376 +0.008447 +0.008519 +0.008371 +0.0083 +0.008338 +0.008422 +0.008497 +0.008559 +0.008416 +0.008347 +0.008414 +0.008464 +0.008528 +0.008581 +0.008445 +0.00839 +0.008433 +0.008486 +0.008571 +0.008632 +0.008494 +0.008413 +0.008471 +0.008546 +0.008621 +0.008683 +0.008536 +0.008476 +0.008529 +0.008602 +0.008653 +0.008721 +0.008577 +0.008501 +0.008559 +0.00862 +0.00871 +0.008769 +0.008624 +0.008564 +0.008614 +0.008673 +0.008759 +0.008795 +0.008653 +0.008596 +0.008642 +0.00872 +0.008782 +0.00885 +0.008702 +0.008631 +0.00871 +0.008768 +0.008848 +0.00891 +0.008767 +0.008668 +0.008724 +0.008802 +0.008886 +0.008946 +0.008791 +0.00872 +0.008776 +0.008853 +0.008941 +0.009001 +0.008872 +0.008759 +0.008805 +0.008877 +0.008973 +0.009036 +0.008882 +0.008798 +0.008862 +0.008973 +0.00904 +0.009079 +0.008931 +0.008853 +0.008894 +0.008976 +0.009074 +0.009115 +0.008968 +0.008895 +0.008939 +0.009038 +0.009125 +0.009187 +0.009024 +0.008939 +0.00899 +0.009077 +0.009191 +0.009225 +0.009052 +0.008966 +0.009023 +0.009135 +0.00921 +0.009261 +0.009118 +0.009038 +0.009071 +0.009161 +0.009253 +0.009312 +0.009146 +0.009076 +0.009126 +0.009221 +0.009309 +0.009371 +0.0092 +0.009123 +0.009171 +0.009251 +0.009343 +0.009407 +0.009252 +0.009189 +0.00923 +0.009308 +0.009394 +0.009471 +0.00929 +0.009197 +0.009257 +0.009342 +0.009443 +0.009495 +0.009339 +0.009258 +0.009318 +0.009411 +0.009499 +0.009564 +0.009383 +0.009289 +0.009361 +0.009438 +0.009535 +0.009602 +0.009433 +0.009345 +0.009425 +0.009515 +0.009622 +0.009634 +0.009467 +0.009379 +0.009449 +0.009569 +0.009623 +0.009688 +0.009538 +0.009438 +0.009487 +0.009585 +0.009678 +0.009743 +0.009574 +0.009499 +0.009548 +0.009638 +0.009736 +0.009814 +0.009633 +0.009544 +0.009581 +0.009682 +0.009785 +0.009859 +0.0097 +0.009573 +0.009636 +0.009723 +0.009834 +0.009909 +0.009719 +0.009648 +0.009699 +0.009773 +0.009876 +0.009946 +0.009762 +0.009685 +0.009743 +0.009843 +0.009938 +0.009997 +0.0098 +0.009733 +0.009793 +0.009892 +0.009983 +0.010031 +0.009874 +0.00982 +0.00986 +0.009937 +0.010024 +0.010067 +0.009918 +0.009826 +0.009889 +0.009982 +0.010064 +0.010144 +0.009972 +0.009893 +0.009964 +0.010043 +0.010127 +0.01019 +0.010012 +0.009937 +0.009993 +0.010068 +0.010188 +0.010242 +0.010077 +0.010007 +0.010084 +0.01014 +0.010223 +0.010288 +0.010112 +0.010008 +0.010084 +0.010188 +0.010283 +0.010359 +0.010191 +0.010078 +0.010137 +0.010235 +0.010336 +0.010395 +0.010218 +0.010126 +0.0102 +0.010288 +0.01042 +0.010454 +0.010239 +0.01015 +0.010212 +0.010287 +0.010356 +0.010404 +0.010148 +0.010028 +0.010035 +0.010111 +0.010145 +0.01013 +0.009907 +0.009784 +0.009796 +0.009832 +0.009871 +0.00986 +0.009651 +0.009518 +0.009525 +0.00956 +0.009571 +0.00958 +0.009366 +0.00925 +0.009254 +0.009294 +0.00934 +0.009339 +0.009139 +0.009016 +0.009039 +0.009088 +0.009111 +0.009113 +0.008884 +0.008786 +0.008813 +0.008863 +0.008897 +0.008919 +0.008749 +0.008639 +0.008666 +0.008721 +0.00875 +0.008765 +0.008591 +0.008497 +0.008534 +0.008578 +0.008624 +0.008663 +0.008485 +0.008397 +0.008441 +0.008491 +0.008532 +0.008547 +0.008395 +0.008311 +0.008378 +0.00843 +0.008482 +0.008528 +0.008377 +0.00831 +0.008366 +0.008437 +0.008503 +0.008577 +0.008428 +0.008349 +0.008401 +0.008473 +0.008557 +0.008635 +0.008465 +0.008397 +0.00844 +0.008517 +0.008599 +0.008666 +0.008504 +0.008435 +0.008496 +0.008558 +0.008644 +0.008701 +0.008572 +0.008491 +0.008552 +0.008597 +0.008679 +0.008754 +0.008588 +0.008517 +0.008574 +0.008641 +0.00873 +0.008801 +0.008647 +0.008563 +0.008607 +0.008684 +0.00877 +0.008827 +0.008697 +0.00862 +0.008645 +0.00873 +0.008812 +0.008883 +0.008739 +0.008668 +0.008698 +0.008771 +0.008854 +0.008925 +0.008787 +0.008706 +0.008738 +0.008802 +0.008899 +0.008967 +0.008833 +0.008739 +0.008789 +0.00888 +0.008936 +0.008994 +0.008846 +0.008791 +0.008824 +0.008904 +0.009003 +0.009057 +0.00892 +0.00884 +0.008894 +0.008945 +0.009036 +0.00911 +0.008939 +0.008866 +0.008918 +0.008995 +0.009108 +0.009179 +0.008992 +0.008919 +0.008972 +0.009035 +0.009134 +0.009184 +0.009027 +0.008957 +0.009002 +0.009089 +0.009174 +0.009247 +0.009098 +0.009017 +0.009075 +0.009127 +0.00922 +0.009277 +0.009124 +0.00904 +0.009101 +0.009185 +0.009263 +0.009334 +0.009196 +0.009114 +0.009193 +0.009207 +0.009311 +0.009381 +0.009204 +0.009135 +0.009181 +0.009265 +0.009366 +0.009428 +0.009275 +0.009202 +0.009263 +0.009308 +0.009403 +0.009476 +0.009311 +0.009221 +0.009282 +0.00936 +0.00946 +0.009539 +0.009377 +0.009287 +0.00933 +0.009419 +0.009511 +0.009593 +0.009391 +0.009309 +0.009368 +0.009467 +0.009574 +0.009617 +0.009454 +0.009389 +0.009413 +0.009504 +0.009605 +0.00966 +0.009498 +0.009417 +0.00947 +0.00955 +0.009653 +0.009739 +0.009559 +0.009466 +0.00953 +0.009595 +0.009703 +0.00976 +0.009598 +0.009536 +0.009563 +0.009649 +0.009754 +0.009823 +0.009651 +0.00955 +0.009629 +0.009698 +0.009784 +0.009847 +0.009688 +0.009595 +0.009657 +0.009771 +0.009872 +0.009926 +0.009746 +0.009662 +0.009692 +0.0098 +0.0099 +0.009956 +0.009778 +0.009703 +0.009793 +0.009895 +0.009948 +0.009999 +0.009838 +0.00973 +0.009808 +0.009889 +0.009986 +0.010081 +0.009869 +0.009807 +0.009882 +0.009956 +0.010052 +0.01012 +0.009914 +0.009841 +0.00991 +0.00999 +0.010093 +0.010165 +0.009988 +0.009914 +0.009972 +0.010073 +0.010193 +0.010202 +0.010007 +0.009931 +0.010002 +0.010093 +0.010188 +0.010276 +0.010103 +0.010008 +0.010072 +0.010148 +0.010233 +0.010306 +0.010139 +0.010042 +0.010106 +0.0102 +0.010316 +0.010388 +0.010205 +0.010102 +0.010149 +0.010259 +0.010374 +0.010405 +0.0102 +0.010112 +0.010185 +0.010246 +0.0103 +0.010306 +0.010086 +0.009967 +0.010009 +0.010031 +0.010081 +0.010096 +0.009899 +0.009758 +0.009787 +0.009792 +0.009833 +0.009851 +0.009628 +0.0095 +0.009526 +0.009551 +0.009601 +0.009599 +0.009382 +0.009287 +0.009269 +0.009288 +0.009323 +0.009331 +0.009133 +0.009 +0.009015 +0.009069 +0.009115 +0.009109 +0.008911 +0.008806 +0.008817 +0.008851 +0.008893 +0.008915 +0.008721 +0.008624 +0.008672 +0.008704 +0.008738 +0.008754 +0.008589 +0.008481 +0.008523 +0.008563 +0.008608 +0.008651 +0.008477 +0.008369 +0.008403 +0.008467 +0.0085 +0.008541 +0.008387 +0.008294 +0.008332 +0.008397 +0.008448 +0.008494 +0.008355 +0.008281 +0.008335 +0.008408 +0.008496 +0.00854 +0.008393 +0.008315 +0.008372 +0.008446 +0.008517 +0.008577 +0.008436 +0.008361 +0.008415 +0.00849 +0.008578 +0.008662 +0.008469 +0.008398 +0.008442 +0.008525 +0.008606 +0.008668 +0.008514 +0.008444 +0.008516 +0.008578 +0.008667 +0.008703 +0.008565 +0.008489 +0.008534 +0.008614 +0.008683 +0.008748 +0.008603 +0.008535 +0.008581 +0.008656 +0.008729 +0.008803 +0.008657 +0.008581 +0.008637 +0.008716 +0.008802 +0.00886 +0.008674 +0.008605 +0.008654 +0.008737 +0.008819 +0.008869 +0.008736 +0.008677 +0.008712 +0.00879 +0.008874 +0.008918 +0.008773 +0.008698 +0.008755 +0.008824 +0.00891 +0.008965 +0.008829 +0.008761 +0.008804 +0.008885 +0.008958 +0.009004 +0.008859 +0.008797 +0.008858 +0.008934 +0.008994 +0.009058 +0.008921 +0.008819 +0.008883 +0.008953 +0.009041 +0.009114 +0.008945 +0.008873 +0.00893 +0.009004 +0.009098 +0.009158 +0.009009 +0.00893 +0.008968 +0.009048 +0.009149 +0.009202 +0.009037 +0.008967 +0.009011 +0.009098 +0.009187 +0.009253 +0.009121 +0.009015 +0.009064 +0.009135 +0.00923 +0.0093 +0.009142 +0.009045 +0.009102 +0.009191 +0.009273 +0.00934 +0.009192 +0.009097 +0.009157 +0.009247 +0.009329 +0.009387 +0.009221 +0.009153 +0.009204 +0.009276 +0.009377 +0.009435 +0.009272 +0.009198 +0.009254 +0.009365 +0.009431 +0.009476 +0.009301 +0.009233 +0.009291 +0.009374 +0.009483 +0.00956 +0.009356 +0.009278 +0.009341 +0.009434 +0.009508 +0.009561 +0.009409 +0.009332 +0.009401 +0.009485 +0.009575 +0.009641 +0.009447 +0.009385 +0.009438 +0.009517 +0.009618 +0.009679 +0.009538 +0.009424 +0.009479 +0.009573 +0.009657 +0.009729 +0.009534 +0.009465 +0.009535 +0.009621 +0.009702 +0.009764 +0.009606 +0.009537 +0.00959 +0.009674 +0.009765 +0.009803 +0.009647 +0.009568 +0.009616 +0.009717 +0.009795 +0.009873 +0.009724 +0.009643 +0.009719 +0.009761 +0.009864 +0.00988 +0.009735 +0.009657 +0.00971 +0.009805 +0.009897 +0.009983 +0.009809 +0.009731 +0.009776 +0.00985 +0.009958 +0.010006 +0.009842 +0.009766 +0.00982 +0.0099 +0.010027 +0.010078 +0.009903 +0.009815 +0.009885 +0.009986 +0.010047 +0.010103 +0.009935 +0.009884 +0.009908 +0.009993 +0.010098 +0.010173 +0.009987 +0.009904 +0.009981 +0.010054 +0.010165 +0.01023 +0.010048 +0.009952 +0.01002 +0.010116 +0.010212 +0.01028 +0.010087 +0.010013 +0.01007 +0.010169 +0.010302 +0.010327 +0.010128 +0.010049 +0.010118 +0.010216 +0.01034 +0.010358 +0.010194 +0.010104 +0.010166 +0.010265 +0.010364 +0.010429 +0.010241 +0.010131 +0.010204 +0.010295 +0.010369 +0.010397 +0.010182 +0.010045 +0.010064 +0.010128 +0.010174 +0.010196 +0.009943 +0.009784 +0.009815 +0.009842 +0.009887 +0.009888 +0.009673 +0.009545 +0.009533 +0.009573 +0.00959 +0.009596 +0.009376 +0.009243 +0.009257 +0.009284 +0.009322 +0.009344 +0.009112 +0.008991 +0.009011 +0.009042 +0.009059 +0.009068 +0.008877 +0.00876 +0.008803 +0.008811 +0.00885 +0.008877 +0.008705 +0.008601 +0.008615 +0.008668 +0.008704 +0.008737 +0.008571 +0.008448 +0.008484 +0.008532 +0.008582 +0.008609 +0.008454 +0.008349 +0.00838 +0.008433 +0.008502 +0.0085 +0.008335 +0.008247 +0.008285 +0.008344 +0.008412 +0.008456 +0.008302 +0.008247 +0.008281 +0.008349 +0.008431 +0.008467 +0.008317 +0.00826 +0.008297 +0.008372 +0.008449 +0.008508 +0.008364 +0.008297 +0.008365 +0.008435 +0.008507 +0.008563 +0.008415 +0.008333 +0.008385 +0.008458 +0.008549 +0.008593 +0.008457 +0.008386 +0.008437 +0.008523 +0.00859 +0.008651 +0.008506 +0.008453 +0.008461 +0.008536 +0.008614 +0.008682 +0.008563 +0.008456 +0.008507 +0.008586 +0.00868 +0.00873 +0.008591 +0.008517 +0.008566 +0.008621 +0.008711 +0.008775 +0.008621 +0.008538 +0.008599 +0.008684 +0.008754 +0.008834 +0.008685 +0.008606 +0.008657 +0.008708 +0.008801 +0.008868 +0.008732 +0.00863 +0.008682 +0.008751 +0.008839 +0.008917 +0.008768 +0.008706 +0.008733 +0.008814 +0.008873 +0.008939 +0.008801 +0.008714 +0.008777 +0.008849 +0.008933 +0.009 +0.008856 +0.008791 +0.008834 +0.008904 +0.008969 +0.009035 +0.008887 +0.0088 +0.008864 +0.008944 +0.009045 +0.009083 +0.008937 +0.008857 +0.008914 +0.008997 +0.009064 +0.009124 +0.008973 +0.008926 +0.008938 +0.00902 +0.009112 +0.009173 +0.009028 +0.008953 +0.009014 +0.009077 +0.009157 +0.00923 +0.009067 +0.008988 +0.009039 +0.009123 +0.009206 +0.009265 +0.009125 +0.009055 +0.009123 +0.009199 +0.009245 +0.009292 +0.009149 +0.009081 +0.009124 +0.009203 +0.009298 +0.009358 +0.009209 +0.009139 +0.009189 +0.009265 +0.009345 +0.009413 +0.009246 +0.009175 +0.00923 +0.009309 +0.009384 +0.009456 +0.009312 +0.009225 +0.009278 +0.009371 +0.009458 +0.009522 +0.009329 +0.009252 +0.009313 +0.009392 +0.00948 +0.009545 +0.009403 +0.009322 +0.009372 +0.009444 +0.009537 +0.009607 +0.009438 +0.009352 +0.009405 +0.009481 +0.009584 +0.009666 +0.009486 +0.009413 +0.009478 +0.009533 +0.009628 +0.009698 +0.00953 +0.009482 +0.009493 +0.009579 +0.009687 +0.009739 +0.009606 +0.009494 +0.009538 +0.009644 +0.009724 +0.009795 +0.00963 +0.009556 +0.009601 +0.009682 +0.009789 +0.009844 +0.009675 +0.009612 +0.009642 +0.009734 +0.009832 +0.009891 +0.009739 +0.00965 +0.009701 +0.009817 +0.009869 +0.009933 +0.009762 +0.00968 +0.009755 +0.009841 +0.009964 +0.009997 +0.009818 +0.009728 +0.009798 +0.009877 +0.009974 +0.010049 +0.009856 +0.009787 +0.009867 +0.009944 +0.010043 +0.010106 +0.00991 +0.00983 +0.00989 +0.009989 +0.010135 +0.010125 +0.00996 +0.00988 +0.009943 +0.010052 +0.010133 +0.010187 +0.010026 +0.009958 +0.009979 +0.010091 +0.010169 +0.010249 +0.01008 +0.009979 +0.010062 +0.010149 +0.010244 +0.01029 +0.010123 +0.010027 +0.010092 +0.010185 +0.010301 +0.01039 +0.01016 +0.010121 +0.010122 +0.010226 +0.010328 +0.010399 +0.010222 +0.010112 +0.010177 +0.010266 +0.010358 +0.010394 +0.010181 +0.010032 +0.01006 +0.010103 +0.010178 +0.010168 +0.009936 +0.009811 +0.009829 +0.009853 +0.009915 +0.009927 +0.009685 +0.009522 +0.009536 +0.009577 +0.009661 +0.00961 +0.009397 +0.009269 +0.009291 +0.009315 +0.009375 +0.009372 +0.00915 +0.009022 +0.009041 +0.009084 +0.009128 +0.009123 +0.008942 +0.008812 +0.008821 +0.008869 +0.008929 +0.008935 +0.00874 +0.008634 +0.008665 +0.008727 +0.008744 +0.008771 +0.008583 +0.008487 +0.008518 +0.008566 +0.008641 +0.008649 +0.008465 +0.008355 +0.008388 +0.008441 +0.008499 +0.008523 +0.008352 +0.008256 +0.008294 +0.008352 +0.00841 +0.00846 +0.008313 +0.0082 +0.008249 +0.008322 +0.00839 +0.008452 +0.008307 +0.008245 +0.0083 +0.008364 +0.008425 +0.008482 +0.008351 +0.00829 +0.008328 +0.008392 +0.008473 +0.008533 +0.008414 +0.008311 +0.008359 +0.008435 +0.008517 +0.008574 +0.008435 +0.008368 +0.008428 +0.008476 +0.008557 +0.008628 +0.008487 +0.008402 +0.008448 +0.008517 +0.008599 +0.008658 +0.008522 +0.008446 +0.008525 +0.008596 +0.008649 +0.008684 +0.008547 +0.008484 +0.008532 +0.008601 +0.008681 +0.008753 +0.0086 +0.008534 +0.008595 +0.008669 +0.008735 +0.008784 +0.008645 +0.00857 +0.00862 +0.008689 +0.008786 +0.008835 +0.008676 +0.008605 +0.00868 +0.008742 +0.008832 +0.008884 +0.008737 +0.008679 +0.008703 +0.008771 +0.00887 +0.008924 +0.008773 +0.008691 +0.008755 +0.008835 +0.008927 +0.008972 +0.008808 +0.008746 +0.008784 +0.008865 +0.008952 +0.009023 +0.008854 +0.00878 +0.008843 +0.008944 +0.009013 +0.009069 +0.008904 +0.008827 +0.008875 +0.008958 +0.009047 +0.009134 +0.008938 +0.008869 +0.008926 +0.009013 +0.009107 +0.009176 +0.008975 +0.008911 +0.00897 +0.00904 +0.009136 +0.009193 +0.009032 +0.008964 +0.009025 +0.009118 +0.009201 +0.009251 +0.009088 +0.008995 +0.009053 +0.009127 +0.009229 +0.00929 +0.009128 +0.009065 +0.009133 +0.009223 +0.009283 +0.009331 +0.009162 +0.009088 +0.009146 +0.009229 +0.009335 +0.009387 +0.00921 +0.009139 +0.009206 +0.009296 +0.009383 +0.009446 +0.00926 +0.009189 +0.009236 +0.009324 +0.009408 +0.009474 +0.009315 +0.009237 +0.009298 +0.00939 +0.009486 +0.009558 +0.009366 +0.009269 +0.009321 +0.009412 +0.009505 +0.009569 +0.009404 +0.009328 +0.009404 +0.00951 +0.00956 +0.009616 +0.009439 +0.009373 +0.009435 +0.009515 +0.009609 +0.009662 +0.00952 +0.009422 +0.009483 +0.009585 +0.00965 +0.009707 +0.009554 +0.009479 +0.009558 +0.009603 +0.009724 +0.009746 +0.009591 +0.009531 +0.009579 +0.009667 +0.009768 +0.009794 +0.009638 +0.009568 +0.009627 +0.009711 +0.009798 +0.009865 +0.009695 +0.00961 +0.009682 +0.009748 +0.009846 +0.009928 +0.009742 +0.009664 +0.009734 +0.009846 +0.009916 +0.009951 +0.009778 +0.009702 +0.009789 +0.00984 +0.009939 +0.010016 +0.00984 +0.009759 +0.009842 +0.009903 +0.010005 +0.010065 +0.009893 +0.009811 +0.009847 +0.009966 +0.010046 +0.010115 +0.009955 +0.009866 +0.00991 +0.010011 +0.010107 +0.010202 +0.009977 +0.009888 +0.009949 +0.010055 +0.010157 +0.010251 +0.01005 +0.009942 +0.010007 +0.010097 +0.010208 +0.010269 +0.010079 +0.010015 +0.010073 +0.010149 +0.010276 +0.010334 +0.010119 +0.010036 +0.010087 +0.010167 +0.010265 +0.0103 +0.010072 +0.009904 +0.00995 +0.010013 +0.010052 +0.010051 +0.009824 +0.009671 +0.009729 +0.00972 +0.009761 +0.009786 +0.009546 +0.009413 +0.009435 +0.009485 +0.009493 +0.009503 +0.009295 +0.009162 +0.009172 +0.009215 +0.009249 +0.009251 +0.009053 +0.008935 +0.008977 +0.009026 +0.009015 +0.009024 +0.008835 +0.00872 +0.008754 +0.00879 +0.008836 +0.008866 +0.008671 +0.008562 +0.008603 +0.008656 +0.008693 +0.008708 +0.008531 +0.00843 +0.008458 +0.008512 +0.008571 +0.008586 +0.008415 +0.008309 +0.008352 +0.008409 +0.008463 +0.008475 +0.008313 +0.008233 +0.008277 +0.008316 +0.008395 +0.008416 +0.00826 +0.008192 +0.008229 +0.008311 +0.008392 +0.008457 +0.008295 +0.008233 +0.008273 +0.008355 +0.008423 +0.008479 +0.008345 +0.008267 +0.008322 +0.00839 +0.008481 +0.00853 +0.008394 +0.008308 +0.008366 +0.008419 +0.008498 +0.008569 +0.008434 +0.008374 +0.008422 +0.008472 +0.008556 +0.008622 +0.008472 +0.008429 +0.008432 +0.008507 +0.008587 +0.008652 +0.008503 +0.008438 +0.008486 +0.008561 +0.008641 +0.008702 +0.008558 +0.008482 +0.008537 +0.008602 +0.008683 +0.008736 +0.008591 +0.00853 +0.008568 +0.008644 +0.008733 +0.008794 +0.008662 +0.008583 +0.008619 +0.008696 +0.008759 +0.008823 +0.00868 +0.008597 +0.008656 +0.008727 +0.008817 +0.008881 +0.008723 +0.008673 +0.008714 +0.008781 +0.00885 +0.008909 +0.008789 +0.008688 +0.008748 +0.008819 +0.008894 +0.008969 +0.008818 +0.008742 +0.008787 +0.008872 +0.008966 +0.009026 +0.008849 +0.008774 +0.008825 +0.008906 +0.008996 +0.009061 +0.008916 +0.008834 +0.008888 +0.008956 +0.009037 +0.009087 +0.008941 +0.008862 +0.008918 +0.009006 +0.009081 +0.009146 +0.009034 +0.008931 +0.008968 +0.009043 +0.009137 +0.009168 +0.009023 +0.008957 +0.009013 +0.00912 +0.009188 +0.009226 +0.009083 +0.009012 +0.009068 +0.009133 +0.009221 +0.009266 +0.009127 +0.009037 +0.009101 +0.00918 +0.00927 +0.009326 +0.009187 +0.009103 +0.009153 +0.009239 +0.009326 +0.009352 +0.009213 +0.009136 +0.009188 +0.009267 +0.009362 +0.009432 +0.009316 +0.009197 +0.009243 +0.009318 +0.009423 +0.009457 +0.009291 +0.009219 +0.009282 +0.009371 +0.009448 +0.009526 +0.009376 +0.009301 +0.009334 +0.009426 +0.009492 +0.009555 +0.009405 +0.009315 +0.009375 +0.009465 +0.009557 +0.009613 +0.009448 +0.009375 +0.009439 +0.009536 +0.00959 +0.00966 +0.009496 +0.009417 +0.009478 +0.009555 +0.009647 +0.009725 +0.009547 +0.009461 +0.009528 +0.009594 +0.009699 +0.009773 +0.009624 +0.009507 +0.00957 +0.009647 +0.009742 +0.009808 +0.009648 +0.009562 +0.009609 +0.009707 +0.009804 +0.009893 +0.009687 +0.0096 +0.009657 +0.009748 +0.009852 +0.009909 +0.009737 +0.009663 +0.009712 +0.009796 +0.009907 +0.009954 +0.009791 +0.009716 +0.009758 +0.009867 +0.009962 +0.010012 +0.009826 +0.009744 +0.009811 +0.009893 +0.010001 +0.010069 +0.009933 +0.009806 +0.009843 +0.009938 +0.010042 +0.010113 +0.009925 +0.009857 +0.009917 +0.010006 +0.010095 +0.01017 +0.009974 +0.009896 +0.009965 +0.010044 +0.010145 +0.010227 +0.010035 +0.009948 +0.01001 +0.010103 +0.010198 +0.010262 +0.01009 +0.010025 +0.01006 +0.010158 +0.010243 +0.010307 +0.010134 +0.010049 +0.01011 +0.010168 +0.010258 +0.010288 +0.010092 +0.009949 +0.009976 +0.010022 +0.010102 +0.010088 +0.009883 +0.009747 +0.009765 +0.009799 +0.009839 +0.009848 +0.00962 +0.009492 +0.009502 +0.009582 +0.00957 +0.009574 +0.00936 +0.009229 +0.009252 +0.009279 +0.009317 +0.009337 +0.009128 +0.008998 +0.009005 +0.00907 +0.009099 +0.00912 +0.008915 +0.008785 +0.008812 +0.008851 +0.008897 +0.008914 +0.008728 +0.008622 +0.008639 +0.008693 +0.008737 +0.008772 +0.008594 +0.008495 +0.008504 +0.008552 +0.008605 +0.008636 +0.008469 +0.008363 +0.008392 +0.008442 +0.008514 +0.008533 +0.008361 +0.008267 +0.008306 +0.008356 +0.008428 +0.00847 +0.008332 +0.008229 +0.00827 +0.008352 +0.008416 +0.008489 +0.008337 +0.008275 +0.008308 +0.008381 +0.008464 +0.008523 +0.008397 +0.008317 +0.008355 +0.008427 +0.008503 +0.008563 +0.008434 +0.008343 +0.008393 +0.008472 +0.008549 +0.008608 +0.008465 +0.008387 +0.008436 +0.008514 +0.008588 +0.008662 +0.008516 +0.008455 +0.008509 +0.008543 +0.008621 +0.008683 +0.008547 +0.008472 +0.008514 +0.008594 +0.008686 +0.008749 +0.008618 +0.008514 +0.008571 +0.008625 +0.008715 +0.008787 +0.008633 +0.008559 +0.008605 +0.008678 +0.008761 +0.008826 +0.008681 +0.0086 +0.008658 +0.008721 +0.008813 +0.008871 +0.008725 +0.008647 +0.008696 +0.008762 +0.008852 +0.008919 +0.008758 +0.00869 +0.008742 +0.00883 +0.008921 +0.008969 +0.0088 +0.008723 +0.008777 +0.008857 +0.008941 +0.009005 +0.008847 +0.008775 +0.008828 +0.00891 +0.008987 +0.009066 +0.008899 +0.008812 +0.008871 +0.008944 +0.009029 +0.009098 +0.008945 +0.008861 +0.008915 +0.009001 +0.009084 +0.009134 +0.008982 +0.008911 +0.008983 +0.00905 +0.009119 +0.009171 +0.009022 +0.00897 +0.00903 +0.00909 +0.009172 +0.009237 +0.00907 +0.008979 +0.009046 +0.009131 +0.009219 +0.009278 +0.009128 +0.009031 +0.009113 +0.009184 +0.009264 +0.00933 +0.009165 +0.009073 +0.009136 +0.00922 +0.009322 +0.009385 +0.009233 +0.00912 +0.009189 +0.009273 +0.009358 +0.00942 +0.009256 +0.009184 +0.009226 +0.00931 +0.009395 +0.009466 +0.009301 +0.009212 +0.009291 +0.009371 +0.009464 +0.009518 +0.009349 +0.009249 +0.009323 +0.009408 +0.009498 +0.009559 +0.009394 +0.009318 +0.009391 +0.009469 +0.009533 +0.009593 +0.009425 +0.009368 +0.00943 +0.0095 +0.009598 +0.009677 +0.009493 +0.009401 +0.009467 +0.009536 +0.009641 +0.009711 +0.009535 +0.009463 +0.009513 +0.009606 +0.009695 +0.009747 +0.009591 +0.009498 +0.009562 +0.009658 +0.009758 +0.009824 +0.009627 +0.009544 +0.009601 +0.009689 +0.009806 +0.009835 +0.009683 +0.009605 +0.009655 +0.009751 +0.009858 +0.009893 +0.009725 +0.009653 +0.009704 +0.009806 +0.009884 +0.009947 +0.009779 +0.009705 +0.009758 +0.009834 +0.009933 +0.010002 +0.009863 +0.009764 +0.009822 +0.009882 +0.01 +0.010039 +0.009867 +0.009784 +0.009853 +0.009929 +0.010042 +0.010124 +0.009937 +0.009856 +0.009909 +0.009987 +0.010078 +0.010143 +0.009974 +0.009888 +0.009966 +0.010035 +0.01014 +0.010201 +0.010033 +0.00997 +0.010009 +0.01008 +0.010179 +0.010248 +0.01009 +0.010031 +0.010028 +0.010142 +0.01023 +0.010301 +0.010135 +0.010026 +0.010104 +0.010201 +0.010289 +0.010368 +0.010168 +0.01005 +0.010107 +0.01018 +0.010235 +0.010257 +0.010041 +0.009916 +0.009951 +0.010021 +0.010097 +0.010047 +0.009823 +0.009686 +0.009716 +0.009745 +0.009788 +0.00978 +0.00956 +0.009441 +0.009456 +0.009505 +0.009533 +0.009521 +0.009316 +0.009211 +0.009195 +0.009236 +0.009269 +0.009265 +0.009073 +0.008973 +0.008989 +0.009025 +0.009064 +0.009071 +0.008888 +0.008757 +0.008784 +0.008821 +0.008865 +0.008886 +0.008702 +0.008609 +0.008653 +0.008725 +0.008733 +0.008749 +0.00856 +0.008477 +0.008506 +0.008555 +0.008603 +0.008629 +0.008447 +0.008362 +0.008411 +0.008452 +0.008503 +0.008531 +0.00837 +0.008263 +0.008312 +0.008381 +0.008436 +0.008499 +0.008343 +0.008257 +0.008301 +0.008372 +0.008451 +0.008521 +0.008379 +0.008296 +0.008331 +0.008413 +0.008491 +0.008553 +0.008411 +0.008331 +0.00838 +0.008461 +0.008553 +0.008611 +0.008457 +0.00838 +0.008442 +0.008488 +0.008583 +0.008639 +0.008488 +0.008421 +0.008469 +0.008552 +0.008631 +0.008714 +0.00856 +0.008457 +0.008527 +0.008595 +0.008654 +0.00871 +0.008566 +0.0085 +0.00856 +0.008631 +0.008703 +0.008767 +0.008632 +0.008556 +0.008608 +0.00869 +0.008751 +0.008804 +0.008656 +0.008591 +0.008639 +0.008713 +0.008798 +0.008862 +0.008711 +0.008649 +0.008699 +0.008779 +0.008871 +0.008878 +0.008739 +0.008666 +0.008721 +0.008821 +0.00888 +0.008941 +0.008794 +0.008736 +0.00878 +0.008852 +0.008948 +0.008987 +0.00883 +0.008759 +0.008824 +0.008887 +0.008973 +0.009037 +0.008882 +0.008812 +0.008873 +0.008951 +0.009028 +0.00908 +0.008931 +0.008861 +0.008925 +0.008976 +0.009057 +0.009117 +0.008983 +0.00891 +0.008951 +0.009031 +0.009151 +0.009169 +0.008997 +0.008934 +0.008997 +0.009062 +0.009167 +0.009228 +0.009065 +0.008981 +0.009032 +0.009122 +0.009199 +0.009264 +0.009107 +0.009034 +0.009089 +0.009167 +0.00927 +0.009347 +0.009157 +0.009062 +0.009119 +0.009206 +0.009297 +0.00936 +0.009191 +0.009117 +0.009195 +0.009287 +0.009354 +0.009415 +0.009246 +0.009152 +0.009214 +0.009313 +0.009388 +0.009453 +0.009301 +0.009215 +0.009275 +0.009363 +0.009444 +0.009511 +0.00934 +0.00925 +0.00934 +0.009408 +0.009492 +0.009545 +0.009374 +0.009301 +0.009368 +0.009454 +0.00954 +0.009605 +0.009422 +0.00934 +0.009421 +0.009485 +0.009583 +0.009658 +0.009476 +0.009407 +0.009469 +0.009553 +0.009647 +0.009701 +0.009526 +0.009445 +0.009497 +0.009591 +0.009689 +0.009765 +0.009565 +0.009499 +0.009553 +0.009676 +0.009739 +0.009769 +0.009617 +0.009542 +0.00959 +0.009681 +0.009782 +0.009828 +0.00968 +0.009614 +0.009666 +0.009745 +0.009839 +0.009876 +0.009715 +0.009631 +0.009696 +0.009787 +0.00986 +0.009962 +0.009812 +0.009719 +0.009743 +0.009824 +0.009907 +0.009985 +0.009816 +0.009742 +0.009788 +0.009874 +0.009981 +0.010061 +0.00988 +0.009787 +0.00985 +0.009932 +0.010016 +0.010082 +0.009915 +0.009828 +0.009885 +0.01 +0.010098 +0.010154 +0.009984 +0.009906 +0.009953 +0.010009 +0.010122 +0.010177 +0.010017 +0.00992 +0.009983 +0.010085 +0.010203 +0.010238 +0.010068 +0.009975 +0.010031 +0.010132 +0.010253 +0.010293 +0.010121 +0.010049 +0.01008 +0.01018 +0.010298 +0.010329 +0.010145 +0.01005 +0.010138 +0.010202 +0.010213 +0.010228 +0.010026 +0.009897 +0.009932 +0.009961 +0.010005 +0.010032 +0.009818 +0.009672 +0.009705 +0.009728 +0.009778 +0.009803 +0.009566 +0.009462 +0.009476 +0.009501 +0.009537 +0.009541 +0.009343 +0.009211 +0.009232 +0.009281 +0.009314 +0.009352 +0.009116 +0.009023 +0.009017 +0.009046 +0.009095 +0.009088 +0.008907 +0.0088 +0.008828 +0.008871 +0.00891 +0.008936 +0.008747 +0.008656 +0.008689 +0.008721 +0.008763 +0.008787 +0.008622 +0.008516 +0.008555 +0.008598 +0.008659 +0.008678 +0.008503 +0.008421 +0.008459 +0.00852 +0.008564 +0.008571 +0.008407 +0.008333 +0.00839 +0.008453 +0.008496 +0.008547 +0.008399 +0.008331 +0.008397 +0.008473 +0.008538 +0.008592 +0.008449 +0.00837 +0.008431 +0.008482 +0.008577 +0.008631 +0.008497 +0.008416 +0.008469 +0.008555 +0.008636 +0.008677 +0.008527 +0.00846 +0.00851 +0.008607 +0.008659 +0.008714 +0.00856 +0.008501 +0.008568 +0.00864 +0.008763 +0.008746 +0.00861 +0.008535 +0.008586 +0.008658 +0.008745 +0.008805 +0.008659 +0.00861 +0.008653 +0.008699 +0.00879 +0.008854 +0.008702 +0.008629 +0.008682 +0.008753 +0.008836 +0.008897 +0.008755 +0.008698 +0.008758 +0.008801 +0.008865 +0.008934 +0.008785 +0.008716 +0.008764 +0.008834 +0.008926 +0.009014 +0.008841 +0.008769 +0.008824 +0.00889 +0.008961 +0.009035 +0.008878 +0.008801 +0.00885 +0.008937 +0.009012 +0.009074 +0.00893 +0.008859 +0.008906 +0.008984 +0.009066 +0.009125 +0.008989 +0.008905 +0.008925 +0.00901 +0.009091 +0.009176 +0.009014 +0.008947 +0.009004 +0.00907 +0.009146 +0.009209 +0.009058 +0.008974 +0.009025 +0.009123 +0.009187 +0.009259 +0.009114 +0.009048 +0.009085 +0.009167 +0.009249 +0.009308 +0.009138 +0.009067 +0.009119 +0.009229 +0.009296 +0.009346 +0.009197 +0.00914 +0.009175 +0.00925 +0.009323 +0.009389 +0.009236 +0.009156 +0.009208 +0.009302 +0.009379 +0.009447 +0.009317 +0.009213 +0.009266 +0.009349 +0.009429 +0.009484 +0.009324 +0.009253 +0.009316 +0.009384 +0.009494 +0.009561 +0.009421 +0.009308 +0.009343 +0.009427 +0.009509 +0.00959 +0.00943 +0.009336 +0.009415 +0.009482 +0.009567 +0.009627 +0.009469 +0.00939 +0.009447 +0.009543 +0.009621 +0.009687 +0.009533 +0.009445 +0.009513 +0.009579 +0.009672 +0.009733 +0.009574 +0.009487 +0.009569 +0.009634 +0.009717 +0.009778 +0.009612 +0.009562 +0.009587 +0.009671 +0.009795 +0.009824 +0.009663 +0.009577 +0.009638 +0.009723 +0.009825 +0.0099 +0.009722 +0.009645 +0.009689 +0.009764 +0.009868 +0.009938 +0.009759 +0.009668 +0.009743 +0.009836 +0.009975 +0.009983 +0.009812 +0.00971 +0.009778 +0.009876 +0.009973 +0.010026 +0.009863 +0.009788 +0.009831 +0.009939 +0.01003 +0.010085 +0.009914 +0.009812 +0.009882 +0.009969 +0.01008 +0.010128 +0.009963 +0.009878 +0.009949 +0.010036 +0.010138 +0.010205 +0.010001 +0.009918 +0.00997 +0.010067 +0.010168 +0.010249 +0.010072 +0.009992 +0.010045 +0.010112 +0.010213 +0.010279 +0.010103 +0.010041 +0.010085 +0.010171 +0.010284 +0.010342 +0.010159 +0.010104 +0.010117 +0.010222 +0.010334 +0.010408 +0.010244 +0.010128 +0.010167 +0.010268 +0.010414 +0.010422 +0.010259 +0.010172 +0.010239 +0.010327 +0.010441 +0.010498 +0.0103 +0.010205 +0.010256 +0.010334 +0.010395 +0.010426 +0.010182 +0.010052 +0.010096 +0.010135 +0.01019 +0.010204 +0.009985 +0.00984 +0.009824 +0.009851 +0.009903 +0.009895 +0.009686 +0.00957 +0.009561 +0.00961 +0.009659 +0.009656 +0.009432 +0.009308 +0.009322 +0.009358 +0.009395 +0.009388 +0.009177 +0.009062 +0.009078 +0.009136 +0.00917 +0.009177 +0.00898 +0.008859 +0.008872 +0.008916 +0.008962 +0.008996 +0.008792 +0.008675 +0.008698 +0.008755 +0.008811 +0.008832 +0.008662 +0.00854 +0.008573 +0.008611 +0.00866 +0.008681 +0.00852 +0.008414 +0.008456 +0.008499 +0.008574 +0.0086 +0.008415 +0.008332 +0.008367 +0.008417 +0.008476 +0.008522 +0.008359 +0.008285 +0.008339 +0.008422 +0.008496 +0.008564 +0.008406 +0.008331 +0.008371 +0.008446 +0.008527 +0.008573 +0.008427 +0.008366 +0.008437 +0.008483 +0.008568 +0.008624 +0.008503 +0.00842 +0.008464 +0.008544 +0.00862 +0.008659 +0.008518 +0.008451 +0.008499 +0.008568 +0.008658 +0.008714 +0.008581 +0.008501 +0.008556 +0.008628 +0.008731 +0.008783 +0.008583 +0.00852 +0.008577 +0.008652 +0.008735 +0.008795 +0.008641 +0.008591 +0.008634 +0.00871 +0.008793 +0.008857 +0.008685 +0.008614 +0.008678 +0.008738 +0.008823 +0.008889 +0.008752 +0.008676 +0.008726 +0.008793 +0.008868 +0.008926 +0.008779 +0.008709 +0.008779 +0.00885 +0.008907 +0.008974 +0.008844 +0.008758 +0.008813 +0.00887 +0.008947 +0.009017 +0.008864 +0.008792 +0.008847 +0.008936 +0.009004 +0.009068 +0.00892 +0.008853 +0.0089 +0.008973 +0.00905 +0.009102 +0.008952 +0.008882 +0.008938 +0.009014 +0.009097 +0.009168 +0.009025 +0.008959 +0.008981 +0.009059 +0.009133 +0.009192 +0.009065 +0.008957 +0.00902 +0.009109 +0.009181 +0.009248 +0.009097 +0.009042 +0.009075 +0.00916 +0.009243 +0.009284 +0.009134 +0.009062 +0.009113 +0.009189 +0.009277 +0.009355 +0.009185 +0.009126 +0.009181 +0.009258 +0.009356 +0.009374 +0.009221 +0.009145 +0.00919 +0.009293 +0.009359 +0.009448 +0.009285 +0.009211 +0.009268 +0.009348 +0.009427 +0.009479 +0.009313 +0.009242 +0.009288 +0.009404 +0.009466 +0.00953 +0.009373 +0.009301 +0.009362 +0.009424 +0.009508 +0.009582 +0.009431 +0.009357 +0.009382 +0.009456 +0.009563 +0.009626 +0.009482 +0.009396 +0.009445 +0.009521 +0.009601 +0.00968 +0.009512 +0.009438 +0.009477 +0.009553 +0.009659 +0.009731 +0.009577 +0.009509 +0.009539 +0.009626 +0.009706 +0.00976 +0.009601 +0.009526 +0.009589 +0.009695 +0.009746 +0.009851 +0.009647 +0.009576 +0.009637 +0.009705 +0.0098 +0.00987 +0.009703 +0.009617 +0.009691 +0.009763 +0.009845 +0.009926 +0.009745 +0.009665 +0.009727 +0.009809 +0.009907 +0.009976 +0.009805 +0.00972 +0.009777 +0.009873 +0.009985 +0.010034 +0.009843 +0.009751 +0.009836 +0.00991 +0.009995 +0.010076 +0.009919 +0.009812 +0.009873 +0.009958 +0.010074 +0.01012 +0.009933 +0.009855 +0.009918 +0.010011 +0.010133 +0.010189 +0.00999 +0.009919 +0.009972 +0.010055 +0.010166 +0.010245 +0.010057 +0.009951 +0.010013 +0.010129 +0.010237 +0.010286 +0.010086 +0.010009 +0.010064 +0.010157 +0.01026 +0.01034 +0.010145 +0.010067 +0.010151 +0.010223 +0.010303 +0.010369 +0.010194 +0.010112 +0.01017 +0.010255 +0.010375 +0.010473 +0.010289 +0.010146 +0.010198 +0.01026 +0.010349 +0.010381 +0.010151 +0.010023 +0.010084 +0.010116 +0.010163 +0.010188 +0.009953 +0.009798 +0.009821 +0.009855 +0.009904 +0.009959 +0.009693 +0.009565 +0.009577 +0.009615 +0.00966 +0.009668 +0.009444 +0.009339 +0.009359 +0.009357 +0.009391 +0.009417 +0.009209 +0.009062 +0.009079 +0.009124 +0.009169 +0.009163 +0.008962 +0.008838 +0.008859 +0.0089 +0.008943 +0.00897 +0.00877 +0.008647 +0.008681 +0.008722 +0.008777 +0.008797 +0.008621 +0.008515 +0.00854 +0.008604 +0.00866 +0.008709 +0.008508 +0.008399 +0.008424 +0.008482 +0.008524 +0.008554 +0.008379 +0.008291 +0.008334 +0.008391 +0.008456 +0.008506 +0.008349 +0.008272 +0.008308 +0.008365 +0.008454 +0.008501 +0.008359 +0.008286 +0.008339 +0.0084 +0.008499 +0.008571 +0.008416 +0.008342 +0.008392 +0.008465 +0.008543 +0.008605 +0.008439 +0.008365 +0.008416 +0.008502 +0.008579 +0.008628 +0.008485 +0.008418 +0.008477 +0.008541 +0.008616 +0.008671 +0.008545 +0.008475 +0.008506 +0.008612 +0.008663 +0.00871 +0.008567 +0.008502 +0.008545 +0.00862 +0.008705 +0.008769 +0.008615 +0.008548 +0.008603 +0.008688 +0.008786 +0.008811 +0.00865 +0.008582 +0.00863 +0.008711 +0.008788 +0.008852 +0.008701 +0.008638 +0.008685 +0.008756 +0.008852 +0.008913 +0.008741 +0.008666 +0.008722 +0.008795 +0.008904 +0.008937 +0.008781 +0.008706 +0.008759 +0.008838 +0.008927 +0.009 +0.008852 +0.008774 +0.008851 +0.0089 +0.008962 +0.009017 +0.008877 +0.008798 +0.008857 +0.008929 +0.00903 +0.009093 +0.00894 +0.008855 +0.008903 +0.008985 +0.009043 +0.009112 +0.008973 +0.008881 +0.008945 +0.009034 +0.009103 +0.009166 +0.009012 +0.008943 +0.008986 +0.009063 +0.00915 +0.009223 +0.009088 +0.008986 +0.009054 +0.009122 +0.009186 +0.009259 +0.009101 +0.009027 +0.009074 +0.009158 +0.009249 +0.009303 +0.009159 +0.009068 +0.009132 +0.009222 +0.009294 +0.009351 +0.009193 +0.009112 +0.00916 +0.009248 +0.009346 +0.009405 +0.009247 +0.009162 +0.009242 +0.009329 +0.009399 +0.009436 +0.009272 +0.009202 +0.009258 +0.009344 +0.009438 +0.009499 +0.009345 +0.009263 +0.009322 +0.009396 +0.00949 +0.009557 +0.009371 +0.009292 +0.009351 +0.009432 +0.009536 +0.009595 +0.009433 +0.009359 +0.009413 +0.009478 +0.009573 +0.009646 +0.009505 +0.009391 +0.009451 +0.009522 +0.009629 +0.009694 +0.009523 +0.009453 +0.009497 +0.009577 +0.009667 +0.009734 +0.009576 +0.009482 +0.009546 +0.009642 +0.009749 +0.009776 +0.009627 +0.009542 +0.009584 +0.009679 +0.009765 +0.009831 +0.009668 +0.009592 +0.009677 +0.009714 +0.009824 +0.009888 +0.009718 +0.009648 +0.009694 +0.009755 +0.009904 +0.009922 +0.009751 +0.009679 +0.009725 +0.009831 +0.009936 +0.009996 +0.009821 +0.00974 +0.009783 +0.00987 +0.009963 +0.010035 +0.009853 +0.009766 +0.009847 +0.009965 +0.010074 +0.010083 +0.0099 +0.009804 +0.009888 +0.009963 +0.010059 +0.010138 +0.009957 +0.009884 +0.009955 +0.010034 +0.010133 +0.010191 +0.009994 +0.009918 +0.009974 +0.010079 +0.010167 +0.010229 +0.01008 +0.009997 +0.010049 +0.010143 +0.010255 +0.010283 +0.010095 +0.010008 +0.010077 +0.010187 +0.010271 +0.010354 +0.010182 +0.010079 +0.010148 +0.010222 +0.010331 +0.010345 +0.010174 +0.010051 +0.010096 +0.010135 +0.010218 +0.010228 +0.010014 +0.009896 +0.009902 +0.009927 +0.009965 +0.009981 +0.009801 +0.00963 +0.009632 +0.009686 +0.009732 +0.00971 +0.009502 +0.009372 +0.00939 +0.0094 +0.009444 +0.009481 +0.009258 +0.009138 +0.009152 +0.00919 +0.009196 +0.009221 +0.009034 +0.008898 +0.008908 +0.00895 +0.00899 +0.009017 +0.008829 +0.008723 +0.008742 +0.008812 +0.00881 +0.00883 +0.008663 +0.008559 +0.00859 +0.008632 +0.008693 +0.008703 +0.008544 +0.008436 +0.008466 +0.008525 +0.008573 +0.008586 +0.008428 +0.008342 +0.008367 +0.008412 +0.008467 +0.008513 +0.008357 +0.008271 +0.008309 +0.008371 +0.008436 +0.008497 +0.008342 +0.008277 +0.008338 +0.008391 +0.008465 +0.008522 +0.008372 +0.008305 +0.008361 +0.008429 +0.008504 +0.008574 +0.00845 +0.008341 +0.008397 +0.008475 +0.008546 +0.008604 +0.008463 +0.008391 +0.008441 +0.008513 +0.008593 +0.008653 +0.008507 +0.00844 +0.008487 +0.008561 +0.008652 +0.008689 +0.008554 +0.008482 +0.008548 +0.008594 +0.008677 +0.008742 +0.008597 +0.008531 +0.008575 +0.008632 +0.008715 +0.008779 +0.008628 +0.008569 +0.008626 +0.008673 +0.008755 +0.008827 +0.008696 +0.008607 +0.008666 +0.008718 +0.008809 +0.00887 +0.008719 +0.008653 +0.008693 +0.008761 +0.008856 +0.008922 +0.008799 +0.008714 +0.008744 +0.008815 +0.008909 +0.008941 +0.008803 +0.008729 +0.008775 +0.008855 +0.008942 +0.009023 +0.008861 +0.008795 +0.008835 +0.008903 +0.00898 +0.00904 +0.008892 +0.008812 +0.008875 +0.008943 +0.009028 +0.009101 +0.008941 +0.008863 +0.008919 +0.008999 +0.009102 +0.009151 +0.008984 +0.008907 +0.008966 +0.009044 +0.009159 +0.009165 +0.009018 +0.008949 +0.009002 +0.009081 +0.009176 +0.009233 +0.009079 +0.008996 +0.009068 +0.00913 +0.009225 +0.009264 +0.009119 +0.009036 +0.009095 +0.009181 +0.009261 +0.009324 +0.009171 +0.009097 +0.009183 +0.009231 +0.009314 +0.009354 +0.009199 +0.009133 +0.00918 +0.009266 +0.00938 +0.009412 +0.00925 +0.009179 +0.009253 +0.009314 +0.00941 +0.009463 +0.009294 +0.009226 +0.009278 +0.009362 +0.009437 +0.00951 +0.009377 +0.009274 +0.00933 +0.009421 +0.009514 +0.009575 +0.009384 +0.009309 +0.009367 +0.009452 +0.009565 +0.00959 +0.009437 +0.009365 +0.009422 +0.009507 +0.009589 +0.009652 +0.009488 +0.00941 +0.009474 +0.009557 +0.009643 +0.009711 +0.009542 +0.009454 +0.009516 +0.009595 +0.009683 +0.009759 +0.009596 +0.009536 +0.009572 +0.009676 +0.00973 +0.009779 +0.009633 +0.009546 +0.009602 +0.009701 +0.009778 +0.009852 +0.009701 +0.009597 +0.009673 +0.009753 +0.009831 +0.009895 +0.00973 +0.009654 +0.009725 +0.00978 +0.009912 +0.009944 +0.009773 +0.009714 +0.009775 +0.009872 +0.009917 +0.009994 +0.009821 +0.009774 +0.009794 +0.009874 +0.009984 +0.010056 +0.009894 +0.00981 +0.009857 +0.009929 +0.010038 +0.010106 +0.009922 +0.009837 +0.009914 +0.00999 +0.010114 +0.010176 +0.009986 +0.009879 +0.009951 +0.010047 +0.010181 +0.010193 +0.010013 +0.009932 +0.010007 +0.01011 +0.010233 +0.01025 +0.010061 +0.009978 +0.010047 +0.01015 +0.010229 +0.010299 +0.01014 +0.010057 +0.010119 +0.01021 +0.010299 +0.010343 +0.010174 +0.010086 +0.010142 +0.01023 +0.01036 +0.010379 +0.010149 +0.010038 +0.010051 +0.010088 +0.010156 +0.010183 +0.009963 +0.00985 +0.009852 +0.009867 +0.009909 +0.009899 +0.009689 +0.00956 +0.009564 +0.009597 +0.009639 +0.009658 +0.009439 +0.00932 +0.009332 +0.009348 +0.009374 +0.009406 +0.009196 +0.009105 +0.009072 +0.009101 +0.009145 +0.009183 +0.008997 +0.008872 +0.00888 +0.008914 +0.00895 +0.008969 +0.008798 +0.008685 +0.008696 +0.00874 +0.008786 +0.008839 +0.008656 +0.008551 +0.008576 +0.008619 +0.008666 +0.008683 +0.008522 +0.008418 +0.008451 +0.008501 +0.008558 +0.008601 +0.008454 +0.008333 +0.008364 +0.008411 +0.008472 +0.008512 +0.008348 +0.00827 +0.008307 +0.008378 +0.00845 +0.008511 +0.008361 +0.008305 +0.008339 +0.008417 +0.008496 +0.008547 +0.008392 +0.00832 +0.008385 +0.008439 +0.008527 +0.008588 +0.008446 +0.008367 +0.008438 +0.008508 +0.008585 +0.00866 +0.008495 +0.008414 +0.008449 +0.008529 +0.008631 +0.008677 +0.008534 +0.008453 +0.008511 +0.008566 +0.008649 +0.00871 +0.008571 +0.0085 +0.008545 +0.008623 +0.008701 +0.008755 +0.008613 +0.008546 +0.008603 +0.008658 +0.008732 +0.008814 +0.008655 +0.008589 +0.008634 +0.008708 +0.008815 +0.008853 +0.008699 +0.008629 +0.008684 +0.008759 +0.008819 +0.008903 +0.008747 +0.008672 +0.008711 +0.008793 +0.008872 +0.008942 +0.008784 +0.008721 +0.008776 +0.008853 +0.008919 +0.008975 +0.008834 +0.008748 +0.008804 +0.008884 +0.008962 +0.009031 +0.008876 +0.008815 +0.008884 +0.008961 +0.009016 +0.009053 +0.008911 +0.008835 +0.00889 +0.008964 +0.009052 +0.009118 +0.008963 +0.008892 +0.008964 +0.009038 +0.00911 +0.009162 +0.009004 +0.008926 +0.008973 +0.009056 +0.009177 +0.009199 +0.009055 +0.008987 +0.009045 +0.009122 +0.009204 +0.009267 +0.009112 +0.00902 +0.009077 +0.009133 +0.009241 +0.009301 +0.009133 +0.009062 +0.00912 +0.009208 +0.009286 +0.009357 +0.009198 +0.009102 +0.00916 +0.009238 +0.009352 +0.009399 +0.009228 +0.009164 +0.009212 +0.009292 +0.009392 +0.00945 +0.009281 +0.009203 +0.009268 +0.009382 +0.009453 +0.009482 +0.009307 +0.009267 +0.009298 +0.009375 +0.009472 +0.009543 +0.009359 +0.009296 +0.009363 +0.009428 +0.009526 +0.00959 +0.00942 +0.009336 +0.009402 +0.009492 +0.009574 +0.009642 +0.009487 +0.009393 +0.009445 +0.009531 +0.009631 +0.009685 +0.009514 +0.009433 +0.009521 +0.009595 +0.009687 +0.009754 +0.00956 +0.009478 +0.009528 +0.009619 +0.009733 +0.009776 +0.009604 +0.009533 +0.009595 +0.009694 +0.00978 +0.009847 +0.009647 +0.009573 +0.009647 +0.009724 +0.009814 +0.009884 +0.009723 +0.009625 +0.009699 +0.009792 +0.009919 +0.009919 +0.009739 +0.009671 +0.009731 +0.009828 +0.009909 +0.00998 +0.009847 +0.009728 +0.009789 +0.00989 +0.009961 +0.010022 +0.009878 +0.009768 +0.009831 +0.009921 +0.010027 +0.010087 +0.009912 +0.00984 +0.009893 +0.009971 +0.010073 +0.010157 +0.009962 +0.009868 +0.00994 +0.010009 +0.010129 +0.010177 +0.010009 +0.009941 +0.009967 +0.010074 +0.01018 +0.010239 +0.01006 +0.009985 +0.010032 +0.01015 +0.010228 +0.010287 +0.010106 +0.010019 +0.010087 +0.010174 +0.010288 +0.010367 +0.010203 +0.010063 +0.010124 +0.010215 +0.010313 +0.010397 +0.010216 +0.010115 +0.010184 +0.010291 +0.010352 +0.010376 +0.010164 +0.010046 +0.010083 +0.010109 +0.010172 +0.010187 +0.00998 +0.009841 +0.009845 +0.009887 +0.009922 +0.009907 +0.009693 +0.009563 +0.00958 +0.009651 +0.009665 +0.009671 +0.009453 +0.009325 +0.009342 +0.009385 +0.009391 +0.009407 +0.009215 +0.00908 +0.009082 +0.009132 +0.009157 +0.009195 +0.009003 +0.00887 +0.008877 +0.008923 +0.008962 +0.008969 +0.008783 +0.008676 +0.008694 +0.00874 +0.008783 +0.008832 +0.008657 +0.008564 +0.008559 +0.008624 +0.008646 +0.008664 +0.008504 +0.008405 +0.008426 +0.008478 +0.008542 +0.00856 +0.00841 +0.008311 +0.008354 +0.008404 +0.008467 +0.008513 +0.008344 +0.008263 +0.008305 +0.008382 +0.00845 +0.008508 +0.008368 +0.008299 +0.008348 +0.00842 +0.008507 +0.008567 +0.008436 +0.008353 +0.008374 +0.008451 +0.008534 +0.008592 +0.008453 +0.008384 +0.008428 +0.008511 +0.008597 +0.008641 +0.008499 +0.008435 +0.00847 +0.008557 +0.008619 +0.008679 +0.008541 +0.008471 +0.008519 +0.00858 +0.008658 +0.008722 +0.00858 +0.008503 +0.008567 +0.008621 +0.008721 +0.008779 +0.008647 +0.008544 +0.008604 +0.00867 +0.008746 +0.008821 +0.008685 +0.008582 +0.008641 +0.008717 +0.008791 +0.008862 +0.008715 +0.00865 +0.0087 +0.008766 +0.008847 +0.008905 +0.008746 +0.008677 +0.008727 +0.008798 +0.008879 +0.00896 +0.008796 +0.008733 +0.008787 +0.008858 +0.008962 +0.00899 +0.008831 +0.008755 +0.008812 +0.008896 +0.008988 +0.009032 +0.008888 +0.00884 +0.008865 +0.008942 +0.009029 +0.009075 +0.008926 +0.008844 +0.008912 +0.008984 +0.009068 +0.009128 +0.008976 +0.008902 +0.008959 +0.009045 +0.009118 +0.009163 +0.009021 +0.008947 +0.009022 +0.009095 +0.009151 +0.009197 +0.009059 +0.008985 +0.009058 +0.009128 +0.009211 +0.009255 +0.009107 +0.00903 +0.009091 +0.009157 +0.009249 +0.009311 +0.009157 +0.009075 +0.009145 +0.00923 +0.009309 +0.009349 +0.009203 +0.009124 +0.009161 +0.009255 +0.009348 +0.009425 +0.00926 +0.009161 +0.009225 +0.009309 +0.0094 +0.009454 +0.009285 +0.00921 +0.009271 +0.009343 +0.009427 +0.009509 +0.009358 +0.009257 +0.009326 +0.009407 +0.009482 +0.009542 +0.009389 +0.0093 +0.009358 +0.00945 +0.009522 +0.009592 +0.009437 +0.00937 +0.009452 +0.009503 +0.009577 +0.009622 +0.009477 +0.009392 +0.00945 +0.009539 +0.009624 +0.00969 +0.009536 +0.009463 +0.009512 +0.00959 +0.009698 +0.009733 +0.009558 +0.009484 +0.009542 +0.009631 +0.009739 +0.009807 +0.009624 +0.009547 +0.00961 +0.009673 +0.009763 +0.009841 +0.009681 +0.009611 +0.009649 +0.009715 +0.009825 +0.00991 +0.009701 +0.009624 +0.009687 +0.009776 +0.009871 +0.009938 +0.009774 +0.009701 +0.009742 +0.009829 +0.009916 +0.009987 +0.009823 +0.009724 +0.00979 +0.00989 +0.009975 +0.010044 +0.009879 +0.009795 +0.009865 +0.009915 +0.010016 +0.010086 +0.009906 +0.009827 +0.009907 +0.009981 +0.010075 +0.010141 +0.009962 +0.009876 +0.009933 +0.010019 +0.010132 +0.01019 +0.010006 +0.009937 +0.009982 +0.010078 +0.010195 +0.010242 +0.010055 +0.009976 +0.010047 +0.010169 +0.01022 +0.010278 +0.010108 +0.010023 +0.010092 +0.010196 +0.010293 +0.010355 +0.010146 +0.010066 +0.010138 +0.010224 +0.010317 +0.010407 +0.010211 +0.010135 +0.010223 +0.010276 +0.010372 +0.010433 +0.010236 +0.010146 +0.010209 +0.010288 +0.010336 +0.010314 +0.010134 +0.010001 +0.010009 +0.010054 +0.010106 +0.010085 +0.009865 +0.009737 +0.009772 +0.00979 +0.009835 +0.009833 +0.009617 +0.009476 +0.009489 +0.00953 +0.009567 +0.009565 +0.00935 +0.009232 +0.009238 +0.009291 +0.009342 +0.009335 +0.009158 +0.008993 +0.009019 +0.009051 +0.009089 +0.009106 +0.008897 +0.008811 +0.008842 +0.00889 +0.008936 +0.008937 +0.008761 +0.008672 +0.008675 +0.00873 +0.008785 +0.008799 +0.008637 +0.008545 +0.008563 +0.008618 +0.008665 +0.0087 +0.008513 +0.008428 +0.00846 +0.008523 +0.008587 +0.008611 +0.008428 +0.008364 +0.008388 +0.008453 +0.008536 +0.008545 +0.0084 +0.008332 +0.008395 +0.008452 +0.008535 +0.008594 +0.008452 +0.008383 +0.008433 +0.008504 +0.008573 +0.00863 +0.008492 +0.008421 +0.008465 +0.008539 +0.008618 +0.008685 +0.008524 +0.008455 +0.008518 +0.008608 +0.008693 +0.008721 +0.008579 +0.008499 +0.00855 +0.008633 +0.008712 +0.008795 +0.008625 +0.008544 +0.008597 +0.008668 +0.008761 +0.0088 +0.008656 +0.008586 +0.008638 +0.008721 +0.0088 +0.008846 +0.008715 +0.008628 +0.008685 +0.008761 +0.008851 +0.008898 +0.008749 +0.008682 +0.008744 +0.008823 +0.008879 +0.008945 +0.008785 +0.008717 +0.008781 +0.008854 +0.008936 +0.008994 +0.008849 +0.008752 +0.008808 +0.008894 +0.008967 +0.009032 +0.008878 +0.00881 +0.008856 +0.008942 +0.009045 +0.009087 +0.008913 +0.008847 +0.0089 +0.008981 +0.009057 +0.009128 +0.00898 +0.008928 +0.008949 +0.009033 +0.00911 +0.009172 +0.009014 +0.008928 +0.008987 +0.009074 +0.009161 +0.009218 +0.009071 +0.008991 +0.009039 +0.009118 +0.009216 +0.009265 +0.009091 +0.009026 +0.009083 +0.009158 +0.009256 +0.009315 +0.009159 +0.009063 +0.009121 +0.00921 +0.009323 +0.009361 +0.009187 +0.009117 +0.009197 +0.009249 +0.009341 +0.009402 +0.009236 +0.009166 +0.00921 +0.009301 +0.009398 +0.009447 +0.009291 +0.009217 +0.009267 +0.009351 +0.009454 +0.009511 +0.009323 +0.009254 +0.009316 +0.009392 +0.009482 +0.009542 +0.009392 +0.009337 +0.009364 +0.009453 +0.009539 +0.009589 +0.009427 +0.009368 +0.009393 +0.009481 +0.009573 +0.00963 +0.009475 +0.009411 +0.009462 +0.009551 +0.009654 +0.009672 +0.00952 +0.009446 +0.009492 +0.009578 +0.009674 +0.009737 +0.009577 +0.009509 +0.009565 +0.009678 +0.00972 +0.009777 +0.009612 +0.009532 +0.009592 +0.009678 +0.009768 +0.009889 +0.00966 +0.009584 +0.009648 +0.009728 +0.009805 +0.009875 +0.009714 +0.009629 +0.009711 +0.00978 +0.009865 +0.009933 +0.009773 +0.009674 +0.009732 +0.009827 +0.009929 +0.010009 +0.009803 +0.009752 +0.009788 +0.009874 +0.009976 +0.01004 +0.009859 +0.009779 +0.009831 +0.009932 +0.010029 +0.010079 +0.009924 +0.009831 +0.009881 +0.009973 +0.010084 +0.010137 +0.00996 +0.009871 +0.009935 +0.010027 +0.010131 +0.010216 +0.010045 +0.009911 +0.009983 +0.010074 +0.010166 +0.010244 +0.010056 +0.009975 +0.010041 +0.01013 +0.010243 +0.010307 +0.010107 +0.010016 +0.01009 +0.010159 +0.010269 +0.01035 +0.010159 +0.010073