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.Nat;
009import edu.wpi.first.math.Num;
010import edu.wpi.first.math.numbers.N1;
011import edu.wpi.first.math.system.NumericalJacobian;
012import java.util.function.BiFunction;
013import java.util.function.Function;
014
015/**
016 * Constructs a control-affine plant inversion model-based feedforward from given model dynamics.
017 *
018 * <p>If given the vector valued function as f(x, u) where x is the state vector and u is the input
019 * vector, the B matrix(continuous input matrix) is calculated through a {@link NumericalJacobian}.
020 * In this case f has to be control-affine (of the form f(x) + Bu).
021 *
022 * <p>The feedforward is calculated as <strong> u_ff = B<sup>+</sup> (rDot - f(x))</strong>, where
023 * <strong> B<sup>+</sup> </strong> is the pseudoinverse of B.
024 *
025 * <p>This feedforward does not account for a dynamic B matrix, B is either determined or supplied
026 * when the feedforward is created and remains constant.
027 *
028 * <p>For more on the underlying math, read <a
029 * href="https://file.tavsys.net/control/controls-engineering-in-frc.pdf">https://file.tavsys.net/control/controls-engineering-in-frc.pdf</a>.
030 *
031 * @param <States> Number of states.
032 * @param <Inputs> Number of inputs.
033 */
034public class ControlAffinePlantInversionFeedforward<States extends Num, Inputs extends Num> {
035  /** The current reference state. */
036  private Matrix<States, N1> m_r;
037
038  /** The computed feedforward. */
039  private Matrix<Inputs, N1> m_uff;
040
041  private final Matrix<States, Inputs> m_B;
042
043  private final Nat<Inputs> m_inputs;
044
045  private final double m_dt;
046
047  /** The model dynamics. */
048  private final BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<States, N1>> m_f;
049
050  /**
051   * Constructs a feedforward with given model dynamics as a function of state and input.
052   *
053   * @param states A {@link Nat} representing the number of states.
054   * @param inputs A {@link Nat} representing the number of inputs.
055   * @param f A vector-valued function of x, the state, and u, the input, that returns the
056   *     derivative of the state vector. HAS to be control-affine (of the form f(x) + Bu).
057   * @param dtSeconds The timestep between calls of calculate().
058   */
059  public ControlAffinePlantInversionFeedforward(
060      Nat<States> states,
061      Nat<Inputs> inputs,
062      BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<States, N1>> f,
063      double dtSeconds) {
064    this.m_dt = dtSeconds;
065    this.m_f = f;
066    this.m_inputs = inputs;
067
068    this.m_B =
069        NumericalJacobian.numericalJacobianU(
070            states, inputs, m_f, new Matrix<>(states, Nat.N1()), new Matrix<>(inputs, Nat.N1()));
071
072    m_r = new Matrix<>(states, Nat.N1());
073    m_uff = new Matrix<>(inputs, Nat.N1());
074
075    reset();
076  }
077
078  /**
079   * Constructs a feedforward with given model dynamics as a function of state, and the plant's
080   * B(continuous input matrix) matrix.
081   *
082   * @param states A {@link Nat} representing the number of states.
083   * @param inputs A {@link Nat} representing the number of inputs.
084   * @param f A vector-valued function of x, the state, that returns the derivative of the state
085   *     vector.
086   * @param B Continuous input matrix of the plant being controlled.
087   * @param dtSeconds The timestep between calls of calculate().
088   */
089  public ControlAffinePlantInversionFeedforward(
090      Nat<States> states,
091      Nat<Inputs> inputs,
092      Function<Matrix<States, N1>, Matrix<States, N1>> f,
093      Matrix<States, Inputs> B,
094      double dtSeconds) {
095    this.m_dt = dtSeconds;
096    this.m_inputs = inputs;
097
098    this.m_f = (x, u) -> f.apply(x);
099    this.m_B = B;
100
101    m_r = new Matrix<>(states, Nat.N1());
102    m_uff = new Matrix<>(inputs, Nat.N1());
103
104    reset();
105  }
106
107  /**
108   * Returns the previously calculated feedforward as an input vector.
109   *
110   * @return The calculated feedforward.
111   */
112  public Matrix<Inputs, N1> getUff() {
113    return m_uff;
114  }
115
116  /**
117   * Returns an element of the previously calculated feedforward.
118   *
119   * @param row Row of uff.
120   * @return The row of the calculated feedforward.
121   */
122  public double getUff(int row) {
123    return m_uff.get(row, 0);
124  }
125
126  /**
127   * Returns the current reference vector r.
128   *
129   * @return The current reference vector.
130   */
131  public Matrix<States, N1> getR() {
132    return m_r;
133  }
134
135  /**
136   * Returns an element of the current reference vector r.
137   *
138   * @param row Row of r.
139   * @return The row of the current reference vector.
140   */
141  public double getR(int row) {
142    return m_r.get(row, 0);
143  }
144
145  /**
146   * Resets the feedforward with a specified initial state vector.
147   *
148   * @param initialState The initial state vector.
149   */
150  public final void reset(Matrix<States, N1> initialState) {
151    m_r = initialState;
152    m_uff.fill(0.0);
153  }
154
155  /** Resets the feedforward with a zero initial state vector. */
156  public final void reset() {
157    m_r.fill(0.0);
158    m_uff.fill(0.0);
159  }
160
161  /**
162   * Calculate the feedforward with only the desired future reference. This uses the internally
163   * stored "current" reference.
164   *
165   * <p>If this method is used the initial state of the system is the one set using {@link
166   * LinearPlantInversionFeedforward#reset(Matrix)}. If the initial state is not set it defaults to
167   * a zero vector.
168   *
169   * @param nextR The reference state of the future timestep (k + dt).
170   * @return The calculated feedforward.
171   */
172  public Matrix<Inputs, N1> calculate(Matrix<States, N1> nextR) {
173    return calculate(m_r, nextR);
174  }
175
176  /**
177   * Calculate the feedforward with current and future reference vectors.
178   *
179   * @param r The reference state of the current timestep (k).
180   * @param nextR The reference state of the future timestep (k + dt).
181   * @return The calculated feedforward.
182   */
183  public Matrix<Inputs, N1> calculate(Matrix<States, N1> r, Matrix<States, N1> nextR) {
184    var rDot = nextR.minus(r).div(m_dt);
185
186    // ṙ = f(r) + Bu
187    // Bu = ṙ − f(r)
188    // u = B⁺(ṙ − f(r))
189    m_uff = m_B.solve(rDot.minus(m_f.apply(r, new Matrix<>(m_inputs, Nat.N1()))));
190
191    m_r = nextR;
192    return m_uff;
193  }
194}