blob: 6ebd32e322ba44f8973e6ae1a59dbe8d356738e3 [file] [log] [blame]
#ifndef FRC971_CONTROL_LOOPS_STATE_FEEDBACK_LOOP_H_
#define FRC971_CONTROL_LOOPS_STATE_FEEDBACK_LOOP_H_
#include <assert.h>
#include <vector>
#include <memory>
#include <iostream>
#include "Eigen/Dense"
#include "aos/common/logging/logging.h"
#include "aos/common/macros.h"
// For everything in this file, "inputs" and "outputs" are defined from the
// perspective of the plant. This means U is an input and Y is an output
// (because you give the plant U (powers) and it gives you back a Y (sensor
// values). This is the opposite of what they mean from the perspective of the
// controller (U is an output because that's what goes to the motors and Y is an
// input because that's what comes back from the sensors).
template <int number_of_states, int number_of_inputs, int number_of_outputs>
class StateFeedbackPlantCoefficients final {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
StateFeedbackPlantCoefficients(const StateFeedbackPlantCoefficients &other)
: A_(other.A()),
B_(other.B()),
C_(other.C()),
D_(other.D()),
U_min_(other.U_min()),
U_max_(other.U_max()) {
}
StateFeedbackPlantCoefficients(
const Eigen::Matrix<double, number_of_states, number_of_states> &A,
const Eigen::Matrix<double, number_of_states, number_of_inputs> &B,
const Eigen::Matrix<double, number_of_outputs, number_of_states> &C,
const Eigen::Matrix<double, number_of_outputs, number_of_inputs> &D,
const Eigen::Matrix<double, number_of_inputs, 1> &U_max,
const Eigen::Matrix<double, number_of_inputs, 1> &U_min)
: A_(A),
B_(B),
C_(C),
D_(D),
U_min_(U_min),
U_max_(U_max) {
}
const Eigen::Matrix<double, number_of_states, number_of_states> &A() const {
return A_;
}
double A(int i, int j) const { return A()(i, j); }
const Eigen::Matrix<double, number_of_states, number_of_inputs> &B() const {
return B_;
}
double B(int i, int j) const { return B()(i, j); }
const Eigen::Matrix<double, number_of_outputs, number_of_states> &C() const {
return C_;
}
double C(int i, int j) const { return C()(i, j); }
const Eigen::Matrix<double, number_of_outputs, number_of_inputs> &D() const {
return D_;
}
double D(int i, int j) const { return D()(i, j); }
const Eigen::Matrix<double, number_of_inputs, 1> &U_min() const {
return U_min_;
}
double U_min(int i, int j) const { return U_min()(i, j); }
const Eigen::Matrix<double, number_of_inputs, 1> &U_max() const {
return U_max_;
}
double U_max(int i, int j) const { return U_max()(i, j); }
private:
const Eigen::Matrix<double, number_of_states, number_of_states> A_;
const Eigen::Matrix<double, number_of_states, number_of_inputs> B_;
const Eigen::Matrix<double, number_of_outputs, number_of_states> C_;
const Eigen::Matrix<double, number_of_outputs, number_of_inputs> D_;
const Eigen::Matrix<double, number_of_inputs, 1> U_min_;
const Eigen::Matrix<double, number_of_inputs, 1> U_max_;
StateFeedbackPlantCoefficients &operator=(
StateFeedbackPlantCoefficients other) = delete;
};
template <int number_of_states, int number_of_inputs, int number_of_outputs>
class StateFeedbackPlant {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
StateFeedbackPlant(
::std::vector< ::std::unique_ptr<StateFeedbackPlantCoefficients<
number_of_states, number_of_inputs, number_of_outputs>>> *
coefficients)
: coefficients_(::std::move(*coefficients)), plant_index_(0) {
Reset();
}
StateFeedbackPlant(StateFeedbackPlant &&other)
: plant_index_(other.plant_index_) {
::std::swap(coefficients_, other.coefficients_);
X_.swap(other.X_);
Y_.swap(other.Y_);
U_.swap(other.U_);
}
virtual ~StateFeedbackPlant() {}
const Eigen::Matrix<double, number_of_states, number_of_states> &A() const {
return coefficients().A();
}
double A(int i, int j) const { return A()(i, j); }
const Eigen::Matrix<double, number_of_states, number_of_inputs> &B() const {
return coefficients().B();
}
double B(int i, int j) const { return B()(i, j); }
const Eigen::Matrix<double, number_of_outputs, number_of_states> &C() const {
return coefficients().C();
}
double C(int i, int j) const { return C()(i, j); }
const Eigen::Matrix<double, number_of_outputs, number_of_inputs> &D() const {
return coefficients().D();
}
double D(int i, int j) const { return D()(i, j); }
const Eigen::Matrix<double, number_of_inputs, 1> &U_min() const {
return coefficients().U_min();
}
double U_min(int i, int j) const { return U_min()(i, j); }
const Eigen::Matrix<double, number_of_inputs, 1> &U_max() const {
return coefficients().U_max();
}
double U_max(int i, int j) const { return U_max()(i, j); }
const Eigen::Matrix<double, number_of_states, 1> &X() const { return X_; }
double X(int i, int j) const { return X()(i, j); }
const Eigen::Matrix<double, number_of_outputs, 1> &Y() const { return Y_; }
double Y(int i, int j) const { return Y()(i, j); }
const Eigen::Matrix<double, number_of_inputs, 1> &U() const { return U_; }
double U(int i, int j) const { return U()(i, j); }
Eigen::Matrix<double, number_of_states, 1> &mutable_X() { return X_; }
double &mutable_X(int i, int j) { return mutable_X()(i, j); }
Eigen::Matrix<double, number_of_outputs, 1> &mutable_Y() { return Y_; }
double &mutable_Y(int i, int j) { return mutable_Y()(i, j); }
Eigen::Matrix<double, number_of_inputs, 1> &mutable_U() { return U_; }
double &mutable_U(int i, int j) { return mutable_U()(i, j); }
const StateFeedbackPlantCoefficients<
number_of_states, number_of_inputs, number_of_outputs>
&coefficients() const {
return *coefficients_[plant_index_];
}
int plant_index() const { return plant_index_; }
void set_plant_index(int plant_index) {
if (plant_index < 0) {
plant_index_ = 0;
} else if (plant_index >= static_cast<int>(coefficients_.size())) {
plant_index_ = static_cast<int>(coefficients_.size()) - 1;
} else {
plant_index_ = plant_index;
}
}
void Reset() {
X_.setZero();
Y_.setZero();
U_.setZero();
}
// Assert that U is within the hardware range.
virtual void CheckU() {
for (int i = 0; i < kNumInputs; ++i) {
assert(U(i, 0) <= U_max(i, 0) + 0.00001);
assert(U(i, 0) >= U_min(i, 0) - 0.00001);
}
}
// Computes the new X and Y given the control input.
void Update() {
// Powers outside of the range are more likely controller bugs than things
// that the plant should deal with.
CheckU();
X_ = A() * X() + B() * U();
Y_ = C() * X() + D() * U();
}
protected:
// these are accessible from non-templated subclasses
static const int kNumStates = number_of_states;
static const int kNumOutputs = number_of_outputs;
static const int kNumInputs = number_of_inputs;
private:
Eigen::Matrix<double, number_of_states, 1> X_;
Eigen::Matrix<double, number_of_outputs, 1> Y_;
Eigen::Matrix<double, number_of_inputs, 1> U_;
::std::vector< ::std::unique_ptr<StateFeedbackPlantCoefficients<
number_of_states, number_of_inputs, number_of_outputs>>> coefficients_;
int plant_index_;
DISALLOW_COPY_AND_ASSIGN(StateFeedbackPlant);
};
// A Controller is a structure which holds a plant and the K and L matrices.
// This is designed such that multiple controllers can share one set of state to
// support gain scheduling easily.
template <int number_of_states, int number_of_inputs, int number_of_outputs>
struct StateFeedbackController final {
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
const Eigen::Matrix<double, number_of_states, number_of_outputs> L;
const Eigen::Matrix<double, number_of_inputs, number_of_states> K;
const Eigen::Matrix<double, number_of_states, number_of_states> A_inv;
StateFeedbackPlantCoefficients<number_of_states, number_of_inputs,
number_of_outputs> plant;
StateFeedbackController(
const Eigen::Matrix<double, number_of_states, number_of_outputs> &L,
const Eigen::Matrix<double, number_of_inputs, number_of_states> &K,
const Eigen::Matrix<double, number_of_states, number_of_states> &A_inv,
const StateFeedbackPlantCoefficients<number_of_states, number_of_inputs,
number_of_outputs> &plant)
: L(L),
K(K),
A_inv(A_inv),
plant(plant) {
}
};
template <int number_of_states, int number_of_inputs, int number_of_outputs>
class StateFeedbackLoop {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
StateFeedbackLoop(const StateFeedbackController<
number_of_states, number_of_inputs, number_of_outputs> &controller)
: controller_index_(0) {
controllers_.emplace_back(new StateFeedbackController<
number_of_states, number_of_inputs, number_of_outputs>(controller));
Reset();
}
StateFeedbackLoop(
const Eigen::Matrix<double, number_of_states, number_of_outputs> &L,
const Eigen::Matrix<double, number_of_inputs, number_of_states> &K,
const Eigen::Matrix<double, number_of_states, number_of_states> &A_inv,
const StateFeedbackPlantCoefficients<number_of_states, number_of_inputs,
number_of_outputs> &plant)
: controller_index_(0) {
controllers_.emplace_back(
new StateFeedbackController<number_of_states, number_of_inputs,
number_of_outputs>(L, K, A_inv, plant));
Reset();
}
StateFeedbackLoop(::std::vector< ::std::unique_ptr<StateFeedbackController<
number_of_states, number_of_inputs, number_of_outputs>>> *controllers)
: controllers_(::std::move(*controllers)), controller_index_(0) {
Reset();
}
StateFeedbackLoop(StateFeedbackLoop &&other) {
X_hat_.swap(other.X_hat_);
R_.swap(other.R_);
U_.swap(other.U_);
U_uncapped_.swap(other.U_uncapped_);
::std::swap(controllers_, other.controllers_);
controller_index_ = other.controller_index_;
}
virtual ~StateFeedbackLoop() {}
const Eigen::Matrix<double, number_of_states, number_of_states> &A() const {
return controller().plant.A();
}
double A(int i, int j) const { return A()(i, j); }
const Eigen::Matrix<double, number_of_states, number_of_inputs> &B() const {
return controller().plant.B();
}
const Eigen::Matrix<double, number_of_states, number_of_states> &A_inv() const {
return controller().A_inv;
}
double A_inv(int i, int j) const { return A_inv()(i, j); }
double B(int i, int j) const { return B()(i, j); }
const Eigen::Matrix<double, number_of_outputs, number_of_states> &C() const {
return controller().plant.C();
}
double C(int i, int j) const { return C()(i, j); }
const Eigen::Matrix<double, number_of_outputs, number_of_inputs> &D() const {
return controller().plant.D();
}
double D(int i, int j) const { return D()(i, j); }
const Eigen::Matrix<double, number_of_inputs, 1> &U_min() const {
return controller().plant.U_min();
}
double U_min(int i, int j) const { return U_min()(i, j); }
const Eigen::Matrix<double, number_of_inputs, 1> &U_max() const {
return controller().plant.U_max();
}
double U_max(int i, int j) const { return U_max()(i, j); }
const Eigen::Matrix<double, number_of_inputs, number_of_states> &K() const {
return controller().K;
}
double K(int i, int j) const { return K()(i, j); }
const Eigen::Matrix<double, number_of_states, number_of_outputs> &L() const {
return controller().L;
}
double L(int i, int j) const { return L()(i, j); }
const Eigen::Matrix<double, number_of_states, 1> &X_hat() const {
return X_hat_;
}
double X_hat(int i, int j) const { return X_hat()(i, j); }
const Eigen::Matrix<double, number_of_states, 1> &R() const { return R_; }
double R(int i, int j) const { return R()(i, j); }
const Eigen::Matrix<double, number_of_inputs, 1> &U() const { return U_; }
double U(int i, int j) const { return U()(i, j); }
const Eigen::Matrix<double, number_of_inputs, 1> &U_uncapped() const {
return U_uncapped_;
}
double U_uncapped(int i, int j) const { return U_uncapped()(i, j); }
Eigen::Matrix<double, number_of_states, 1> &mutable_X_hat() { return X_hat_; }
double &mutable_X_hat(int i, int j) { return mutable_X_hat()(i, j); }
Eigen::Matrix<double, number_of_states, 1> &mutable_R() { return R_; }
double &mutable_R(int i, int j) { return mutable_R()(i, j); }
Eigen::Matrix<double, number_of_inputs, 1> &mutable_U() { return U_; }
double &mutable_U(int i, int j) { return mutable_U()(i, j); }
Eigen::Matrix<double, number_of_inputs, 1> &mutable_U_uncapped() {
return U_uncapped_;
}
double &mutable_U_uncapped(int i, int j) {
return mutable_U_uncapped()(i, j);
}
const StateFeedbackController<number_of_states, number_of_inputs,
number_of_outputs> &controller() const {
return *controllers_[controller_index_];
}
const StateFeedbackController<number_of_states, number_of_inputs,
number_of_outputs> &controller(
int index) const {
return *controllers_[index];
}
void Reset() {
X_hat_.setZero();
R_.setZero();
U_.setZero();
U_uncapped_.setZero();
}
// If U is outside the hardware range, limit it before the plant tries to use
// it.
virtual void CapU() {
for (int i = 0; i < kNumInputs; ++i) {
if (U(i, 0) > U_max(i, 0)) {
U_(i, 0) = U_max(i, 0);
} else if (U(i, 0) < U_min(i, 0)) {
U_(i, 0) = U_min(i, 0);
}
}
}
// Corrects X_hat given the observation in Y.
void Correct(const Eigen::Matrix<double, number_of_outputs, 1> &Y) {
X_hat_ += A_inv() * L() * (Y - C() * X_hat_ - D() * U());
}
// stop_motors is whether or not to output all 0s.
void Update(bool stop_motors) {
if (stop_motors) {
U_.setZero();
U_uncapped_.setZero();
} else {
U_ = U_uncapped_ = K() * (R() - X_hat());
CapU();
}
UpdateObserver();
}
void UpdateObserver() {
X_hat_ = A() * X_hat() + B() * U();
}
// Sets the current controller to be index, clamped to be within range.
void set_controller_index(int index) {
if (index < 0) {
controller_index_ = 0;
} else if (index >= static_cast<int>(controllers_.size())) {
controller_index_ = static_cast<int>(controllers_.size()) - 1;
} else {
controller_index_ = index;
}
}
int controller_index() const { return controller_index_; }
protected:
::std::vector< ::std::unique_ptr<StateFeedbackController<
number_of_states, number_of_inputs, number_of_outputs>>> controllers_;
// These are accessible from non-templated subclasses.
static const int kNumStates = number_of_states;
static const int kNumOutputs = number_of_outputs;
static const int kNumInputs = number_of_inputs;
private:
Eigen::Matrix<double, number_of_states, 1> X_hat_;
Eigen::Matrix<double, number_of_states, 1> R_;
Eigen::Matrix<double, number_of_inputs, 1> U_;
Eigen::Matrix<double, number_of_inputs, 1> U_uncapped_;
int controller_index_;
DISALLOW_COPY_AND_ASSIGN(StateFeedbackLoop);
};
#endif // FRC971_CONTROL_LOOPS_STATE_FEEDBACK_LOOP_H_