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}