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.controller; 006 007import edu.wpi.first.math.DARE; 008import edu.wpi.first.math.Matrix; 009import edu.wpi.first.math.Num; 010import edu.wpi.first.math.StateSpaceUtil; 011import edu.wpi.first.math.Vector; 012import edu.wpi.first.math.numbers.N1; 013import edu.wpi.first.math.system.Discretization; 014import edu.wpi.first.math.system.LinearSystem; 015import org.ejml.simple.SimpleMatrix; 016 017/** 018 * Contains the controller coefficients and logic for a linear-quadratic regulator (LQR). LQRs use 019 * the control law u = K(r - x). 020 * 021 * <p>For more on the underlying math, read <a 022 * href="https://file.tavsys.net/control/controls-engineering-in-frc.pdf">https://file.tavsys.net/control/controls-engineering-in-frc.pdf</a>. 023 * 024 * @param <States> Number of states. 025 * @param <Inputs> Number of inputs. 026 * @param <Outputs> Number of outputs. 027 */ 028public class LinearQuadraticRegulator<States extends Num, Inputs extends Num, Outputs extends Num> { 029 /** The current reference state. */ 030 private Matrix<States, N1> m_r; 031 032 /** The computed and capped controller output. */ 033 private Matrix<Inputs, N1> m_u; 034 035 // Controller gain. 036 private Matrix<Inputs, States> m_K; 037 038 /** 039 * Constructs a controller with the given coefficients and plant. Rho is defaulted to 1. 040 * 041 * <p>See <a 042 * href="https://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-intro.html#lqr-tuning">https://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-intro.html#lqr-tuning</a> 043 * for how to select the tolerances. 044 * 045 * @param plant The plant being controlled. 046 * @param qelms The maximum desired error tolerance for each state. 047 * @param relms The maximum desired control effort for each input. 048 * @param dtSeconds Discretization timestep. 049 * @throws IllegalArgumentException If the system is unstabilizable. 050 */ 051 public LinearQuadraticRegulator( 052 LinearSystem<States, Inputs, Outputs> plant, 053 Vector<States> qelms, 054 Vector<Inputs> relms, 055 double dtSeconds) { 056 this( 057 plant.getA(), 058 plant.getB(), 059 StateSpaceUtil.makeCostMatrix(qelms), 060 StateSpaceUtil.makeCostMatrix(relms), 061 dtSeconds); 062 } 063 064 /** 065 * Constructs a controller with the given coefficients and plant. 066 * 067 * <p>See <a 068 * href="https://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-intro.html#lqr-tuning">https://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-intro.html#lqr-tuning</a> 069 * for how to select the tolerances. 070 * 071 * @param A Continuous system matrix of the plant being controlled. 072 * @param B Continuous input matrix of the plant being controlled. 073 * @param qelms The maximum desired error tolerance for each state. 074 * @param relms The maximum desired control effort for each input. 075 * @param dtSeconds Discretization timestep. 076 * @throws IllegalArgumentException If the system is unstabilizable. 077 */ 078 public LinearQuadraticRegulator( 079 Matrix<States, States> A, 080 Matrix<States, Inputs> B, 081 Vector<States> qelms, 082 Vector<Inputs> relms, 083 double dtSeconds) { 084 this( 085 A, 086 B, 087 StateSpaceUtil.makeCostMatrix(qelms), 088 StateSpaceUtil.makeCostMatrix(relms), 089 dtSeconds); 090 } 091 092 /** 093 * Constructs a controller with the given coefficients and plant. 094 * 095 * @param A Continuous system matrix of the plant being controlled. 096 * @param B Continuous input matrix of the plant being controlled. 097 * @param Q The state cost matrix. 098 * @param R The input cost matrix. 099 * @param dtSeconds Discretization timestep. 100 * @throws IllegalArgumentException If the system is unstabilizable. 101 */ 102 public LinearQuadraticRegulator( 103 Matrix<States, States> A, 104 Matrix<States, Inputs> B, 105 Matrix<States, States> Q, 106 Matrix<Inputs, Inputs> R, 107 double dtSeconds) { 108 var discABPair = Discretization.discretizeAB(A, B, dtSeconds); 109 var discA = discABPair.getFirst(); 110 var discB = discABPair.getSecond(); 111 112 var S = DARE.dare(discA, discB, Q, R); 113 114 // K = (BᵀSB + R)⁻¹BᵀSA 115 m_K = 116 discB 117 .transpose() 118 .times(S) 119 .times(discB) 120 .plus(R) 121 .solve(discB.transpose().times(S).times(discA)); 122 123 m_r = new Matrix<>(new SimpleMatrix(B.getNumRows(), 1)); 124 m_u = new Matrix<>(new SimpleMatrix(B.getNumCols(), 1)); 125 126 reset(); 127 } 128 129 /** 130 * Constructs a controller with the given coefficients and plant. 131 * 132 * @param A Continuous system matrix of the plant being controlled. 133 * @param B Continuous input matrix of the plant being controlled. 134 * @param Q The state cost matrix. 135 * @param R The input cost matrix. 136 * @param N The state-input cross-term cost matrix. 137 * @param dtSeconds Discretization timestep. 138 * @throws IllegalArgumentException If the system is unstabilizable. 139 */ 140 public LinearQuadraticRegulator( 141 Matrix<States, States> A, 142 Matrix<States, Inputs> B, 143 Matrix<States, States> Q, 144 Matrix<Inputs, Inputs> R, 145 Matrix<States, Inputs> N, 146 double dtSeconds) { 147 var discABPair = Discretization.discretizeAB(A, B, dtSeconds); 148 var discA = discABPair.getFirst(); 149 var discB = discABPair.getSecond(); 150 151 var S = DARE.dare(discA, discB, Q, R, N); 152 153 // K = (BᵀSB + R)⁻¹(BᵀSA + Nᵀ) 154 m_K = 155 discB 156 .transpose() 157 .times(S) 158 .times(discB) 159 .plus(R) 160 .solve(discB.transpose().times(S).times(discA).plus(N.transpose())); 161 162 m_r = new Matrix<>(new SimpleMatrix(B.getNumRows(), 1)); 163 m_u = new Matrix<>(new SimpleMatrix(B.getNumCols(), 1)); 164 165 reset(); 166 } 167 168 /** 169 * Returns the control input vector u. 170 * 171 * @return The control input. 172 */ 173 public Matrix<Inputs, N1> getU() { 174 return m_u; 175 } 176 177 /** 178 * Returns an element of the control input vector u. 179 * 180 * @param row Row of u. 181 * @return The row of the control input vector. 182 */ 183 public double getU(int row) { 184 return m_u.get(row, 0); 185 } 186 187 /** 188 * Returns the reference vector r. 189 * 190 * @return The reference vector. 191 */ 192 public Matrix<States, N1> getR() { 193 return m_r; 194 } 195 196 /** 197 * Returns an element of the reference vector r. 198 * 199 * @param row Row of r. 200 * @return The row of the reference vector. 201 */ 202 public double getR(int row) { 203 return m_r.get(row, 0); 204 } 205 206 /** 207 * Returns the controller matrix K. 208 * 209 * @return the controller matrix K. 210 */ 211 public Matrix<Inputs, States> getK() { 212 return m_K; 213 } 214 215 /** Resets the controller. */ 216 public final void reset() { 217 m_r.fill(0.0); 218 m_u.fill(0.0); 219 } 220 221 /** 222 * Returns the next output of the controller. 223 * 224 * @param x The current state x. 225 * @return The next controller output. 226 */ 227 public Matrix<Inputs, N1> calculate(Matrix<States, N1> x) { 228 m_u = m_K.times(m_r.minus(x)); 229 return m_u; 230 } 231 232 /** 233 * Returns the next output of the controller. 234 * 235 * @param x The current state x. 236 * @param nextR the next reference vector r. 237 * @return The next controller output. 238 */ 239 public Matrix<Inputs, N1> calculate(Matrix<States, N1> x, Matrix<States, N1> nextR) { 240 m_r = nextR; 241 return calculate(x); 242 } 243 244 /** 245 * Adjusts LQR controller gain to compensate for a pure time delay in the input. 246 * 247 * <p>Linear-Quadratic regulator controller gains tend to be aggressive. If sensor measurements 248 * are time-delayed too long, the LQR may be unstable. However, if we know the amount of delay, we 249 * can compute the control based on where the system will be after the time delay. 250 * 251 * <p>See <a 252 * href="https://file.tavsys.net/control/controls-engineering-in-frc.pdf">https://file.tavsys.net/control/controls-engineering-in-frc.pdf</a> 253 * appendix C.4 for a derivation. 254 * 255 * @param plant The plant being controlled. 256 * @param dtSeconds Discretization timestep in seconds. 257 * @param inputDelaySeconds Input time delay in seconds. 258 */ 259 public void latencyCompensate( 260 LinearSystem<States, Inputs, Outputs> plant, double dtSeconds, double inputDelaySeconds) { 261 var discABPair = Discretization.discretizeAB(plant.getA(), plant.getB(), dtSeconds); 262 var discA = discABPair.getFirst(); 263 var discB = discABPair.getSecond(); 264 265 m_K = m_K.times((discA.minus(discB.times(m_K))).pow(inputDelaySeconds / dtSeconds)); 266 } 267}