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.MathSharedStore; 008import edu.wpi.first.math.MathUsageId; 009import edu.wpi.first.math.Matrix; 010import edu.wpi.first.math.Num; 011import edu.wpi.first.math.StateSpaceUtil; 012import edu.wpi.first.math.controller.LinearPlantInversionFeedforward; 013import edu.wpi.first.math.controller.LinearQuadraticRegulator; 014import edu.wpi.first.math.estimator.KalmanFilter; 015import edu.wpi.first.math.numbers.N1; 016import java.util.function.Function; 017import org.ejml.MatrixDimensionException; 018import org.ejml.simple.SimpleMatrix; 019 020/** 021 * Combines a controller, feedforward, and observer for controlling a mechanism with full state 022 * feedback. 023 * 024 * <p>For everything in this file, "inputs" and "outputs" are defined from the perspective of the 025 * plant. This means U is an input and Y is an output (because you give the plant U (powers) and it 026 * gives you back a Y (sensor values)). This is the opposite of what they mean from the perspective 027 * of the controller (U is an output because that's what goes to the motors and Y is an input 028 * because that's what comes back from the sensors). 029 * 030 * <p>For more on the underlying math, read <a 031 * href="https://file.tavsys.net/control/controls-engineering-in-frc.pdf">https://file.tavsys.net/control/controls-engineering-in-frc.pdf</a>. 032 * 033 * @param <States> Number of states. 034 * @param <Inputs> Number of inputs. 035 * @param <Outputs> Number of outputs. 036 */ 037public class LinearSystemLoop<States extends Num, Inputs extends Num, Outputs extends Num> { 038 private final LinearQuadraticRegulator<States, Inputs, Outputs> m_controller; 039 private final LinearPlantInversionFeedforward<States, Inputs, Outputs> m_feedforward; 040 private final KalmanFilter<States, Inputs, Outputs> m_observer; 041 private Matrix<States, N1> m_nextR; 042 private Function<Matrix<Inputs, N1>, Matrix<Inputs, N1>> m_clampFunction; 043 044 /** 045 * Constructs a state-space loop with the given plant, controller, and observer. By default, the 046 * initial reference is all zeros. Users should call reset with the initial system state before 047 * enabling the loop. This constructor assumes that the input(s) to this system are voltage. 048 * 049 * @param plant State-space plant. 050 * @param controller State-space controller. 051 * @param observer State-space observer. 052 * @param maxVoltageVolts The maximum voltage that can be applied. Commonly 12. 053 * @param dtSeconds The nominal timestep. 054 */ 055 public LinearSystemLoop( 056 LinearSystem<States, Inputs, Outputs> plant, 057 LinearQuadraticRegulator<States, Inputs, Outputs> controller, 058 KalmanFilter<States, Inputs, Outputs> observer, 059 double maxVoltageVolts, 060 double dtSeconds) { 061 this( 062 controller, 063 new LinearPlantInversionFeedforward<>(plant, dtSeconds), 064 observer, 065 u -> StateSpaceUtil.desaturateInputVector(u, maxVoltageVolts)); 066 } 067 068 /** 069 * Constructs a state-space loop with the given plant, controller, and observer. By default, the 070 * initial reference is all zeros. Users should call reset with the initial system state before 071 * enabling the loop. 072 * 073 * @param plant State-space plant. 074 * @param controller State-space controller. 075 * @param observer State-space observer. 076 * @param clampFunction The function used to clamp the input U. 077 * @param dtSeconds The nominal timestep. 078 */ 079 public LinearSystemLoop( 080 LinearSystem<States, Inputs, Outputs> plant, 081 LinearQuadraticRegulator<States, Inputs, Outputs> controller, 082 KalmanFilter<States, Inputs, Outputs> observer, 083 Function<Matrix<Inputs, N1>, Matrix<Inputs, N1>> clampFunction, 084 double dtSeconds) { 085 this( 086 controller, 087 new LinearPlantInversionFeedforward<>(plant, dtSeconds), 088 observer, 089 clampFunction); 090 } 091 092 /** 093 * Constructs a state-space loop with the given controller, feedforward and observer. By default, 094 * the initial reference is all zeros. Users should call reset with the initial system state 095 * before enabling the loop. 096 * 097 * @param controller State-space controller. 098 * @param feedforward Plant inversion feedforward. 099 * @param observer State-space observer. 100 * @param maxVoltageVolts The maximum voltage that can be applied. Assumes that the inputs are 101 * voltages. 102 */ 103 public LinearSystemLoop( 104 LinearQuadraticRegulator<States, Inputs, Outputs> controller, 105 LinearPlantInversionFeedforward<States, Inputs, Outputs> feedforward, 106 KalmanFilter<States, Inputs, Outputs> observer, 107 double maxVoltageVolts) { 108 this( 109 controller, 110 feedforward, 111 observer, 112 u -> StateSpaceUtil.desaturateInputVector(u, maxVoltageVolts)); 113 } 114 115 /** 116 * Constructs a state-space loop with the given controller, feedforward, and observer. By default, 117 * the initial reference is all zeros. Users should call reset with the initial system state 118 * before enabling the loop. 119 * 120 * @param controller State-space controller. 121 * @param feedforward Plant inversion feedforward. 122 * @param observer State-space observer. 123 * @param clampFunction The function used to clamp the input U. 124 */ 125 public LinearSystemLoop( 126 LinearQuadraticRegulator<States, Inputs, Outputs> controller, 127 LinearPlantInversionFeedforward<States, Inputs, Outputs> feedforward, 128 KalmanFilter<States, Inputs, Outputs> observer, 129 Function<Matrix<Inputs, N1>, Matrix<Inputs, N1>> clampFunction) { 130 this.m_controller = controller; 131 this.m_feedforward = feedforward; 132 this.m_observer = observer; 133 this.m_clampFunction = clampFunction; 134 135 m_nextR = new Matrix<>(new SimpleMatrix(controller.getK().getNumCols(), 1)); 136 reset(m_nextR); 137 MathSharedStore.getMathShared().reportUsage(MathUsageId.kSystem_LinearSystemLoop, 1); 138 } 139 140 /** 141 * Returns the observer's state estimate x-hat. 142 * 143 * @return the observer's state estimate x-hat. 144 */ 145 public Matrix<States, N1> getXHat() { 146 return getObserver().getXhat(); 147 } 148 149 /** 150 * Returns an element of the observer's state estimate x-hat. 151 * 152 * @param row Row of x-hat. 153 * @return the i-th element of the observer's state estimate x-hat. 154 */ 155 public double getXHat(int row) { 156 return getObserver().getXhat(row); 157 } 158 159 /** 160 * Set the initial state estimate x-hat. 161 * 162 * @param xhat The initial state estimate x-hat. 163 */ 164 public void setXHat(Matrix<States, N1> xhat) { 165 getObserver().setXhat(xhat); 166 } 167 168 /** 169 * Set an element of the initial state estimate x-hat. 170 * 171 * @param row Row of x-hat. 172 * @param value Value for element of x-hat. 173 */ 174 public void setXHat(int row, double value) { 175 getObserver().setXhat(row, value); 176 } 177 178 /** 179 * Returns an element of the controller's next reference r. 180 * 181 * @param row Row of r. 182 * @return the element i of the controller's next reference r. 183 */ 184 public double getNextR(int row) { 185 return getNextR().get(row, 0); 186 } 187 188 /** 189 * Returns the controller's next reference r. 190 * 191 * @return the controller's next reference r. 192 */ 193 public Matrix<States, N1> getNextR() { 194 return m_nextR; 195 } 196 197 /** 198 * Set the next reference r. 199 * 200 * @param nextR Next reference. 201 */ 202 public void setNextR(Matrix<States, N1> nextR) { 203 m_nextR = nextR; 204 } 205 206 /** 207 * Set the next reference r. 208 * 209 * @param nextR Next reference. 210 */ 211 public void setNextR(double... nextR) { 212 if (nextR.length != m_nextR.getNumRows()) { 213 throw new MatrixDimensionException( 214 String.format( 215 "The next reference does not have the " 216 + "correct number of entries! Expected %s, but got %s.", 217 m_nextR.getNumRows(), nextR.length)); 218 } 219 m_nextR = new Matrix<>(new SimpleMatrix(m_nextR.getNumRows(), 1, true, nextR)); 220 } 221 222 /** 223 * Returns the controller's calculated control input u plus the calculated feedforward u_ff. 224 * 225 * @return the calculated control input u. 226 */ 227 public Matrix<Inputs, N1> getU() { 228 return clampInput(m_controller.getU().plus(m_feedforward.getUff())); 229 } 230 231 /** 232 * Returns an element of the controller's calculated control input u. 233 * 234 * @param row Row of u. 235 * @return the calculated control input u at the row i. 236 */ 237 public double getU(int row) { 238 return getU().get(row, 0); 239 } 240 241 /** 242 * Return the controller used internally. 243 * 244 * @return the controller used internally. 245 */ 246 public LinearQuadraticRegulator<States, Inputs, Outputs> getController() { 247 return m_controller; 248 } 249 250 /** 251 * Return the feedforward used internally. 252 * 253 * @return the feedforward used internally. 254 */ 255 public LinearPlantInversionFeedforward<States, Inputs, Outputs> getFeedforward() { 256 return m_feedforward; 257 } 258 259 /** 260 * Return the observer used internally. 261 * 262 * @return the observer used internally. 263 */ 264 public KalmanFilter<States, Inputs, Outputs> getObserver() { 265 return m_observer; 266 } 267 268 /** 269 * Zeroes reference r and controller output u. The previous reference of the 270 * PlantInversionFeedforward and the initial state estimate of the KalmanFilter are set to the 271 * initial state provided. 272 * 273 * @param initialState The initial state. 274 */ 275 public final void reset(Matrix<States, N1> initialState) { 276 m_nextR.fill(0.0); 277 m_controller.reset(); 278 m_feedforward.reset(initialState); 279 m_observer.setXhat(initialState); 280 } 281 282 /** 283 * Returns difference between reference r and current state x-hat. 284 * 285 * @return The state error matrix. 286 */ 287 public Matrix<States, N1> getError() { 288 return getController().getR().minus(m_observer.getXhat()); 289 } 290 291 /** 292 * Returns difference between reference r and current state x-hat. 293 * 294 * @param index The index of the error matrix to return. 295 * @return The error at that index. 296 */ 297 public double getError(int index) { 298 return getController().getR().minus(m_observer.getXhat()).get(index, 0); 299 } 300 301 /** 302 * Get the function used to clamp the input u. 303 * 304 * @return The clamping function. 305 */ 306 public Function<Matrix<Inputs, N1>, Matrix<Inputs, N1>> getClampFunction() { 307 return m_clampFunction; 308 } 309 310 /** 311 * Set the clamping function used to clamp inputs. 312 * 313 * @param clampFunction The clamping function. 314 */ 315 public void setClampFunction(Function<Matrix<Inputs, N1>, Matrix<Inputs, N1>> clampFunction) { 316 this.m_clampFunction = clampFunction; 317 } 318 319 /** 320 * Correct the state estimate x-hat using the measurements in y. 321 * 322 * @param y Measurement vector. 323 */ 324 public void correct(Matrix<Outputs, N1> y) { 325 getObserver().correct(getU(), y); 326 } 327 328 /** 329 * Sets new controller output, projects model forward, and runs observer prediction. 330 * 331 * <p>After calling this, the user should send the elements of u to the actuators. 332 * 333 * @param dtSeconds Timestep for model update. 334 */ 335 public void predict(double dtSeconds) { 336 var u = 337 clampInput( 338 m_controller 339 .calculate(getObserver().getXhat(), m_nextR) 340 .plus(m_feedforward.calculate(m_nextR))); 341 getObserver().predict(u, dtSeconds); 342 } 343 344 /** 345 * Clamp the input u to the min and max. 346 * 347 * @param unclampedU The input to clamp. 348 * @return The clamped input. 349 */ 350 public Matrix<Inputs, N1> clampInput(Matrix<Inputs, N1> unclampedU) { 351 return m_clampFunction.apply(unclampedU); 352 } 353}