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; 006 007import edu.wpi.first.math.geometry.Pose2d; 008import edu.wpi.first.math.numbers.N1; 009import edu.wpi.first.math.numbers.N3; 010import edu.wpi.first.math.numbers.N4; 011import java.util.Random; 012import org.ejml.simple.SimpleMatrix; 013 014@SuppressWarnings("ParameterName") 015public final class StateSpaceUtil { 016 private static Random rand = new Random(); 017 018 private StateSpaceUtil() { 019 // Utility class 020 } 021 022 /** 023 * Creates a covariance matrix from the given vector for use with Kalman filters. 024 * 025 * <p>Each element is squared and placed on the covariance matrix diagonal. 026 * 027 * @param <States> Num representing the states of the system. 028 * @param states A Nat representing the states of the system. 029 * @param stdDevs For a Q matrix, its elements are the standard deviations of each state from how 030 * the model behaves. For an R matrix, its elements are the standard deviations for each 031 * output measurement. 032 * @return Process noise or measurement noise covariance matrix. 033 */ 034 @SuppressWarnings("MethodTypeParameterName") 035 public static <States extends Num> Matrix<States, States> makeCovarianceMatrix( 036 Nat<States> states, Matrix<States, N1> stdDevs) { 037 var result = new Matrix<>(states, states); 038 for (int i = 0; i < states.getNum(); i++) { 039 result.set(i, i, Math.pow(stdDevs.get(i, 0), 2)); 040 } 041 return result; 042 } 043 044 /** 045 * Creates a vector of normally distributed white noise with the given noise intensities for each 046 * element. 047 * 048 * @param <N> Num representing the dimensionality of the noise vector to create. 049 * @param stdDevs A matrix whose elements are the standard deviations of each element of the noise 050 * vector. 051 * @return White noise vector. 052 */ 053 public static <N extends Num> Matrix<N, N1> makeWhiteNoiseVector(Matrix<N, N1> stdDevs) { 054 Matrix<N, N1> result = new Matrix<>(new SimpleMatrix(stdDevs.getNumRows(), 1)); 055 for (int i = 0; i < stdDevs.getNumRows(); i++) { 056 result.set(i, 0, rand.nextGaussian() * stdDevs.get(i, 0)); 057 } 058 return result; 059 } 060 061 /** 062 * Creates a cost matrix from the given vector for use with LQR. 063 * 064 * <p>The cost matrix is constructed using Bryson's rule. The inverse square of each element in 065 * the input is taken and placed on the cost matrix diagonal. 066 * 067 * @param <States> Nat representing the states of the system. 068 * @param costs An array. For a Q matrix, its elements are the maximum allowed excursions of the 069 * states from the reference. For an R matrix, its elements are the maximum allowed excursions 070 * of the control inputs from no actuation. 071 * @return State excursion or control effort cost matrix. 072 */ 073 @SuppressWarnings("MethodTypeParameterName") 074 public static <States extends Num> Matrix<States, States> makeCostMatrix( 075 Matrix<States, N1> costs) { 076 Matrix<States, States> result = 077 new Matrix<>(new SimpleMatrix(costs.getNumRows(), costs.getNumRows())); 078 result.fill(0.0); 079 080 for (int i = 0; i < costs.getNumRows(); i++) { 081 result.set(i, i, 1.0 / (Math.pow(costs.get(i, 0), 2))); 082 } 083 084 return result; 085 } 086 087 /** 088 * Returns true if (A, B) is a stabilizable pair. 089 * 090 * <p>(A, B) is stabilizable if and only if the uncontrollable eigenvalues of A, if any, have 091 * absolute values less than one, where an eigenvalue is uncontrollable if rank(λI - A, B) %3C n 092 * where n is the number of states. 093 * 094 * @param <States> Num representing the size of A. 095 * @param <Inputs> Num representing the columns of B. 096 * @param A System matrix. 097 * @param B Input matrix. 098 * @return If the system is stabilizable. 099 */ 100 @SuppressWarnings("MethodTypeParameterName") 101 public static <States extends Num, Inputs extends Num> boolean isStabilizable( 102 Matrix<States, States> A, Matrix<States, Inputs> B) { 103 return WPIMathJNI.isStabilizable(A.getNumRows(), B.getNumCols(), A.getData(), B.getData()); 104 } 105 106 /** 107 * Returns true if (A, C) is a detectable pair. 108 * 109 * <p>(A, C) is detectable if and only if the unobservable eigenvalues of A, if any, have absolute 110 * values less than one, where an eigenvalue is unobservable if rank(λI - A; C) %3C n where n is 111 * the number of states. 112 * 113 * @param <States> Num representing the size of A. 114 * @param <Outputs> Num representing the rows of C. 115 * @param A System matrix. 116 * @param C Output matrix. 117 * @return If the system is detectable. 118 */ 119 @SuppressWarnings("MethodTypeParameterName") 120 public static <States extends Num, Outputs extends Num> boolean isDetectable( 121 Matrix<States, States> A, Matrix<Outputs, States> C) { 122 return WPIMathJNI.isStabilizable( 123 A.getNumRows(), C.getNumRows(), A.transpose().getData(), C.transpose().getData()); 124 } 125 126 /** 127 * Convert a {@link Pose2d} to a vector of [x, y, theta], where theta is in radians. 128 * 129 * @param pose A pose to convert to a vector. 130 * @return The given pose in vector form, with the third element, theta, in radians. 131 */ 132 public static Matrix<N3, N1> poseToVector(Pose2d pose) { 133 return VecBuilder.fill(pose.getX(), pose.getY(), pose.getRotation().getRadians()); 134 } 135 136 /** 137 * Clamp the input u to the min and max. 138 * 139 * @param u The input to clamp. 140 * @param umin The minimum input magnitude. 141 * @param umax The maximum input magnitude. 142 * @param <I> The number of inputs. 143 * @return The clamped input. 144 */ 145 @SuppressWarnings({"ParameterName", "LocalVariableName"}) 146 public static <I extends Num> Matrix<I, N1> clampInputMaxMagnitude( 147 Matrix<I, N1> u, Matrix<I, N1> umin, Matrix<I, N1> umax) { 148 var result = new Matrix<I, N1>(new SimpleMatrix(u.getNumRows(), 1)); 149 for (int i = 0; i < u.getNumRows(); i++) { 150 result.set(i, 0, MathUtil.clamp(u.get(i, 0), umin.get(i, 0), umax.get(i, 0))); 151 } 152 return result; 153 } 154 155 /** 156 * Renormalize all inputs if any exceeds the maximum magnitude. Useful for systems such as 157 * differential drivetrains. 158 * 159 * @param u The input vector. 160 * @param maxMagnitude The maximum magnitude any input can have. 161 * @param <I> The number of inputs. 162 * @return The normalizedInput 163 */ 164 public static <I extends Num> Matrix<I, N1> desaturateInputVector( 165 Matrix<I, N1> u, double maxMagnitude) { 166 double maxValue = u.maxAbs(); 167 boolean isCapped = maxValue > maxMagnitude; 168 169 if (isCapped) { 170 return u.times(maxMagnitude / maxValue); 171 } 172 return u; 173 } 174 175 /** 176 * Convert a {@link Pose2d} to a vector of [x, y, cos(theta), sin(theta)], where theta is in 177 * radians. 178 * 179 * @param pose A pose to convert to a vector. 180 * @return The given pose in as a 4x1 vector of x, y, cos(theta), and sin(theta). 181 */ 182 public static Matrix<N4, N1> poseTo4dVector(Pose2d pose) { 183 return VecBuilder.fill( 184 pose.getTranslation().getX(), 185 pose.getTranslation().getY(), 186 pose.getRotation().getCos(), 187 pose.getRotation().getSin()); 188 } 189 190 /** 191 * Convert a {@link Pose2d} to a vector of [x, y, theta], where theta is in radians. 192 * 193 * @param pose A pose to convert to a vector. 194 * @return The given pose in vector form, with the third element, theta, in radians. 195 */ 196 public static Matrix<N3, N1> poseTo3dVector(Pose2d pose) { 197 return VecBuilder.fill( 198 pose.getTranslation().getX(), 199 pose.getTranslation().getY(), 200 pose.getRotation().getRadians()); 201 } 202}