Skip to content

Commit

Permalink
Add ImplicitModelFollower
Browse files Browse the repository at this point in the history
  • Loading branch information
calcmogul committed Feb 26, 2022
1 parent bc9e96e commit b26f128
Show file tree
Hide file tree
Showing 3 changed files with 276 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// 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.

package edu.wpi.first.math.controller;

import edu.wpi.first.math.Matrix;
import edu.wpi.first.math.Num;
import edu.wpi.first.math.numbers.N1;
import edu.wpi.first.math.system.Discretization;
import edu.wpi.first.math.system.LinearSystem;
import org.ejml.simple.SimpleMatrix;

/**
* Contains the controller coefficients and logic for an implicit model follower.
*
* <p>Implicit model following lets us design a feedback controller that erases the dynamics of our
* system and makes it behave like some other system. This can be used to make a drivetrain more
* controllable during teleop driving by making it behave like a slower or more benign drivetrain.
*
* <p>For more on the underlying math, read appendix B.3 in
* https://file.tavsys.net/control/controls-engineering-in-frc.pdf.
*/
@SuppressWarnings("ClassTypeParameterName")
public class ImplicitModelFollower<States extends Num, Inputs extends Num, Outputs extends Num> {
// Computed controller output
@SuppressWarnings("MemberName")
private Matrix<Inputs, N1> m_u;

// State space conversion gain
@SuppressWarnings("MemberName")
private Matrix<Inputs, States> m_A;

// Input space conversion gain
@SuppressWarnings("MemberName")
private Matrix<Inputs, Inputs> m_B;

/**
* Constructs a controller with the given coefficients and plant.
*
* @param plant The plant being controlled.
* @param plantRef The plant whose dynamics should be followed.
* @param dtSeconds Discretization timestep.
*/
public ImplicitModelFollower(
LinearSystem<States, Inputs, Outputs> plant,
LinearSystem<States, Inputs, Outputs> plantRef,
double dtSeconds) {
this(plant.getA(), plant.getB(), plantRef.getA(), plantRef.getB(), dtSeconds);
}

/**
* Constructs a controller with the given coefficients and plant.
*
* @param A Continuous system matrix of the plant being controlled.
* @param B Continuous input matrix of the plant being controlled.
* @param Aref Continuous system matrix whose dynamics should be followed.
* @param Bref Continuous input matrix whose dynamics should be followed.
* @param dtSeconds Discretization timestep.
*/
@SuppressWarnings("ParameterName")
public ImplicitModelFollower(
Matrix<States, States> A,
Matrix<States, Inputs> B,
Matrix<States, States> Aref,
Matrix<States, Inputs> Bref,
double dtSeconds) {
m_u = new Matrix<>(new SimpleMatrix(B.getNumCols(), 1));

// Discretize real dynamics
var discABPair = Discretization.discretizeAB(A, B, dtSeconds);
var discA = discABPair.getFirst();
var discB = discABPair.getSecond();

// Discretize desired dynamics
var discABrefPair = Discretization.discretizeAB(Aref, Bref, dtSeconds);
var discAref = discABrefPair.getFirst();
var discBref = discABrefPair.getSecond();

// Find u_imf that makes real model match reference model.
//
// x_k+1 = Ax_k + Bu_imf
// z_k+1 = Aref z_k + Bref u_k
//
// Let x_k = z_k.
//
// x_k+1 = z_k+1
// Ax_k + Bu_imf = Aref x_k + Bref u_k
// Bu_imf = Aref x_k - Ax_k + Bref u_k
// Bu_imf = (Aref - A)x_k + Bref u_k
// u_imf = B^+ ((Aref - A)x_k + Bref u_k)
// u_imf = -B^+ (A - Aref)x_k + B^+ Bref u_k

// The first term makes the open-loop poles that of the reference
// system, and the second term makes the input behave like that of the
// reference system.
m_A = discB.solve(discA.minus(discAref)).times(-1.0);
m_B = discB.solve(discBref);

reset();
}

/**
* Returns the control input vector u.
*
* @return The control input.
*/
public Matrix<Inputs, N1> getU() {
return m_u;
}

/**
* Returns an element of the control input vector u.
*
* @param i Row of u.
* @return The row of the control input vector.
*/
public double getU(int i) {
return m_u.get(i, 0);
}

/** Resets the controller. */
public void reset() {
m_u.fill(0.0);
}

/**
* Returns the next output of the controller.
*
* @param x The current state x.
* @param u The current input for the original model.
* @return The next controller output.
*/
public Matrix<Inputs, N1> calculate(Matrix<States, N1> x, Matrix<Inputs, N1> u) {
m_u = m_A.times(x).plus(m_B.times(u));
return m_u;
}
}
137 changes: 137 additions & 0 deletions wpimath/src/main/native/include/frc/controller/ImplicitModelFollower.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// 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.

