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.system;
006
007import edu.wpi.first.math.MathSharedStore;
008import edu.wpi.first.math.MathUsageId;
009import edu.wpi.first.math.Matrix;
010import edu.wpi.first.math.Num;
011import edu.wpi.first.math.StateSpaceUtil;
012import edu.wpi.first.math.controller.LinearPlantInversionFeedforward;
013import edu.wpi.first.math.controller.LinearQuadraticRegulator;
014import edu.wpi.first.math.estimator.KalmanFilter;
015import edu.wpi.first.math.numbers.N1;
016import java.util.function.Function;
017import org.ejml.MatrixDimensionException;
018import org.ejml.simple.SimpleMatrix;
019
020/**
021 * Combines a controller, feedforward, and observer for controlling a mechanism with full state
022 * feedback.
023 *
024 * <p>For everything in this file, "inputs" and "outputs" are defined from the perspective of the
025 * plant. This means U is an input and Y is an output (because you give the plant U (powers) and it
026 * gives you back a Y (sensor values)). This is the opposite of what they mean from the perspective
027 * of the controller (U is an output because that's what goes to the motors and Y is an input
028 * because that's what comes back from the sensors).
029 *
030 * <p>For more on the underlying math, read <a
031 * href="https://file.tavsys.net/control/controls-engineering-in-frc.pdf">https://file.tavsys.net/control/controls-engineering-in-frc.pdf</a>.
032 *
033 * @param <States> Number of states.
034 * @param <Inputs> Number of inputs.
035 * @param <Outputs> Number of outputs.
036 */
037public class LinearSystemLoop<States extends Num, Inputs extends Num, Outputs extends Num> {
038  private final LinearQuadraticRegulator<States, Inputs, Outputs> m_controller;
039  private final LinearPlantInversionFeedforward<States, Inputs, Outputs> m_feedforward;
040  private final KalmanFilter<States, Inputs, Outputs> m_observer;
041  private Matrix<States, N1> m_nextR;
042  private Function<Matrix<Inputs, N1>, Matrix<Inputs, N1>> m_clampFunction;
043
044  /**
045   * Constructs a state-space loop with the given plant, controller, and observer. By default, the
046   * initial reference is all zeros. Users should call reset with the initial system state before
047   * enabling the loop. This constructor assumes that the input(s) to this system are voltage.
048   *
049   * @param plant State-space plant.
050   * @param controller State-space controller.
051   * @param observer State-space observer.
052   * @param maxVoltageVolts The maximum voltage that can be applied. Commonly 12.
053   * @param dtSeconds The nominal timestep.
054   */
055  public LinearSystemLoop(
056      LinearSystem<States, Inputs, Outputs> plant,
057      LinearQuadraticRegulator<States, Inputs, Outputs> controller,
058      KalmanFilter<States, Inputs, Outputs> observer,
059      double maxVoltageVolts,
060      double dtSeconds) {
061    this(
062        controller,
063        new LinearPlantInversionFeedforward<>(plant, dtSeconds),
064        observer,
065        u -> StateSpaceUtil.desaturateInputVector(u, maxVoltageVolts));
066  }
067
068  /**
069   * Constructs a state-space loop with the given plant, controller, and observer. By default, the
070   * initial reference is all zeros. Users should call reset with the initial system state before
071   * enabling the loop.
072   *
073   * @param plant State-space plant.
074   * @param controller State-space controller.
075   * @param observer State-space observer.
076   * @param clampFunction The function used to clamp the input U.
077   * @param dtSeconds The nominal timestep.
078   */
079  public LinearSystemLoop(
080      LinearSystem<States, Inputs, Outputs> plant,
081      LinearQuadraticRegulator<States, Inputs, Outputs> controller,
082      KalmanFilter<States, Inputs, Outputs> observer,
083      Function<Matrix<Inputs, N1>, Matrix<Inputs, N1>> clampFunction,
084      double dtSeconds) {
085    this(
086        controller,
087        new LinearPlantInversionFeedforward<>(plant, dtSeconds),
088        observer,
089        clampFunction);
090  }
091
092  /**
093   * Constructs a state-space loop with the given controller, feedforward and observer. By default,
094   * the initial reference is all zeros. Users should call reset with the initial system state
095   * before enabling the loop.
096   *
097   * @param controller State-space controller.
098   * @param feedforward Plant inversion feedforward.
099   * @param observer State-space observer.
100   * @param maxVoltageVolts The maximum voltage that can be applied. Assumes that the inputs are
101   *     voltages.
102   */
103  public LinearSystemLoop(
104      LinearQuadraticRegulator<States, Inputs, Outputs> controller,
105      LinearPlantInversionFeedforward<States, Inputs, Outputs> feedforward,
106      KalmanFilter<States, Inputs, Outputs> observer,
107      double maxVoltageVolts) {
108    this(
109        controller,
110        feedforward,
111        observer,
112        u -> StateSpaceUtil.desaturateInputVector(u, maxVoltageVolts));
113  }
114
115  /**
116   * Constructs a state-space loop with the given controller, feedforward, and observer. By default,
117   * the initial reference is all zeros. Users should call reset with the initial system state
118   * before enabling the loop.
119   *
120   * @param controller State-space controller.
121   * @param feedforward Plant inversion feedforward.
122   * @param observer State-space observer.
123   * @param clampFunction The function used to clamp the input U.
124   */
125  public LinearSystemLoop(
126      LinearQuadraticRegulator<States, Inputs, Outputs> controller,
127      LinearPlantInversionFeedforward<States, Inputs, Outputs> feedforward,
128      KalmanFilter<States, Inputs, Outputs> observer,
129      Function<Matrix<Inputs, N1>, Matrix<Inputs, N1>> clampFunction) {
130    this.m_controller = controller;
131    this.m_feedforward = feedforward;
132    this.m_observer = observer;
133    this.m_clampFunction = clampFunction;
134
135    m_nextR = new Matrix<>(new SimpleMatrix(controller.getK().getNumCols(), 1));
136    reset(m_nextR);
137    MathSharedStore.getMathShared().reportUsage(MathUsageId.kSystem_LinearSystemLoop, 1);
138  }
139
140  /**
141   * Returns the observer's state estimate x-hat.
142   *
143   * @return the observer's state estimate x-hat.
144   */
145  public Matrix<States, N1> getXHat() {
146    return getObserver().getXhat();
147  }
148
149  /**
150   * Returns an element of the observer's state estimate x-hat.
151   *
152   * @param row Row of x-hat.
153   * @return the i-th element of the observer's state estimate x-hat.
154   */
155  public double getXHat(int row) {
156    return getObserver().getXhat(row);
157  }
158
159  /**
160   * Set the initial state estimate x-hat.
161   *
162   * @param xhat The initial state estimate x-hat.
163   */
164  public void setXHat(Matrix<States, N1> xhat) {
165    getObserver().setXhat(xhat);
166  }
167
168  /**
169   * Set an element of the initial state estimate x-hat.
170   *
171   * @param row Row of x-hat.
172   * @param value Value for element of x-hat.
173   */
174  public void setXHat(int row, double value) {
175    getObserver().setXhat(row, value);
176  }
177
178  /**
179   * Returns an element of the controller's next reference r.
180   *
181   * @param row Row of r.
182   * @return the element i of the controller's next reference r.
183   */
184  public double getNextR(int row) {
185    return getNextR().get(row, 0);
186  }
187
188  /**
189   * Returns the controller's next reference r.
190   *
191   * @return the controller's next reference r.
192   */
193  public Matrix<States, N1> getNextR() {
194    return m_nextR;
195  }
196
197  /**
198   * Set the next reference r.
199   *
200   * @param nextR Next reference.
201   */
202  public void setNextR(Matrix<States, N1> nextR) {
203    m_nextR = nextR;
204  }
205
206  /**
207   * Set the next reference r.
208   *
209   * @param nextR Next reference.
210   */
211  public void setNextR(double... nextR) {
212    if (nextR.length != m_nextR.getNumRows()) {
213      throw new MatrixDimensionException(
214          String.format(
215              "The next reference does not have the "
216                  + "correct number of entries! Expected %s, but got %s.",
217              m_nextR.getNumRows(), nextR.length));
218    }
219    m_nextR = new Matrix<>(new SimpleMatrix(m_nextR.getNumRows(), 1, true, nextR));
220  }
221
222  /**
223   * Returns the controller's calculated control input u plus the calculated feedforward u_ff.
224   *
225   * @return the calculated control input u.
226   */
227  public Matrix<Inputs, N1> getU() {
228    return clampInput(m_controller.getU().plus(m_feedforward.getUff()));
229  }
230
231  /**
232   * Returns an element of the controller's calculated control input u.
233   *
234   * @param row Row of u.
235   * @return the calculated control input u at the row i.
236   */
237  public double getU(int row) {
238    return getU().get(row, 0);
239  }
240
241  /**
242   * Return the controller used internally.
243   *
244   * @return the controller used internally.
245   */
246  public LinearQuadraticRegulator<States, Inputs, Outputs> getController() {
247    return m_controller;
248  }
249
250  /**
251   * Return the feedforward used internally.
252   *
253   * @return the feedforward used internally.
254   */
255  public LinearPlantInversionFeedforward<States, Inputs, Outputs> getFeedforward() {
256    return m_feedforward;
257  }
258
259  /**
260   * Return the observer used internally.
261   *
262   * @return the observer used internally.
263   */
264  public KalmanFilter<States, Inputs, Outputs> getObserver() {
265    return m_observer;
266  }
267
268  /**
269   * Zeroes reference r and controller output u. The previous reference of the
270   * PlantInversionFeedforward and the initial state estimate of the KalmanFilter are set to the
271   * initial state provided.
272   *
273   * @param initialState The initial state.
274   */
275  public final void reset(Matrix<States, N1> initialState) {
276    m_nextR.fill(0.0);
277    m_controller.reset();
278    m_feedforward.reset(initialState);
279    m_observer.setXhat(initialState);
280  }
281
282  /**
283   * Returns difference between reference r and current state x-hat.
284   *
285   * @return The state error matrix.
286   */
287  public Matrix<States, N1> getError() {
288    return getController().getR().minus(m_observer.getXhat());
289  }
290
291  /**
292   * Returns difference between reference r and current state x-hat.
293   *
294   * @param index The index of the error matrix to return.
295   * @return The error at that index.
296   */
297  public double getError(int index) {
298    return getController().getR().minus(m_observer.getXhat()).get(index, 0);
299  }
300
301  /**
302   * Get the function used to clamp the input u.
303   *
304   * @return The clamping function.
305   */
306  public Function<Matrix<Inputs, N1>, Matrix<Inputs, N1>> getClampFunction() {
307    return m_clampFunction;
308  }
309
310  /**
311   * Set the clamping function used to clamp inputs.
312   *
313   * @param clampFunction The clamping function.
314   */
315  public void setClampFunction(Function<Matrix<Inputs, N1>, Matrix<Inputs, N1>> clampFunction) {
316    this.m_clampFunction = clampFunction;
317  }
318
319  /**
320   * Correct the state estimate x-hat using the measurements in y.
321   *
322   * @param y Measurement vector.
323   */
324  public void correct(Matrix<Outputs, N1> y) {
325    getObserver().correct(getU(), y);
326  }
327
328  /**
329   * Sets new controller output, projects model forward, and runs observer prediction.
330   *
331   * <p>After calling this, the user should send the elements of u to the actuators.
332   *
333   * @param dtSeconds Timestep for model update.
334   */
335  public void predict(double dtSeconds) {
336    var u =
337        clampInput(
338            m_controller
339                .calculate(getObserver().getXhat(), m_nextR)
340                .plus(m_feedforward.calculate(m_nextR)));
341    getObserver().predict(u, dtSeconds);
342  }
343
344  /**
345   * Clamp the input u to the min and max.
346   *
347   * @param unclampedU The input to clamp.
348   * @return The clamped input.
349   */
350  public Matrix<Inputs, N1> clampInput(Matrix<Inputs, N1> unclampedU) {
351    return m_clampFunction.apply(unclampedU);
352  }
353}