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}