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));
|
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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -9,6 +9,10 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
@ -44,28 +48,30 @@ details, a {
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h1>RR Dead Wheel Angular Ramp Regression</h1>
|
<header>
|
||||||
<details></details>
|
<h1>RR Dead Wheel Angular Ramp Regression</h1>
|
||||||
|
<details></details>
|
||||||
|
|
||||||
<div id="download"></div>
|
<div id="download"></div>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Feedforward Parameters</legend>
|
<legend>Feedforward Parameters</legend>
|
||||||
<div>
|
<div>
|
||||||
kV: <input id="kv" type="text" />
|
kV: <input id="kv" type="text" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
kS: <input id="ks" type="text" />
|
kS: <input id="ks" type="text" />
|
||||||
</div>
|
</div>
|
||||||
<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');
|
||||||
|
@ -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">
|
||||||
<h1>RR Drive Encoder Angular Ramp Regression</h1>
|
<header>
|
||||||
<details></details>
|
<h1>RR Drive Encoder Angular Ramp Regression</h1>
|
||||||
|
<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 {
|
||||||
|
@ -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">
|
||||||
<h1>RR Forward Ramp Regression</h1>
|
<header>
|
||||||
<details></details>
|
<h1>RR Forward Ramp Regression</h1>
|
||||||
|
<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 {
|
||||||
|
Reference in New Issue
Block a user