#pragma once

#include <frc/system/Discretization.h>
#include <frc/system/LinearSystem.h>

#include "Eigen/Core"
#include "Eigen/QR"
#include "units/time.h"

namespace frc {

/**
* Contains the controller coefficients and logic for an implicit model
* follower.
*
* Implicit model following lets us design a feedback controller that erases the
* dynamics of our system and makes it behave like some other system. This can
* be used to make a drivetrain more controllable during teleop driving by
* making it behave like a slower or more benign drivetrain.
*
* For more on the underlying math, read appendix B.3 in
* https://file.tavsys.net/control/controls-engineering-in-frc.pdf.
*/
template <int States, int Inputs>
class ImplicitModelFollower {
public:
/**
* Constructs a controller with the given coefficients and plant.
*
* @param plant The plant being controlled.
* @param plantRef The plant whose dynamics should be followed.
* @param dt Discretization timestep.
*/
template <int Outputs>
ImplicitModelFollower(const LinearSystem<States, Inputs, Outputs>& plant,
const LinearSystem<States, Inputs, Outputs>& plantRef,
units::second_t dt)
: ImplicitModelFollower<States, Inputs>(plant.A(), plant.B(),
plantRef.A(), plantRef.B(), dt) {}

/**
* Constructs a controller with the given coefficients and plant.
*
* @param A Continuous system matrix of the plant being controlled.
* @param B Continuous input matrix of the plant being controlled.
* @param Aref Continuous system matrix whose dynamics should be followed.
* @param Bref Continuous input matrix whose dynamics should be followed.
* @param dt Discretization timestep.
*/
ImplicitModelFollower(const Eigen::Matrix<double, States, States>& A,
const Eigen::Matrix<double, States, Inputs>& B,
const Eigen::Matrix<double, States, States>& Aref,
const Eigen::Matrix<double, States, Inputs>& Bref,
units::second_t dt) {
// Discretize real dynamics
Eigen::Matrix<double, States, States> discA;
Eigen::Matrix<double, States, Inputs> discB;
frc::DiscretizeAB<States, Inputs>(A, B, dt, &discA, &discB);

// Discretize desired dynamics
Eigen::Matrix<double, States, States> discAref;
Eigen::Matrix<double, States, Inputs> discBref;
frc::DiscretizeAB<States, Inputs>(Aref, Bref, dt, &discAref, &discBref);

// Find u_imf that makes real model match reference model.
//
// x_k+1 = Ax_k + Bu_imf
// z_k+1 = Aref z_k + Bref u_k
//
// Let x_k = z_k.
//
// x_k+1 = z_k+1
// Ax_k + Bu_imf = Aref x_k + Bref u_k
// Bu_imf = Aref x_k - Ax_k + Bref u_k
// Bu_imf = (Aref - A)x_k + Bref u_k
// u_imf = B^+ ((Aref - A)x_k + Bref u_k)
// u_imf = -B^+ (A - Aref)x_k + B^+ Bref u_k

// The first term makes the open-loop poles that of the reference
// system, and the second term makes the input behave like that of the
// reference system.
m_A = -discB.householderQr().solve(discA - discAref);
m_B = discB.householderQr().solve(discBref);

Reset();
}

/**
* Returns the control input vector u.
*
* @return The control input.
*/
const Eigen::Vector<double, Inputs>& U() const { return m_u; }

/**
* Returns an element of the control input vector u.
*
* @param i Row of u.
*
* @return The row of the control input vector.
*/
double U(int i) const { return m_u(i); }

/**
* Resets the controller.
*/
void Reset() { m_u.setZero(); }

/**
* Returns the next output of the controller.
*
* @param x The current state x.
* @param u The current input for the original model.
*/
Eigen::Vector<double, Inputs> Calculate(
const Eigen::Vector<double, States>& x,
const Eigen::Vector<double, Inputs>& u) {
m_u = m_A * x + m_B * u;
return m_u;
}

private:
// Computed controller output
Eigen::Vector<double, Inputs> m_u;

// State space conversion gain
Eigen::Matrix<double, Inputs, States> m_A;

// Input space conversion gain
Eigen::Matrix<double, Inputs, Inputs> m_B;
};

} // namespace frc
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ class LinearQuadraticRegulatorImpl {
*
* @return The row of the control input vector.
*/
double U(int i) const { return m_u(i, 0); }
double U(int i) const { return m_u(i); }

/**
* Resets the controller.
Expand Down

0 comments on commit b26f128

Please sign in to comment.