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; 010import edu.wpi.first.math.numbers.N10; 011import edu.wpi.first.math.numbers.N11; 012import edu.wpi.first.math.numbers.N12; 013import edu.wpi.first.math.numbers.N13; 014import edu.wpi.first.math.numbers.N14; 015import edu.wpi.first.math.numbers.N15; 016import edu.wpi.first.math.numbers.N16; 017import edu.wpi.first.math.numbers.N17; 018import edu.wpi.first.math.numbers.N18; 019import edu.wpi.first.math.numbers.N19; 020import edu.wpi.first.math.numbers.N2; 021import edu.wpi.first.math.numbers.N20; 022import edu.wpi.first.math.numbers.N3; 023import edu.wpi.first.math.numbers.N4; 024import edu.wpi.first.math.numbers.N5; 025import edu.wpi.first.math.numbers.N6; 026import edu.wpi.first.math.numbers.N7; 027import edu.wpi.first.math.numbers.N8; 028import edu.wpi.first.math.numbers.N9; 029import java.util.Arrays; 030import java.util.Collections; 031import java.util.List; 032import java.util.stream.Collectors; 033import org.ejml.simple.SimpleMatrix; 034 035/** 036 * A plant defined using state-space notation. 037 * 038 * <p>A plant is a mathematical model of a system's dynamics. 039 * 040 * <p>For more on the underlying math, read 041 * https://file.tavsys.net/control/controls-engineering-in-frc.pdf. 042 * 043 * @param <States> Number of states. 044 * @param <Inputs> Number of inputs. 045 * @param <Outputs> Number of outputs. 046 */ 047public class LinearSystem<States extends Num, Inputs extends Num, Outputs extends Num> { 048 /** Continuous system matrix. */ 049 private final Matrix<States, States> m_A; 050 051 /** Continuous input matrix. */ 052 private final Matrix<States, Inputs> m_B; 053 054 /** Output matrix. */ 055 private final Matrix<Outputs, States> m_C; 056 057 /** Feedthrough matrix. */ 058 private final Matrix<Outputs, Inputs> m_D; 059 060 /** 061 * Construct a new LinearSystem from the four system matrices. 062 * 063 * @param A The system matrix A. 064 * @param B The input matrix B. 065 * @param C The output matrix C. 066 * @param D The feedthrough matrix D. 067 * @throws IllegalArgumentException if any matrix element isn't finite. 068 */ 069 public LinearSystem( 070 Matrix<States, States> A, 071 Matrix<States, Inputs> B, 072 Matrix<Outputs, States> C, 073 Matrix<Outputs, Inputs> D) { 074 for (int row = 0; row < A.getNumRows(); ++row) { 075 for (int col = 0; col < A.getNumCols(); ++col) { 076 if (!Double.isFinite(A.get(row, col))) { 077 throw new IllegalArgumentException( 078 "Elements of A aren't finite. This is usually due to model implementation errors."); 079 } 080 } 081 } 082 for (int row = 0; row < B.getNumRows(); ++row) { 083 for (int col = 0; col < B.getNumCols(); ++col) { 084 if (!Double.isFinite(B.get(row, col))) { 085 throw new IllegalArgumentException( 086 "Elements of B aren't finite. This is usually due to model implementation errors."); 087 } 088 } 089 } 090 for (int row = 0; row < C.getNumRows(); ++row) { 091 for (int col = 0; col < C.getNumCols(); ++col) { 092 if (!Double.isFinite(C.get(row, col))) { 093 throw new IllegalArgumentException( 094 "Elements of C aren't finite. This is usually due to model implementation errors."); 095 } 096 } 097 } 098 for (int row = 0; row < D.getNumRows(); ++row) { 099 for (int col = 0; col < D.getNumCols(); ++col) { 100 if (!Double.isFinite(D.get(row, col))) { 101 throw new IllegalArgumentException( 102 "Elements of D aren't finite. This is usually due to model implementation errors."); 103 } 104 } 105 } 106 107 this.m_A = A; 108 this.m_B = B; 109 this.m_C = C; 110 this.m_D = D; 111 } 112 113 /** 114 * Returns the system matrix A. 115 * 116 * @return the system matrix A. 117 */ 118 public Matrix<States, States> getA() { 119 return m_A; 120 } 121 122 /** 123 * Returns an element of the system matrix A. 124 * 125 * @param row Row of A. 126 * @param col Column of A. 127 * @return the system matrix A at (i, j). 128 */ 129 public double getA(int row, int col) { 130 return m_A.get(row, col); 131 } 132 133 /** 134 * Returns the input matrix B. 135 * 136 * @return the input matrix B. 137 */ 138 public Matrix<States, Inputs> getB() { 139 return m_B; 140 } 141 142 /** 143 * Returns an element of the input matrix B. 144 * 145 * @param row Row of B. 146 * @param col Column of B. 147 * @return The value of the input matrix B at (i, j). 148 */ 149 public double getB(int row, int col) { 150 return m_B.get(row, col); 151 } 152 153 /** 154 * Returns the output matrix C. 155 * 156 * @return Output matrix C. 157 */ 158 public Matrix<Outputs, States> getC() { 159 return m_C; 160 } 161 162 /** 163 * Returns an element of the output matrix C. 164 * 165 * @param row Row of C. 166 * @param col Column of C. 167 * @return the double value of C at the given position. 168 */ 169 public double getC(int row, int col) { 170 return m_C.get(row, col); 171 } 172 173 /** 174 * Returns the feedthrough matrix D. 175 * 176 * @return the feedthrough matrix D. 177 */ 178 public Matrix<Outputs, Inputs> getD() { 179 return m_D; 180 } 181 182 /** 183 * Returns an element of the feedthrough matrix D. 184 * 185 * @param row Row of D. 186 * @param col Column of D. 187 * @return The feedthrough matrix D at (i, j). 188 */ 189 public double getD(int row, int col) { 190 return m_D.get(row, col); 191 } 192 193 /** 194 * Computes the new x given the old x and the control input. 195 * 196 * <p>This is used by state observers directly to run updates based on state estimate. 197 * 198 * @param x The current state. 199 * @param clampedU The control input. 200 * @param dtSeconds Timestep for model update. 201 * @return the updated x. 202 */ 203 public Matrix<States, N1> calculateX( 204 Matrix<States, N1> x, Matrix<Inputs, N1> clampedU, double dtSeconds) { 205 var discABpair = Discretization.discretizeAB(m_A, m_B, dtSeconds); 206 207 return discABpair.getFirst().times(x).plus(discABpair.getSecond().times(clampedU)); 208 } 209 210 /** 211 * Computes the new y given the control input. 212 * 213 * <p>This is used by state observers directly to run updates based on state estimate. 214 * 215 * @param x The current state. 216 * @param clampedU The control input. 217 * @return the updated output matrix Y. 218 */ 219 public Matrix<Outputs, N1> calculateY(Matrix<States, N1> x, Matrix<Inputs, N1> clampedU) { 220 return m_C.times(x).plus(m_D.times(clampedU)); 221 } 222 223 /** 224 * Returns the LinearSystem with the outputs listed in outputIndices. 225 * 226 * <p>This is used by state observers such as the Kalman Filter. 227 * 228 * @param outputIndices the list of output indices to include in the sliced system. 229 * @return the sliced LinearSystem with outputs set to row vectors of LinearSystem. 230 * @throws IllegalArgumentException if any outputIndices are outside the range of system outputs. 231 * @throws IllegalArgumentException if number of outputIndices exceeds the number of system 232 * outputs. 233 * @throws IllegalArgumentException if duplication exists in outputIndices. 234 */ 235 public LinearSystem<States, Inputs, ? extends Num> slice(int... outputIndices) { 236 for (int index : outputIndices) { 237 if (index < 0 || index >= m_C.getNumRows()) { 238 throw new IllegalArgumentException( 239 "Output indices out of range. This is usually due to model implementation errors."); 240 } 241 } 242 243 if (outputIndices.length >= m_C.getNumRows()) { 244 throw new IllegalArgumentException( 245 "More outputs requested than available. This is usually due to model implementation " 246 + "errors."); 247 } 248 249 List<Integer> outputIndicesList = 250 Arrays.stream(outputIndices).distinct().boxed().collect(Collectors.toList()); 251 Collections.sort(outputIndicesList); 252 253 if (outputIndices.length != outputIndicesList.size()) { 254 throw new IllegalArgumentException( 255 "Duplicate indices exist. This is usually due to model implementation " + "errors."); 256 } 257 258 SimpleMatrix new_C_Storage = new SimpleMatrix(outputIndices.length, m_C.getNumCols()); 259 int row = 0; 260 for (var index : outputIndicesList) { 261 var current_row_data = m_C.extractRowVector(index).getData(); 262 new_C_Storage.setRow(row, 0, current_row_data); 263 row++; 264 } 265 266 SimpleMatrix new_D_Storage = new SimpleMatrix(outputIndices.length, m_D.getNumCols()); 267 row = 0; 268 for (var index : outputIndicesList) { 269 var current_row_data = m_D.extractRowVector(index).getData(); 270 new_D_Storage.setRow(row, 0, current_row_data); 271 row++; 272 } 273 274 switch (outputIndices.length) { 275 case 20: 276 Matrix<N20, States> new_C20 = new Matrix<>(new_C_Storage); 277 Matrix<N20, Inputs> new_D20 = new Matrix<>(new_D_Storage); 278 return new LinearSystem<>(m_A, m_B, new_C20, new_D20); 279 case 19: 280 Matrix<N19, States> new_C19 = new Matrix<>(new_C_Storage); 281 Matrix<N19, Inputs> new_D19 = new Matrix<>(new_D_Storage); 282 return new LinearSystem<>(m_A, m_B, new_C19, new_D19); 283 case 18: 284 Matrix<N18, States> new_C18 = new Matrix<>(new_C_Storage); 285 Matrix<N18, Inputs> new_D18 = new Matrix<>(new_D_Storage); 286 return new LinearSystem<>(m_A, m_B, new_C18, new_D18); 287 case 17: 288 Matrix<N17, States> new_C17 = new Matrix<>(new_C_Storage); 289 Matrix<N17, Inputs> new_D17 = new Matrix<>(new_D_Storage); 290 return new LinearSystem<>(m_A, m_B, new_C17, new_D17); 291 case 16: 292 Matrix<N16, States> new_C16 = new Matrix<>(new_C_Storage); 293 Matrix<N16, Inputs> new_D16 = new Matrix<>(new_D_Storage); 294 return new LinearSystem<>(m_A, m_B, new_C16, new_D16); 295 case 15: 296 Matrix<N15, States> new_C15 = new Matrix<>(new_C_Storage); 297 Matrix<N15, Inputs> new_D15 = new Matrix<>(new_D_Storage); 298 return new LinearSystem<>(m_A, m_B, new_C15, new_D15); 299 case 14: 300 Matrix<N14, States> new_C14 = new Matrix<>(new_C_Storage); 301 Matrix<N14, Inputs> new_D14 = new Matrix<>(new_D_Storage); 302 return new LinearSystem<>(m_A, m_B, new_C14, new_D14); 303 case 13: 304 Matrix<N13, States> new_C13 = new Matrix<>(new_C_Storage); 305 Matrix<N13, Inputs> new_D13 = new Matrix<>(new_D_Storage); 306 return new LinearSystem<>(m_A, m_B, new_C13, new_D13); 307 case 12: 308 Matrix<N12, States> new_C12 = new Matrix<>(new_C_Storage); 309 Matrix<N12, Inputs> new_D12 = new Matrix<>(new_D_Storage); 310 return new LinearSystem<>(m_A, m_B, new_C12, new_D12); 311 case 11: 312 Matrix<N11, States> new_C11 = new Matrix<>(new_C_Storage); 313 Matrix<N11, Inputs> new_D11 = new Matrix<>(new_D_Storage); 314 return new LinearSystem<>(m_A, m_B, new_C11, new_D11); 315 case 10: 316 Matrix<N10, States> new_C10 = new Matrix<>(new_C_Storage); 317 Matrix<N10, Inputs> new_D10 = new Matrix<>(new_D_Storage); 318 return new LinearSystem<>(m_A, m_B, new_C10, new_D10); 319 case 9: 320 Matrix<N9, States> new_C9 = new Matrix<>(new_C_Storage); 321 Matrix<N9, Inputs> new_D9 = new Matrix<>(new_D_Storage); 322 return new LinearSystem<>(m_A, m_B, new_C9, new_D9); 323 case 8: 324 Matrix<N8, States> new_C8 = new Matrix<>(new_C_Storage); 325 Matrix<N8, Inputs> new_D8 = new Matrix<>(new_D_Storage); 326 return new LinearSystem<>(m_A, m_B, new_C8, new_D8); 327 case 7: 328 Matrix<N7, States> new_C7 = new Matrix<>(new_C_Storage); 329 Matrix<N7, Inputs> new_D7 = new Matrix<>(new_D_Storage); 330 return new LinearSystem<>(m_A, m_B, new_C7, new_D7); 331 case 6: 332 Matrix<N6, States> new_C6 = new Matrix<>(new_C_Storage); 333 Matrix<N6, Inputs> new_D6 = new Matrix<>(new_D_Storage); 334 return new LinearSystem<>(m_A, m_B, new_C6, new_D6); 335 case 5: 336 Matrix<N5, States> new_C5 = new Matrix<>(new_C_Storage); 337 Matrix<N5, Inputs> new_D5 = new Matrix<>(new_D_Storage); 338 return new LinearSystem<>(m_A, m_B, new_C5, new_D5); 339 case 4: 340 Matrix<N4, States> new_C4 = new Matrix<>(new_C_Storage); 341 Matrix<N4, Inputs> new_D4 = new Matrix<>(new_D_Storage); 342 return new LinearSystem<>(m_A, m_B, new_C4, new_D4); 343 case 3: 344 Matrix<N3, States> new_C3 = new Matrix<>(new_C_Storage); 345 Matrix<N3, Inputs> new_D3 = new Matrix<>(new_D_Storage); 346 return new LinearSystem<>(m_A, m_B, new_C3, new_D3); 347 case 2: 348 Matrix<N2, States> new_C2 = new Matrix<>(new_C_Storage); 349 Matrix<N2, Inputs> new_D2 = new Matrix<>(new_D_Storage); 350 return new LinearSystem<>(m_A, m_B, new_C2, new_D2); 351 default: 352 Matrix<N1, States> new_C1 = new Matrix<>(new_C_Storage); 353 Matrix<N1, Inputs> new_D1 = new Matrix<>(new_D_Storage); 354 return new LinearSystem<>(m_A, m_B, new_C1, new_D1); 355 } 356 } 357 358 @Override 359 public String toString() { 360 return String.format( 361 "Linear System: A\n%s\n\nB:\n%s\n\nC:\n%s\n\nD:\n%s\n", 362 m_A.toString(), m_B.toString(), m_C.toString(), m_D.toString()); 363 } 364}