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.Matrix; 008import edu.wpi.first.math.Nat; 009import edu.wpi.first.math.Num; 010import edu.wpi.first.math.numbers.N1; 011import edu.wpi.first.math.system.NumericalJacobian; 012import java.util.function.BiFunction; 013import java.util.function.Function; 014 015/** 016 * Constructs a control-affine plant inversion model-based feedforward from given model dynamics. 017 * 018 * <p>If given the vector valued function as f(x, u) where x is the state vector and u is the input 019 * vector, the B matrix(continuous input matrix) is calculated through a {@link 020 * edu.wpi.first.math.system.NumericalJacobian}. In this case f has to be control-affine (of the 021 * form f(x) + Bu). 022 * 023 * <p>The feedforward is calculated as <strong> u_ff = B<sup>+</sup> (rDot - f(x))</strong>, where 024 * <strong> B<sup>+</sup> </strong> is the pseudoinverse of B. 025 * 026 * <p>This feedforward does not account for a dynamic B matrix, B is either determined or supplied 027 * when the feedforward is created and remains constant. 028 * 029 * <p>For more on the underlying math, read 030 * https://file.tavsys.net/control/controls-engineering-in-frc.pdf. 031 */ 032public class ControlAffinePlantInversionFeedforward<States extends Num, Inputs extends Num> { 033 /** The current reference state. */ 034 private Matrix<States, N1> m_r; 035 036 /** The computed feedforward. */ 037 private Matrix<Inputs, N1> m_uff; 038 039 private final Matrix<States, Inputs> m_B; 040 041 private final Nat<Inputs> m_inputs; 042 043 private final double m_dt; 044 045 /** The model dynamics. */ 046 private final BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<States, N1>> m_f; 047 048 /** 049 * Constructs a feedforward with given model dynamics as a function of state and input. 050 * 051 * @param states A {@link Nat} representing the number of states. 052 * @param inputs A {@link Nat} representing the number of inputs. 053 * @param f A vector-valued function of x, the state, and u, the input, that returns the 054 * derivative of the state vector. HAS to be control-affine (of the form f(x) + Bu). 055 * @param dtSeconds The timestep between calls of calculate(). 056 */ 057 public ControlAffinePlantInversionFeedforward( 058 Nat<States> states, 059 Nat<Inputs> inputs, 060 BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<States, N1>> f, 061 double dtSeconds) { 062 this.m_dt = dtSeconds; 063 this.m_f = f; 064 this.m_inputs = inputs; 065 066 this.m_B = 067 NumericalJacobian.numericalJacobianU( 068 states, inputs, m_f, new Matrix<>(states, Nat.N1()), new Matrix<>(inputs, Nat.N1())); 069 070 m_r = new Matrix<>(states, Nat.N1()); 071 m_uff = new Matrix<>(inputs, Nat.N1()); 072 073 reset(); 074 } 075 076 /** 077 * Constructs a feedforward with given model dynamics as a function of state, and the plant's 078 * B(continuous input matrix) matrix. 079 * 080 * @param states A {@link Nat} representing the number of states. 081 * @param inputs A {@link Nat} representing the number of inputs. 082 * @param f A vector-valued function of x, the state, that returns the derivative of the state 083 * vector. 084 * @param B Continuous input matrix of the plant being controlled. 085 * @param dtSeconds The timestep between calls of calculate(). 086 */ 087 public ControlAffinePlantInversionFeedforward( 088 Nat<States> states, 089 Nat<Inputs> inputs, 090 Function<Matrix<States, N1>, Matrix<States, N1>> f, 091 Matrix<States, Inputs> B, 092 double dtSeconds) { 093 this.m_dt = dtSeconds; 094 this.m_inputs = inputs; 095 096 this.m_f = (x, u) -> f.apply(x); 097 this.m_B = B; 098 099 m_r = new Matrix<>(states, Nat.N1()); 100 m_uff = new Matrix<>(inputs, Nat.N1()); 101 102 reset(); 103 } 104 105 /** 106 * Returns the previously calculated feedforward as an input vector. 107 * 108 * @return The calculated feedforward. 109 */ 110 public Matrix<Inputs, N1> getUff() { 111 return m_uff; 112 } 113 114 /** 115 * Returns an element of the previously calculated feedforward. 116 * 117 * @param row Row of uff. 118 * @return The row of the calculated feedforward. 119 */ 120 public double getUff(int row) { 121 return m_uff.get(row, 0); 122 } 123 124 /** 125 * Returns the current reference vector r. 126 * 127 * @return The current reference vector. 128 */ 129 public Matrix<States, N1> getR() { 130 return m_r; 131 } 132 133 /** 134 * Returns an element of the current reference vector r. 135 * 136 * @param row Row of r. 137 * @return The row of the current reference vector. 138 */ 139 public double getR(int row) { 140 return m_r.get(row, 0); 141 } 142 143 /** 144 * Resets the feedforward with a specified initial state vector. 145 * 146 * @param initialState The initial state vector. 147 */ 148 public void reset(Matrix<States, N1> initialState) { 149 m_r = initialState; 150 m_uff.fill(0.0); 151 } 152 153 /** Resets the feedforward with a zero initial state vector. */ 154 public void reset() { 155 m_r.fill(0.0); 156 m_uff.fill(0.0); 157 } 158 159 /** 160 * Calculate the feedforward with only the desired future reference. This uses the internally 161 * stored "current" reference. 162 * 163 * <p>If this method is used the initial state of the system is the one set using {@link 164 * LinearPlantInversionFeedforward#reset(Matrix)}. If the initial state is not set it defaults to 165 * a zero vector. 166 * 167 * @param nextR The reference state of the future timestep (k + dt). 168 * @return The calculated feedforward. 169 */ 170 public Matrix<Inputs, N1> calculate(Matrix<States, N1> nextR) { 171 return calculate(m_r, nextR); 172 } 173 174 /** 175 * Calculate the feedforward with current and future reference vectors. 176 * 177 * @param r The reference state of the current timestep (k). 178 * @param nextR The reference state of the future timestep (k + dt). 179 * @return The calculated feedforward. 180 */ 181 public Matrix<Inputs, N1> calculate(Matrix<States, N1> r, Matrix<States, N1> nextR) { 182 var rDot = (nextR.minus(r)).div(m_dt); 183 184 m_uff = m_B.solve(rDot.minus(m_f.apply(r, new Matrix<>(m_inputs, Nat.N1())))); 185 186 m_r = nextR; 187 return m_uff; 188 } 189}