Regression improvements
This commit is contained in:
@ -79,6 +79,26 @@ function fixVels(ts, xs, vs) {
|
||||
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
|
||||
function newLinearRegressionChart(container, xs, ys, options, onChange) {
|
||||
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 chartDiv = document.createElement('div');
|
||||
const width = Math.max(0, window.innerWidth - 50);
|
||||
Plotly.newPlot(chartDiv, [{
|
||||
type: 'scatter',
|
||||
mode: 'markers',
|
||||
@ -124,10 +145,11 @@ function newLinearRegressionChart(container, xs, ys, options, onChange) {
|
||||
dragmode: 'select',
|
||||
showlegend: false,
|
||||
hovermode: false,
|
||||
width: 600,
|
||||
width,
|
||||
height: width * 9 / 16,
|
||||
}, {
|
||||
// 'select2d' left
|
||||
modeBarButtonsToRemove: ['zoom2d', 'pan2d', 'lasso2d', 'zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d'],
|
||||
// 'select2d', 'zoom2d', 'pan2d', 'lasso2d', 'zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d' left
|
||||
modeBarButtonsToRemove: [],
|
||||
});
|
||||
|
||||
const results = document.createElement('p');
|
||||
@ -161,6 +183,10 @@ function newLinearRegressionChart(container, xs, ys, options, onChange) {
|
||||
let pendingSelection = null;
|
||||
|
||||
chartDiv.on('plotly_selected', function(eventData) {
|
||||
if (eventData === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
pendingSelection = eventData;
|
||||
});
|
||||
|
||||
|
@ -9,6 +9,10 @@ body {
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
header {
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
}
|
||||
@ -44,28 +48,30 @@ details, a {
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>RR Dead Wheel Angular Ramp Regression</h1>
|
||||
<details></details>
|
||||
<header>
|
||||
<h1>RR Dead Wheel Angular Ramp Regression</h1>
|
||||
<details></details>
|
||||
|
||||
<div id="download"></div>
|
||||
<div id="download"></div>
|
||||
|
||||
<fieldset>
|
||||
<legend>Feedforward Parameters</legend>
|
||||
<div>
|
||||
kV: <input id="kv" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
kS: <input id="ks" type="text" />
|
||||
</div>
|
||||
<input id="update" type="button" value="update" />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Feedforward Parameters</legend>
|
||||
<div>
|
||||
kV: <input id="kv" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
kS: <input id="ks" type="text" />
|
||||
</div>
|
||||
<input id="update" type="button" value="update" />
|
||||
</fieldset>
|
||||
|
||||
<div id="trackWidthChart">
|
||||
<p>
|
||||
<button id="latest">Latest</button>
|
||||
<input id="browse" type="file" accept="application/json">
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="trackWidthChart"></div>
|
||||
|
||||
<div id="deadWheelCharts"></div>
|
||||
</div>
|
||||
@ -73,7 +79,7 @@ details, a {
|
||||
<script>
|
||||
function loadRegression(data) {
|
||||
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 [accMaxV, _] = acc;
|
||||
if (maxV >= accMaxV) {
|
||||
@ -86,28 +92,29 @@ function loadRegression(data) {
|
||||
data.parEncVels.forEach((vs, i) => {
|
||||
const div = document.createElement('div');
|
||||
newLinearRegressionChart(div,
|
||||
angVels.slice(1, angVels.length - 1),
|
||||
fixVels(data.encTimes, data.parEncPositions[i], vs),
|
||||
{title: `Parallel Wheel ${i} Regression`, slope: 'y-position'});
|
||||
angVels.slice(1, -1),
|
||||
fixVels(data.encTimes.slice(0, -1), data.parEncPositions[i].slice(0, -1), vs.slice(0, -1)),
|
||||
{title: `Parallel Wheel ${i} Regression`, slope: 'y-position', noIntercept: true});
|
||||
deadWheelCharts.appendChild(div);
|
||||
});
|
||||
data.perpEncVels.forEach((vs, i) => {
|
||||
const div = document.createElement('div');
|
||||
newLinearRegressionChart(div,
|
||||
angVels.slice(1, angVels.length - 1),
|
||||
fixVels(data.encTimes, data.perpEncPositions[i], vs),
|
||||
{title: `Perpendicular Wheel ${i} Regression`, slope: 'x-position'});
|
||||
angVels.slice(1, -1),
|
||||
fixVels(data.encTimes.slice(0, -1), data.perpEncPositions[i].slice(0, -1), vs.slice(0, -1)),
|
||||
{title: `Perpendicular Wheel ${i} Regression`, slope: 'x-position', noIntercept: true});
|
||||
deadWheelCharts.appendChild(div);
|
||||
});
|
||||
|
||||
const setParams = (() => {
|
||||
const appliedVoltages = data.voltages.map((v, i) =>
|
||||
[...data.leftPowers, ...data.rightPowers].reduce((acc, ps) => Math.max(acc, ps[i]), 0) * v);
|
||||
const allPowers = [...data.leftPowers, ...data.rightPowers];
|
||||
const appliedVoltages = data.voltages.slice(0, -1).map((v, i) =>
|
||||
allPowers.reduce((acc, ps) => Math.max(acc, ps[i]), 0) * v);
|
||||
|
||||
const setTrackWidthData = newLinearRegressionChart(
|
||||
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) =>
|
||||
@ -119,6 +126,8 @@ function loadRegression(data) {
|
||||
document.getElementById('update').addEventListener('click', () => {
|
||||
setParams(parseFloat(kvInput.value), parseFloat(ksInput.value));
|
||||
});
|
||||
|
||||
setParams(parseFloat(kvInput.value), parseFloat(ksInput.value));
|
||||
}
|
||||
|
||||
const latestButton = document.getElementById('latest');
|
||||
@ -130,10 +139,12 @@ latestButton.addEventListener('click', function() {
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.innerText = 'Download';
|
||||
a.href = `/tuning/forward-ramp/${filename}`;
|
||||
a.download = `forward-ramp-${filename}`;
|
||||
a.href = `/tuning/angular-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();
|
||||
} else {
|
||||
@ -142,7 +153,12 @@ latestButton.addEventListener('click', function() {
|
||||
}
|
||||
})
|
||||
.then(loadRegression)
|
||||
.catch(console.log.bind(console));
|
||||
.catch((e) => {
|
||||
const deadWheelCharts = document.getElementById('deadWheelCharts');
|
||||
deadWheelCharts.innerHTML = '';
|
||||
|
||||
console.log(e);
|
||||
});
|
||||
});
|
||||
|
||||
const browseInput = document.getElementById('browse');
|
||||
|
@ -9,6 +9,10 @@ body {
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
header {
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
}
|
||||
@ -39,48 +43,50 @@ details, a {
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>RR Drive Encoder Angular Ramp Regression</h1>
|
||||
<details></details>
|
||||
<header>
|
||||
<h1>RR Drive Encoder Angular Ramp Regression</h1>
|
||||
<details></details>
|
||||
|
||||
<div id="download"></div>
|
||||
|
||||
<div id="download"></div>
|
||||
|
||||
<div id="rampChart">
|
||||
<p>
|
||||
<button id="latest">Latest</button>
|
||||
<input id="browse" type="file" accept="application/json">
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="rampChart"></div>
|
||||
<div id="trackWidthChart"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function loadRegression(data) {
|
||||
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) =>
|
||||
fixVels(data.encTimes, data.rightEncPositions[i], vs));
|
||||
fixVels(data.encTimes.slice(0, -1), data.rightEncPositions[i].slice(0, -1), vs.slice(0, -1)));
|
||||
|
||||
newLinearRegressionChart(
|
||||
document.getElementById('rampChart'),
|
||||
[
|
||||
...leftEncVels.flatMap(vs => vs.map(v => -v)),
|
||||
...rightEncVels.flatMap(vs => vs),
|
||||
...leftEncVels.flatMap(vs => vs.slice(0, -1).map(v => -v)),
|
||||
...rightEncVels.flatMap(vs => vs.slice(0, -1)),
|
||||
],
|
||||
[
|
||||
...data.leftPowers.flatMap(ps => {
|
||||
const psNew = ps.map((p, i) => -p * data.voltages[i]);
|
||||
return psNew.slice(1, psNew.length - 1);
|
||||
const psNew = ps.slice(0, -1).map((p, i) => -p * data.voltages[i]);
|
||||
return psNew.slice(1, -1);
|
||||
}),
|
||||
...data.rightPowers.flatMap(ps => {
|
||||
const psNew = ps.map((p, i) => p * data.voltages[i]);
|
||||
return psNew.slice(1, psNew.length - 1);
|
||||
const psNew = ps.slice(0, -1).map((p, i) => p * data.voltages[i]);
|
||||
return psNew.slice(1, -1);
|
||||
}),
|
||||
],
|
||||
{title: 'Ramp Regression', slope: 'kV', intercept: 'kS'}
|
||||
);
|
||||
|
||||
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 [accMaxV, _] = acc;
|
||||
if (maxV >= accMaxV) {
|
||||
@ -88,7 +94,7 @@ function loadRegression(data) {
|
||||
}
|
||||
return acc;
|
||||
}, [0, []]);
|
||||
const angVels = p[1].slice(1, p[1].length - 1);
|
||||
const angVels = p[1].slice(1, -1);
|
||||
|
||||
newLinearRegressionChart(
|
||||
document.getElementById('trackWidthChart'),
|
||||
@ -98,7 +104,7 @@ function loadRegression(data) {
|
||||
+ rightEncVels.reduce((acc, vs) => acc + vs[i], 0) / data.rightEncVels.length)
|
||||
* (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');
|
||||
a.innerText = 'Download';
|
||||
a.href = `/tuning/forward-ramp/${filename}`;
|
||||
a.download = `forward-ramp-${filename}`;
|
||||
a.href = `/tuning/angular-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();
|
||||
} else {
|
||||
|
@ -9,6 +9,10 @@ body {
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
header {
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
}
|
||||
@ -39,25 +43,27 @@ details, a {
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>RR Forward Ramp Regression</h1>
|
||||
<details></details>
|
||||
<header>
|
||||
<h1>RR Forward Ramp Regression</h1>
|
||||
<details></details>
|
||||
|
||||
<div id="download"></div>
|
||||
<div id="download"></div>
|
||||
|
||||
<div id="rampChart">
|
||||
<p>
|
||||
<button id="latest">Latest</button>
|
||||
<input id="browse" type="file" accept="application/json">
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="rampChart"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function loadRegression(data) {
|
||||
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 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);
|
||||
|
||||
return voltages.slice(1, voltages.length - 1);
|
||||
@ -66,7 +72,7 @@ function loadRegression(data) {
|
||||
newLinearRegressionChart(
|
||||
document.getElementById('rampChart'),
|
||||
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.download = `forward-ramp-${filename}`;
|
||||
|
||||
document.getElementById('download').appendChild(a);
|
||||
const download = document.getElementById('download');
|
||||
download.innerHTML = '';
|
||||
download.appendChild(a);
|
||||
|
||||
return res.json();
|
||||
} else {
|
||||
|
Reference in New Issue
Block a user