[wpimath] Add ArmFeedforward calculate() overload that takes current and next velocity instead of acceleration (#6540)

Co-authored-by: Tyler Veness <calcmogul@gmail.com>
This commit is contained in:
Nicholas Armstrong
2024-04-28 15:01:08 -04:00
committed by GitHub
parent 1727c74b80
commit 1ec089c7f9
8 changed files with 355 additions and 28 deletions

View File

@@ -0,0 +1,100 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "frc/controller/ArmFeedforward.h"
#include <limits>
#include <sleipnir/autodiff/Gradient.hpp>
#include <sleipnir/autodiff/Hessian.hpp>
#include "frc/EigenCore.h"
#include "frc/system/NumericalIntegration.h"
using namespace frc;
units::volt_t ArmFeedforward::Calculate(units::unit_t<Angle> currentAngle,
units::unit_t<Velocity> currentVelocity,
units::unit_t<Velocity> nextVelocity,
units::second_t dt) const {
using VarMat = sleipnir::VariableMatrix;
// Arm dynamics
Matrixd<2, 2> A{{0.0, 1.0}, {0.0, -kV.value() / kA.value()}};
Matrixd<2, 1> B{{0.0}, {1.0 / kA.value()}};
const auto& f = [&](const VarMat& x, const VarMat& u) -> VarMat {
VarMat c{{0.0},
{-(kS / kA).value() * sleipnir::sign(x(1)) -
(kG / kA).value() * sleipnir::cos(x(0))}};
return A * x + B * u + c;
};
Vectord<2> r_k{currentAngle.value(), currentVelocity.value()};
sleipnir::Variable u_k;
// Initial guess
auto acceleration = (nextVelocity - currentVelocity) / dt;
u_k.SetValue((kS * wpi::sgn(currentVelocity.value()) + kV * currentVelocity +
kA * acceleration + kG * units::math::cos(currentAngle))
.value());
auto r_k1 = RK4<decltype(f), VarMat, VarMat>(f, r_k, u_k, dt);
// Minimize difference between desired and actual next velocity
auto cost =
(nextVelocity.value() - r_k1(1)) * (nextVelocity.value() - r_k1(1));
// Refine solution via Newton's method
{
auto xAD = u_k;
double x = xAD.Value();
sleipnir::Gradient gradientF{cost, xAD};
Eigen::SparseVector<double> g = gradientF.Value();
sleipnir::Hessian hessianF{cost, xAD};
Eigen::SparseMatrix<double> H = hessianF.Value();
double error = std::numeric_limits<double>::infinity();
while (error > 1e-8) {
// Iterate via Newton's method.
//
// xₖ₊₁ = xₖ H⁻¹g
//
// The Hessian is regularized to at least 1e-4.
double p_x = -g.coeff(0) / std::max(H.coeff(0, 0), 1e-4);
// Shrink step until cost goes down
{
double oldCost = cost.Value();
double α = 1.0;
double trial_x = x + α * p_x;
xAD.SetValue(trial_x);
cost.Update();
while (cost.Value() > oldCost) {
α *= 0.5;
trial_x = x + α * p_x;
xAD.SetValue(trial_x);
cost.Update();
}
x = trial_x;
}
xAD.SetValue(x);
g = gradientF.Value();
H = hessianF.Value();
error = std::abs(g.coeff(0));
}
}
return units::volt_t{u_k.Value()};
}

View File

@@ -0,0 +1,36 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include <jni.h>
#include <wpi/jni_util.h>
#include "edu_wpi_first_math_WPIMathJNI.h"
#include "frc/controller/ArmFeedforward.h"
using namespace wpi::java;
extern "C" {
/*
* Class: edu_wpi_first_math_WPIMathJNI
* Method: calculate
* Signature: (DDDDDDDD)D
*/
JNIEXPORT jdouble JNICALL
Java_edu_wpi_first_math_WPIMathJNI_calculate
(JNIEnv* env, jclass, jdouble ks, jdouble kv, jdouble ka, jdouble kg,
jdouble currentAngle, jdouble currentVelocity, jdouble nextVelocity,
jdouble dt)
{
return frc::ArmFeedforward{units::volt_t{ks}, units::volt_t{kg},
units::unit_t<frc::ArmFeedforward::kv_unit>{kv},
units::unit_t<frc::ArmFeedforward::ka_unit>{ka}}
.Calculate(units::radian_t{currentAngle},
units::radians_per_second_t{currentVelocity},
units::radians_per_second_t{nextVelocity}, units::second_t{dt})
.value();
}
} // extern "C"