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