001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.math.controller;
006
007import edu.wpi.first.math.Matrix;
008import edu.wpi.first.math.Num;
009import edu.wpi.first.math.numbers.N1;
010import edu.wpi.first.math.system.Discretization;
011import edu.wpi.first.math.system.LinearSystem;
012import org.ejml.simple.SimpleMatrix;
013
014/**
015 * Constructs a plant inversion model-based feedforward from a {@link LinearSystem}.
016 *
017 * <p>The feedforward is calculated as <strong> u_ff = B<sup>+</sup> (r_k+1 - A r_k) </strong>,
018 * where <strong> B<sup>+</sup> </strong> is the pseudoinverse of B.
019 *
020 * <p>For more on the underlying math, read <a
021 * href="https://file.tavsys.net/control/controls-engineering-in-frc.pdf">https://file.tavsys.net/control/controls-engineering-in-frc.pdf</a>.
022 */
023public class LinearPlantInversionFeedforward<
024    States extends Num, Inputs extends Num, Outputs extends Num> {
025  /** The current reference state. */
026  private Matrix<States, N1> m_r;
027
028  /** The computed feedforward. */
029  private Matrix<Inputs, N1> m_uff;
030
031  private final Matrix<States, Inputs> m_B;
032
033  private final Matrix<States, States> m_A;
034
035  /**
036   * Constructs a feedforward with the given plant.
037   *
038   * @param plant The plant being controlled.
039   * @param dtSeconds Discretization timestep.
040   */
041  public LinearPlantInversionFeedforward(
042      LinearSystem<States, Inputs, Outputs> plant, double dtSeconds) {
043    this(plant.getA(), plant.getB(), dtSeconds);
044  }
045
046  /**
047   * Constructs a feedforward with the given coefficients.
048   *
049   * @param A Continuous system matrix of the plant being controlled.
050   * @param B Continuous input matrix of the plant being controlled.
051   * @param dtSeconds Discretization timestep.
052   */
053  public LinearPlantInversionFeedforward(
054      Matrix<States, States> A, Matrix<States, Inputs> B, double dtSeconds) {
055    var discABPair = Discretization.discretizeAB(A, B, dtSeconds);
056    this.m_A = discABPair.getFirst();
057    this.m_B = discABPair.getSecond();
058
059    m_r = new Matrix<>(new SimpleMatrix(B.getNumRows(), 1));
060    m_uff = new Matrix<>(new SimpleMatrix(B.getNumCols(), 1));
061
062    reset();
063  }
064
065  /**
066   * Returns the previously calculated feedforward as an input vector.
067   *
068   * @return The calculated feedforward.
069   */
070  public Matrix<Inputs, N1> getUff() {
071    return m_uff;
072  }
073
074  /**
075   * Returns an element of the previously calculated feedforward.
076   *
077   * @param row Row of uff.
078   * @return The row of the calculated feedforward.
079   */
080  public double getUff(int row) {
081    return m_uff.get(row, 0);
082  }
083
084  /**
085   * Returns the current reference vector r.
086   *
087   * @return The current reference vector.
088   */
089  public Matrix<States, N1> getR() {
090    return m_r;
091  }
092
093  /**
094   * Returns an element of the current reference vector r.
095   *
096   * @param row Row of r.
097   * @return The row of the current reference vector.
098   */
099  public double getR(int row) {
100    return m_r.get(row, 0);
101  }
102
103  /**
104   * Resets the feedforward with a specified initial state vector.
105   *
106   * @param initialState The initial state vector.
107   */
108  public final void reset(Matrix<States, N1> initialState) {
109    m_r = initialState;
110    m_uff.fill(0.0);
111  }
112
113  /** Resets the feedforward with a zero initial state vector. */
114  public final void reset() {
115    m_r.fill(0.0);
116    m_uff.fill(0.0);
117  }
118
119  /**
120   * Calculate the feedforward with only the desired future reference. This uses the internally
121   * stored "current" reference.
122   *
123   * <p>If this method is used the initial state of the system is the one set using {@link
124   * LinearPlantInversionFeedforward#reset(Matrix)}. If the initial state is not set it defaults to
125   * a zero vector.
126   *
127   * @param nextR The reference state of the future timestep (k + dt).
128   * @return The calculated feedforward.
129   */
130  public Matrix<Inputs, N1> calculate(Matrix<States, N1> nextR) {
131    return calculate(m_r, nextR);
132  }
133
134  /**
135   * Calculate the feedforward with current and future reference vectors.
136   *
137   * @param r The reference state of the current timestep (k).
138   * @param nextR The reference state of the future timestep (k + dt).
139   * @return The calculated feedforward.
140   */
141  public Matrix<Inputs, N1> calculate(Matrix<States, N1> r, Matrix<States, N1> nextR) {
142    // rₖ₊₁ = Arₖ + Buₖ
143    // Buₖ = rₖ₊₁ − Arₖ
144    // uₖ = B⁺(rₖ₊₁ − Arₖ)
145    m_uff = new Matrix<>(m_B.solve(nextR.minus(m_A.times(r))));
146
147    m_r = nextR;
148    return m_uff;
149  }
150}