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