Regression improvements

This commit is contained in:
Ryan Brott
2023-03-02 00:16:35 -08:00
parent 9651735df3
commit 80ea431b29
4 changed files with 119 additions and 61 deletions

View File

@ -79,6 +79,26 @@ function fixVels(ts, xs, vs) {
return numDerivOffline(ts, xs).map((est, i) => inverseOverflow(vs[i + 1], est)); return numDerivOffline(ts, xs).map((est, i) => inverseOverflow(vs[i + 1], est));
} }
// see https://github.com/FIRST-Tech-Challenge/FtcRobotController/issues/617
function fixAngVels(vs) {
if (vs.length === 0) {
return [];
}
let offset = 0;
lastV = vs[0];
const vsFixed = [lastV];
for (let i = 1; i < vs.length; i++) {
if (Math.abs(vs[i] - lastV) > Math.PI) {
offset -= Math.sign(vs[i] - lastV) * 2 * Math.PI;
}
vsFixed.push(offset + vs[i]);
lastV = vs[i];
}
return vsFixed;
}
// data comes in pairs // data comes in pairs
function newLinearRegressionChart(container, xs, ys, options, onChange) { function newLinearRegressionChart(container, xs, ys, options, onChange) {
if (xs.length !== ys.length) { if (xs.length !== ys.length) {
@ -103,6 +123,7 @@ function newLinearRegressionChart(container, xs, ys, options, onChange) {
const maxX = xs.reduce((a, b) => Math.max(a, b), 0); const maxX = xs.reduce((a, b) => Math.max(a, b), 0);
const chartDiv = document.createElement('div'); const chartDiv = document.createElement('div');
const width = Math.max(0, window.innerWidth - 50);
Plotly.newPlot(chartDiv, [{ Plotly.newPlot(chartDiv, [{
type: 'scatter', type: 'scatter',
mode: 'markers', mode: 'markers',
@ -124,10 +145,11 @@ function newLinearRegressionChart(container, xs, ys, options, onChange) {
dragmode: 'select', dragmode: 'select',
showlegend: false, showlegend: false,
hovermode: false, hovermode: false,
width: 600, width,
height: width * 9 / 16,
}, { }, {
// 'select2d' left // 'select2d', 'zoom2d', 'pan2d', 'lasso2d', 'zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d' left
modeBarButtonsToRemove: ['zoom2d', 'pan2d', 'lasso2d', 'zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d'], modeBarButtonsToRemove: [],
}); });
const results = document.createElement('p'); const results = document.createElement('p');
@ -161,6 +183,10 @@ function newLinearRegressionChart(container, xs, ys, options, onChange) {
let pendingSelection = null; let pendingSelection = null;
chartDiv.on('plotly_selected', function(eventData) { chartDiv.on('plotly_selected', function(eventData) {
if (eventData === undefined) {
return;
}
pendingSelection = eventData; pendingSelection = eventData;
}); });

View File

@ -9,6 +9,10 @@ body {
} }
.content { .content {
margin: auto;
}
header {
max-width: 600px; max-width: 600px;
margin: auto; margin: auto;
} }
@ -44,6 +48,7 @@ details, a {
</head> </head>
<body> <body>
<div class="content"> <div class="content">
<header>
<h1>RR Dead Wheel Angular Ramp Regression</h1> <h1>RR Dead Wheel Angular Ramp Regression</h1>
<details></details> <details></details>
@ -60,12 +65,13 @@ details, a {
<input id="update" type="button" value="update" /> <input id="update" type="button" value="update" />
</fieldset> </fieldset>
<div id="trackWidthChart">
<p> <p>
<button id="latest">Latest</button> <button id="latest">Latest</button>
<input id="browse" type="file" accept="application/json"> <input id="browse" type="file" accept="application/json">
</p> </p>
</div> </header>
<div id="trackWidthChart"></div>
<div id="deadWheelCharts"></div> <div id="deadWheelCharts"></div>
</div> </div>
@ -73,7 +79,7 @@ details, a {
<script> <script>
function loadRegression(data) { function loadRegression(data) {
const [_, angVels] = data.angVels.reduce((acc, vsArg) => { const [_, angVels] = data.angVels.reduce((acc, vsArg) => {
const vs = vsArg.map(v => Math.abs(v)); const vs = fixAngVels(vsArg.slice(0, -1)).map(v => Math.abs(v));
const maxV = vs.reduce((acc, v) => Math.max(acc, v), 0); const maxV = vs.reduce((acc, v) => Math.max(acc, v), 0);
const [accMaxV, _] = acc; const [accMaxV, _] = acc;
if (maxV >= accMaxV) { if (maxV >= accMaxV) {
@ -86,28 +92,29 @@ function loadRegression(data) {
data.parEncVels.forEach((vs, i) => { data.parEncVels.forEach((vs, i) => {
const div = document.createElement('div'); const div = document.createElement('div');
newLinearRegressionChart(div, newLinearRegressionChart(div,
angVels.slice(1, angVels.length - 1), angVels.slice(1, -1),
fixVels(data.encTimes, data.parEncPositions[i], vs), fixVels(data.encTimes.slice(0, -1), data.parEncPositions[i].slice(0, -1), vs.slice(0, -1)),
{title: `Parallel Wheel ${i} Regression`, slope: 'y-position'}); {title: `Parallel Wheel ${i} Regression`, slope: 'y-position', noIntercept: true});
deadWheelCharts.appendChild(div); deadWheelCharts.appendChild(div);
}); });
data.perpEncVels.forEach((vs, i) => { data.perpEncVels.forEach((vs, i) => {
const div = document.createElement('div'); const div = document.createElement('div');
newLinearRegressionChart(div, newLinearRegressionChart(div,
angVels.slice(1, angVels.length - 1), angVels.slice(1, -1),
fixVels(data.encTimes, data.perpEncPositions[i], vs), fixVels(data.encTimes.slice(0, -1), data.perpEncPositions[i].slice(0, -1), vs.slice(0, -1)),
{title: `Perpendicular Wheel ${i} Regression`, slope: 'x-position'}); {title: `Perpendicular Wheel ${i} Regression`, slope: 'x-position', noIntercept: true});
deadWheelCharts.appendChild(div); deadWheelCharts.appendChild(div);
}); });
const setParams = (() => { const setParams = (() => {
const appliedVoltages = data.voltages.map((v, i) => const allPowers = [...data.leftPowers, ...data.rightPowers];
[...data.leftPowers, ...data.rightPowers].reduce((acc, ps) => Math.max(acc, ps[i]), 0) * v); const appliedVoltages = data.voltages.slice(0, -1).map((v, i) =>
allPowers.reduce((acc, ps) => Math.max(acc, ps[i]), 0) * v);
const setTrackWidthData = newLinearRegressionChart( const setTrackWidthData = newLinearRegressionChart(
document.getElementById('trackWidthChart'), document.getElementById('trackWidthChart'),
[], [], [], [],
{title: 'Track Width Regression', slope: 'track width'} {title: 'Track Width Regression', slope: 'track width', noIntercept: true}
); );
return (kV, kS) => setTrackWidthData(angVels, appliedVoltages.map((v, i) => return (kV, kS) => setTrackWidthData(angVels, appliedVoltages.map((v, i) =>
@ -119,6 +126,8 @@ function loadRegression(data) {
document.getElementById('update').addEventListener('click', () => { document.getElementById('update').addEventListener('click', () => {
setParams(parseFloat(kvInput.value), parseFloat(ksInput.value)); setParams(parseFloat(kvInput.value), parseFloat(ksInput.value));
}); });
setParams(parseFloat(kvInput.value), parseFloat(ksInput.value));
} }
const latestButton = document.getElementById('latest'); const latestButton = document.getElementById('latest');
@ -130,10 +139,12 @@ latestButton.addEventListener('click', function() {
const a = document.createElement('a'); const a = document.createElement('a');
a.innerText = 'Download'; a.innerText = 'Download';
a.href = `/tuning/forward-ramp/${filename}`; a.href = `/tuning/angular-ramp/${filename}`;
a.download = `forward-ramp-${filename}`; a.download = `angular-ramp-${filename}`;
document.getElementById('download').appendChild(a); const download = document.getElementById('download');
download.innerHTML = '';
download.appendChild(a);
return res.json(); return res.json();
} else { } else {
@ -142,7 +153,12 @@ latestButton.addEventListener('click', function() {
} }
}) })
.then(loadRegression) .then(loadRegression)
.catch(console.log.bind(console)); .catch((e) => {
const deadWheelCharts = document.getElementById('deadWheelCharts');
deadWheelCharts.innerHTML = '';
console.log(e);
});
}); });
const browseInput = document.getElementById('browse'); const browseInput = document.getElementById('browse');

View File

@ -9,6 +9,10 @@ body {
} }
.content { .content {
margin: auto;
}
header {
max-width: 600px; max-width: 600px;
margin: auto; margin: auto;
} }
@ -39,48 +43,50 @@ details, a {
</head> </head>
<body> <body>
<div class="content"> <div class="content">
<header>
<h1>RR Drive Encoder Angular Ramp Regression</h1> <h1>RR Drive Encoder Angular Ramp Regression</h1>
<details></details> <details></details>
<div id="download"></div> <div id="download"></div>
<div id="rampChart">
<p> <p>
<button id="latest">Latest</button> <button id="latest">Latest</button>
<input id="browse" type="file" accept="application/json"> <input id="browse" type="file" accept="application/json">
</p> </p>
</div> </header>
<div id="rampChart"></div>
<div id="trackWidthChart"></div> <div id="trackWidthChart"></div>
</div> </div>
<script> <script>
function loadRegression(data) { function loadRegression(data) {
const leftEncVels = data.leftEncVels.map((vs, i) => const leftEncVels = data.leftEncVels.map((vs, i) =>
fixVels(data.encTimes, data.leftEncPositions[i], vs)); fixVels(data.encTimes.slice(0, -1), data.leftEncPositions[i].slice(0, -1), vs.slice(0, -1)));
const rightEncVels = data.rightEncVels.map((vs, i) => const rightEncVels = data.rightEncVels.map((vs, i) =>
fixVels(data.encTimes, data.rightEncPositions[i], vs)); fixVels(data.encTimes.slice(0, -1), data.rightEncPositions[i].slice(0, -1), vs.slice(0, -1)));
newLinearRegressionChart( newLinearRegressionChart(
document.getElementById('rampChart'), document.getElementById('rampChart'),
[ [
...leftEncVels.flatMap(vs => vs.map(v => -v)), ...leftEncVels.flatMap(vs => vs.slice(0, -1).map(v => -v)),
...rightEncVels.flatMap(vs => vs), ...rightEncVels.flatMap(vs => vs.slice(0, -1)),
], ],
[ [
...data.leftPowers.flatMap(ps => { ...data.leftPowers.flatMap(ps => {
const psNew = ps.map((p, i) => -p * data.voltages[i]); const psNew = ps.slice(0, -1).map((p, i) => -p * data.voltages[i]);
return psNew.slice(1, psNew.length - 1); return psNew.slice(1, -1);
}), }),
...data.rightPowers.flatMap(ps => { ...data.rightPowers.flatMap(ps => {
const psNew = ps.map((p, i) => p * data.voltages[i]); const psNew = ps.slice(0, -1).map((p, i) => p * data.voltages[i]);
return psNew.slice(1, psNew.length - 1); return psNew.slice(1, -1);
}), }),
], ],
{title: 'Ramp Regression', slope: 'kV', intercept: 'kS'} {title: 'Ramp Regression', slope: 'kV', intercept: 'kS'}
); );
const p = data.angVels.reduce((acc, vsArg) => { const p = data.angVels.reduce((acc, vsArg) => {
const vs = vsArg.map(v => Math.abs(v)); const vs = fixAngVels(vsArg).map(v => Math.abs(v));
const maxV = vs.reduce((acc, v) => Math.max(acc, v), 0); const maxV = vs.reduce((acc, v) => Math.max(acc, v), 0);
const [accMaxV, _] = acc; const [accMaxV, _] = acc;
if (maxV >= accMaxV) { if (maxV >= accMaxV) {
@ -88,7 +94,7 @@ function loadRegression(data) {
} }
return acc; return acc;
}, [0, []]); }, [0, []]);
const angVels = p[1].slice(1, p[1].length - 1); const angVels = p[1].slice(1, -1);
newLinearRegressionChart( newLinearRegressionChart(
document.getElementById('trackWidthChart'), document.getElementById('trackWidthChart'),
@ -98,7 +104,7 @@ function loadRegression(data) {
+ rightEncVels.reduce((acc, vs) => acc + vs[i], 0) / data.rightEncVels.length) + rightEncVels.reduce((acc, vs) => acc + vs[i], 0) / data.rightEncVels.length)
* (data.type === 'mecanum' ? 0.5 : 1) * (data.type === 'mecanum' ? 0.5 : 1)
), ),
{title: 'Track Width Regression', slope: 'track width'} {title: 'Track Width Regression', slope: 'track width', noIntercept: true}
); );
} }
@ -111,10 +117,12 @@ latestButton.addEventListener('click', function() {
const a = document.createElement('a'); const a = document.createElement('a');
a.innerText = 'Download'; a.innerText = 'Download';
a.href = `/tuning/forward-ramp/${filename}`; a.href = `/tuning/angular-ramp/${filename}`;
a.download = `forward-ramp-${filename}`; a.download = `angular-ramp-${filename}`;
document.getElementById('download').appendChild(a); const download = document.getElementById('download');
download.innerHTML = '';
download.appendChild(a);
return res.json(); return res.json();
} else { } else {

View File

@ -9,6 +9,10 @@ body {
} }
.content { .content {
margin: auto;
}
header {
max-width: 600px; max-width: 600px;
margin: auto; margin: auto;
} }
@ -39,25 +43,27 @@ details, a {
</head> </head>
<body> <body>
<div class="content"> <div class="content">
<header>
<h1>RR Forward Ramp Regression</h1> <h1>RR Forward Ramp Regression</h1>
<details></details> <details></details>
<div id="download"></div> <div id="download"></div>
<div id="rampChart">
<p> <p>
<button id="latest">Latest</button> <button id="latest">Latest</button>
<input id="browse" type="file" accept="application/json"> <input id="browse" type="file" accept="application/json">
</p> </p>
</div> </header>
<div id="rampChart"></div>
</div> </div>
<script> <script>
function loadRegression(data) { function loadRegression(data) {
const forwardEncVels = data.forwardEncVels.flatMap((vs, i) => const forwardEncVels = data.forwardEncVels.flatMap((vs, i) =>
fixVels(data.encTimes, data.forwardEncPositions[i], vs)); fixVels(data.encTimes.slice(0, -1), data.forwardEncPositions[i].slice(0, -1), vs.slice(0, -1)));
const appliedVoltages = data.forwardEncVels.flatMap(vs => { const appliedVoltages = data.forwardEncVels.flatMap(vs => {
const voltages = data.voltages.map((v, i) => const voltages = data.voltages.slice(0, -1).map((v, i) =>
data.powers.reduce((acc, ps) => Math.max(acc, ps[i]), 0) * v); data.powers.reduce((acc, ps) => Math.max(acc, ps[i]), 0) * v);
return voltages.slice(1, voltages.length - 1); return voltages.slice(1, voltages.length - 1);
@ -66,7 +72,7 @@ function loadRegression(data) {
newLinearRegressionChart( newLinearRegressionChart(
document.getElementById('rampChart'), document.getElementById('rampChart'),
forwardEncVels, appliedVoltages, forwardEncVels, appliedVoltages,
{title: 'Ramp Regression', slope: 'kV', intercept: 'kS'} {title: 'Ramp Regression', slope: 'kV', intercept: 'kS', noIntercept: false}
); );
} }
@ -82,7 +88,9 @@ latestButton.addEventListener('click', function() {
a.href = `/tuning/forward-ramp/${filename}`; a.href = `/tuning/forward-ramp/${filename}`;
a.download = `forward-ramp-${filename}`; a.download = `forward-ramp-${filename}`;
document.getElementById('download').appendChild(a); const download = document.getElementById('download');
download.innerHTML = '';
download.appendChild(a);
return res.json(); return res.json();
} else { } else {