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.wpilibj.simulation;
006
007import edu.wpi.first.math.Matrix;
008import edu.wpi.first.math.Num;
009import edu.wpi.first.math.StateSpaceUtil;
010import edu.wpi.first.math.numbers.N1;
011import edu.wpi.first.math.system.LinearSystem;
012import edu.wpi.first.wpilibj.RobotController;
013import org.ejml.MatrixDimensionException;
014import org.ejml.simple.SimpleMatrix;
015
016/**
017 * This class helps simulate linear systems. To use this class, do the following in the {@link
018 * edu.wpi.first.wpilibj.IterativeRobotBase#simulationPeriodic} method.
019 *
020 * <p>Call {@link #setInput(double...)} with the inputs to the system (usually voltage).
021 *
022 * <p>Call {@link #update} to update the simulation.
023 *
024 * <p>Set simulated sensor readings with the simulated positions in {@link #getOutput()}
025 *
026 * @param <States> The number of states of the system.
027 * @param <Inputs> The number of inputs to the system.
028 * @param <Outputs> The number of outputs of the system.
029 */
030@SuppressWarnings("ClassTypeParameterName")
031public class LinearSystemSim<States extends Num, Inputs extends Num, Outputs extends Num> {
032  // The plant that represents the linear system.
033  protected final LinearSystem<States, Inputs, Outputs> m_plant;
034
035  // Variables for state, output, and input.
036  @SuppressWarnings("MemberName")
037  protected Matrix<States, N1> m_x;
038
039  @SuppressWarnings("MemberName")
040  protected Matrix<Outputs, N1> m_y;
041
042  @SuppressWarnings("MemberName")
043  protected Matrix<Inputs, N1> m_u;
044
045  // The standard deviations of measurements, used for adding noise
046  // to the measurements.
047  protected final Matrix<Outputs, N1> m_measurementStdDevs;
048
049  /**
050   * Creates a simulated generic linear system.
051   *
052   * @param system The system to simulate.
053   */
054  public LinearSystemSim(LinearSystem<States, Inputs, Outputs> system) {
055    this(system, null);
056  }
057
058  /**
059   * Creates a simulated generic linear system with measurement noise.
060   *
061   * @param system The system being controlled.
062   * @param measurementStdDevs Standard deviations of measurements. Can be null if no noise is
063   *     desired.
064   */
065  public LinearSystemSim(
066      LinearSystem<States, Inputs, Outputs> system, Matrix<Outputs, N1> measurementStdDevs) {
067    this.m_plant = system;
068    this.m_measurementStdDevs = measurementStdDevs;
069
070    m_x = new Matrix<>(new SimpleMatrix(system.getA().getNumRows(), 1));
071    m_u = new Matrix<>(new SimpleMatrix(system.getB().getNumCols(), 1));
072    m_y = new Matrix<>(new SimpleMatrix(system.getC().getNumRows(), 1));
073  }
074
075  /**
076   * Updates the simulation.
077   *
078   * @param dtSeconds The time between updates.
079   */
080  @SuppressWarnings("LocalVariableName")
081  public void update(double dtSeconds) {
082    // Update X. By default, this is the linear system dynamics X = Ax + Bu
083    m_x = updateX(m_x, m_u, dtSeconds);
084
085    // y = cx + du
086    m_y = m_plant.calculateY(m_x, m_u);
087
088    // Add measurement noise.
089    if (m_measurementStdDevs != null) {
090      m_y = m_y.plus(StateSpaceUtil.makeWhiteNoiseVector(m_measurementStdDevs));
091    }
092  }
093
094  /**
095   * Returns the current output of the plant.
096   *
097   * @return The current output of the plant.
098   */
099  public Matrix<Outputs, N1> getOutput() {
100    return m_y;
101  }
102
103  /**
104   * Returns an element of the current output of the plant.
105   *
106   * @param row The row to return.
107   * @return An element of the current output of the plant.
108   */
109  public double getOutput(int row) {
110    return m_y.get(row, 0);
111  }
112
113  /**
114   * Sets the system inputs (usually voltages).
115   *
116   * @param u The system inputs.
117   */
118  @SuppressWarnings("ParameterName")
119  public void setInput(Matrix<Inputs, N1> u) {
120    this.m_u = clampInput(u);
121  }
122
123  /**
124   * Sets the system inputs.
125   *
126   * @param row The row in the input matrix to set.
127   * @param value The value to set the row to.
128   */
129  public void setInput(int row, double value) {
130    m_u.set(row, 0, value);
131    m_u = clampInput(m_u);
132  }
133
134  /**
135   * Sets the system inputs.
136   *
137   * @param u An array of doubles that represent the inputs of the system.
138   */
139  @SuppressWarnings("ParameterName")
140  public void setInput(double... u) {
141    if (u.length != m_u.getNumRows()) {
142      throw new MatrixDimensionException(
143          "Malformed input! Got " + u.length + " elements instead of " + m_u.getNumRows());
144    }
145    m_u = new Matrix<>(new SimpleMatrix(m_u.getNumRows(), 1, true, u));
146  }
147
148  /**
149   * Sets the system state.
150   *
151   * @param state The new state.
152   */
153  public void setState(Matrix<States, N1> state) {
154    m_x = state;
155  }
156
157  /**
158   * Returns the current drawn by this simulated system. Override this method to add a custom
159   * current calculation.
160   *
161   * @return The current drawn by this simulated mechanism.
162   */
163  public double getCurrentDrawAmps() {
164    return 0.0;
165  }
166
167  /**
168   * Updates the state estimate of the system.
169   *
170   * @param currentXhat The current state estimate.
171   * @param u The system inputs (usually voltage).
172   * @param dtSeconds The time difference between controller updates.
173   * @return The new state.
174   */
175  @SuppressWarnings("ParameterName")
176  protected Matrix<States, N1> updateX(
177      Matrix<States, N1> currentXhat, Matrix<Inputs, N1> u, double dtSeconds) {
178    return m_plant.calculateX(currentXhat, u, dtSeconds);
179  }
180
181  /**
182   * Clamp the input vector such that no element exceeds the given voltage. If any does, the
183   * relative magnitudes of the input will be maintained.
184   *
185   * @param u The input vector.
186   * @return The normalized input.
187   */
188  protected Matrix<Inputs, N1> clampInput(Matrix<Inputs, N1> u) {
189    return StateSpaceUtil.desaturateInputVector(u, RobotController.getBatteryVoltage());
190  }
191}