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.Matrix;
008import edu.wpi.first.math.Num;
009import edu.wpi.first.math.numbers.N1;
010
011/**
012 * A plant defined using state-space notation.
013 *
014 * <p>A plant is a mathematical model of a system's dynamics.
015 *
016 * <p>For more on the underlying math, read
017 * https://file.tavsys.net/control/controls-engineering-in-frc.pdf.
018 *
019 * @param <States> Number of states.
020 * @param <Inputs> Number of inputs.
021 * @param <Outputs> Number of outputs.
022 */
023public class LinearSystem<States extends Num, Inputs extends Num, Outputs extends Num> {
024  /** Continuous system matrix. */
025  private final Matrix<States, States> m_A;
026
027  /** Continuous input matrix. */
028  private final Matrix<States, Inputs> m_B;
029
030  /** Output matrix. */
031  private final Matrix<Outputs, States> m_C;
032
033  /** Feedthrough matrix. */
034  private final Matrix<Outputs, Inputs> m_D;
035
036  /**
037   * Construct a new LinearSystem from the four system matrices.
038   *
039   * @param A The system matrix A.
040   * @param B The input matrix B.
041   * @param C The output matrix C.
042   * @param D The feedthrough matrix D.
043   * @throws IllegalArgumentException if any matrix element isn't finite.
044   */
045  public LinearSystem(
046      Matrix<States, States> A,
047      Matrix<States, Inputs> B,
048      Matrix<Outputs, States> C,
049      Matrix<Outputs, Inputs> D) {
050    for (int row = 0; row < A.getNumRows(); ++row) {
051      for (int col = 0; col < A.getNumCols(); ++col) {
052        if (!Double.isFinite(A.get(row, col))) {
053          throw new IllegalArgumentException(
054              "Elements of A aren't finite. This is usually due to model implementation errors.");
055        }
056      }
057    }
058    for (int row = 0; row < B.getNumRows(); ++row) {
059      for (int col = 0; col < B.getNumCols(); ++col) {
060        if (!Double.isFinite(B.get(row, col))) {
061          throw new IllegalArgumentException(
062              "Elements of B aren't finite. This is usually due to model implementation errors.");
063        }
064      }
065    }
066    for (int row = 0; row < C.getNumRows(); ++row) {
067      for (int col = 0; col < C.getNumCols(); ++col) {
068        if (!Double.isFinite(C.get(row, col))) {
069          throw new IllegalArgumentException(
070              "Elements of C aren't finite. This is usually due to model implementation errors.");
071        }
072      }
073    }
074    for (int row = 0; row < D.getNumRows(); ++row) {
075      for (int col = 0; col < D.getNumCols(); ++col) {
076        if (!Double.isFinite(D.get(row, col))) {
077          throw new IllegalArgumentException(
078              "Elements of D aren't finite. This is usually due to model implementation errors.");
079        }
080      }
081    }
082
083    this.m_A = A;
084    this.m_B = B;
085    this.m_C = C;
086    this.m_D = D;
087  }
088
089  /**
090   * Returns the system matrix A.
091   *
092   * @return the system matrix A.
093   */
094  public Matrix<States, States> getA() {
095    return m_A;
096  }
097
098  /**
099   * Returns an element of the system matrix A.
100   *
101   * @param row Row of A.
102   * @param col Column of A.
103   * @return the system matrix A at (i, j).
104   */
105  public double getA(int row, int col) {
106    return m_A.get(row, col);
107  }
108
109  /**
110   * Returns the input matrix B.
111   *
112   * @return the input matrix B.
113   */
114  public Matrix<States, Inputs> getB() {
115    return m_B;
116  }
117
118  /**
119   * Returns an element of the input matrix B.
120   *
121   * @param row Row of B.
122   * @param col Column of B.
123   * @return The value of the input matrix B at (i, j).
124   */
125  public double getB(int row, int col) {
126    return m_B.get(row, col);
127  }
128
129  /**
130   * Returns the output matrix C.
131   *
132   * @return Output matrix C.
133   */
134  public Matrix<Outputs, States> getC() {
135    return m_C;
136  }
137
138  /**
139   * Returns an element of the output matrix C.
140   *
141   * @param row Row of C.
142   * @param col Column of C.
143   * @return the double value of C at the given position.
144   */
145  public double getC(int row, int col) {
146    return m_C.get(row, col);
147  }
148
149  /**
150   * Returns the feedthrough matrix D.
151   *
152   * @return the feedthrough matrix D.
153   */
154  public Matrix<Outputs, Inputs> getD() {
155    return m_D;
156  }
157
158  /**
159   * Returns an element of the feedthrough matrix D.
160   *
161   * @param row Row of D.
162   * @param col Column of D.
163   * @return The feedthrough matrix D at (i, j).
164   */
165  public double getD(int row, int col) {
166    return m_D.get(row, col);
167  }
168
169  /**
170   * Computes the new x given the old x and the control input.
171   *
172   * <p>This is used by state observers directly to run updates based on state estimate.
173   *
174   * @param x The current state.
175   * @param clampedU The control input.
176   * @param dtSeconds Timestep for model update.
177   * @return the updated x.
178   */
179  public Matrix<States, N1> calculateX(
180      Matrix<States, N1> x, Matrix<Inputs, N1> clampedU, double dtSeconds) {
181    var discABpair = Discretization.discretizeAB(m_A, m_B, dtSeconds);
182
183    return discABpair.getFirst().times(x).plus(discABpair.getSecond().times(clampedU));
184  }
185
186  /**
187   * Computes the new y given the control input.
188   *
189   * <p>This is used by state observers directly to run updates based on state estimate.
190   *
191   * @param x The current state.
192   * @param clampedU The control input.
193   * @return the updated output matrix Y.
194   */
195  public Matrix<Outputs, N1> calculateY(Matrix<States, N1> x, Matrix<Inputs, N1> clampedU) {
196    return m_C.times(x).plus(m_D.times(clampedU));
197  }
198
199  @Override
200  public String toString() {
201    return String.format(
202        "Linear System: A\n%s\n\nB:\n%s\n\nC:\n%s\n\nD:\n%s\n",
203        m_A.toString(), m_B.toString(), m_C.toString(), m_D.toString());
204  }
205}