2020-12-26 14:12:05 -08:00
|
|
|
// Copyright (c) FIRST and other WPILib contributors.
|
|
|
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
|
|
|
// the WPILib BSD license file in the root directory of this project.
|
2020-08-14 23:40:33 -07:00
|
|
|
|
2021-05-21 22:29:52 -07:00
|
|
|
package edu.wpi.first.math;
|
2020-08-14 23:40:33 -07:00
|
|
|
|
2021-05-01 15:53:30 +00:00
|
|
|
import edu.wpi.first.math.geometry.Pose2d;
|
|
|
|
|
import edu.wpi.first.math.numbers.N1;
|
|
|
|
|
import edu.wpi.first.math.numbers.N3;
|
|
|
|
|
import edu.wpi.first.math.numbers.N4;
|
2020-12-29 22:45:16 -08:00
|
|
|
import java.util.Random;
|
|
|
|
|
import org.ejml.simple.SimpleMatrix;
|
2020-08-14 23:40:33 -07:00
|
|
|
|
2020-10-22 23:53:48 -04:00
|
|
|
@SuppressWarnings("ParameterName")
|
2020-08-14 23:40:33 -07:00
|
|
|
public final class StateSpaceUtil {
|
|
|
|
|
private StateSpaceUtil() {
|
|
|
|
|
// Utility class
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-12-29 22:45:16 -08:00
|
|
|
* Creates a covariance matrix from the given vector for use with Kalman filters.
|
2020-08-14 23:40:33 -07:00
|
|
|
*
|
|
|
|
|
* <p>Each element is squared and placed on the covariance matrix diagonal.
|
|
|
|
|
*
|
2020-12-29 22:45:16 -08:00
|
|
|
* @param <States> Num representing the states of the system.
|
|
|
|
|
* @param states A Nat representing the states of the system.
|
|
|
|
|
* @param stdDevs For a Q matrix, its elements are the standard deviations of each state from how
|
|
|
|
|
* the model behaves. For an R matrix, its elements are the standard deviations for each
|
|
|
|
|
* output measurement.
|
2020-08-14 23:40:33 -07:00
|
|
|
* @return Process noise or measurement noise covariance matrix.
|
|
|
|
|
*/
|
|
|
|
|
@SuppressWarnings("MethodTypeParameterName")
|
|
|
|
|
public static <States extends Num> Matrix<States, States> makeCovarianceMatrix(
|
2020-12-29 22:45:16 -08:00
|
|
|
Nat<States> states, Matrix<States, N1> stdDevs) {
|
2020-08-14 23:40:33 -07:00
|
|
|
var result = new Matrix<>(states, states);
|
|
|
|
|
for (int i = 0; i < states.getNum(); i++) {
|
|
|
|
|
result.set(i, i, Math.pow(stdDevs.get(i, 0), 2));
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-12-29 22:45:16 -08:00
|
|
|
* Creates a vector of normally distributed white noise with the given noise intensities for each
|
|
|
|
|
* element.
|
2020-08-14 23:40:33 -07:00
|
|
|
*
|
2020-12-29 22:45:16 -08:00
|
|
|
* @param <N> Num representing the dimensionality of the noise vector to create.
|
|
|
|
|
* @param stdDevs A matrix whose elements are the standard deviations of each element of the noise
|
|
|
|
|
* vector.
|
2020-08-14 23:40:33 -07:00
|
|
|
* @return White noise vector.
|
|
|
|
|
*/
|
2020-12-29 22:45:16 -08:00
|
|
|
public static <N extends Num> Matrix<N, N1> makeWhiteNoiseVector(Matrix<N, N1> stdDevs) {
|
2020-08-14 23:40:33 -07:00
|
|
|
var rand = new Random();
|
|
|
|
|
|
|
|
|
|
Matrix<N, N1> result = new Matrix<>(new SimpleMatrix(stdDevs.getNumRows(), 1));
|
|
|
|
|
for (int i = 0; i < stdDevs.getNumRows(); i++) {
|
|
|
|
|
result.set(i, 0, rand.nextGaussian() * stdDevs.get(i, 0));
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a cost matrix from the given vector for use with LQR.
|
|
|
|
|
*
|
2020-12-29 22:45:16 -08:00
|
|
|
* <p>The cost matrix is constructed using Bryson's rule. The inverse square of each element in
|
|
|
|
|
* the input is taken and placed on the cost matrix diagonal.
|
2020-08-14 23:40:33 -07:00
|
|
|
*
|
2020-12-29 22:45:16 -08:00
|
|
|
* @param <States> Nat representing the states of the system.
|
|
|
|
|
* @param costs An array. For a Q matrix, its elements are the maximum allowed excursions of the
|
|
|
|
|
* states from the reference. For an R matrix, its elements are the maximum allowed excursions
|
|
|
|
|
* of the control inputs from no actuation.
|
2020-08-14 23:40:33 -07:00
|
|
|
* @return State excursion or control effort cost matrix.
|
|
|
|
|
*/
|
|
|
|
|
@SuppressWarnings("MethodTypeParameterName")
|
2020-12-29 22:45:16 -08:00
|
|
|
public static <States extends Num> Matrix<States, States> makeCostMatrix(
|
|
|
|
|
Matrix<States, N1> costs) {
|
2020-08-14 23:40:33 -07:00
|
|
|
Matrix<States, States> result =
|
|
|
|
|
new Matrix<>(new SimpleMatrix(costs.getNumRows(), costs.getNumRows()));
|
|
|
|
|
result.fill(0.0);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < costs.getNumRows(); i++) {
|
|
|
|
|
result.set(i, i, 1.0 / (Math.pow(costs.get(i, 0), 2)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns true if (A, B) is a stabilizable pair.
|
|
|
|
|
*
|
2020-12-29 22:45:16 -08:00
|
|
|
* <p>(A,B) is stabilizable if and only if the uncontrollable eigenvalues of A, if any, have
|
2021-07-29 22:42:43 -07:00
|
|
|
* absolute values less than one, where an eigenvalue is uncontrollable if rank(λI - A, B) %3C n
|
|
|
|
|
* where n is number of states.
|
2020-08-14 23:40:33 -07:00
|
|
|
*
|
|
|
|
|
* @param <States> Num representing the size of A.
|
|
|
|
|
* @param <Inputs> Num representing the columns of B.
|
2020-12-29 22:45:16 -08:00
|
|
|
* @param A System matrix.
|
|
|
|
|
* @param B Input matrix.
|
2020-08-14 23:40:33 -07:00
|
|
|
* @return If the system is stabilizable.
|
|
|
|
|
*/
|
|
|
|
|
@SuppressWarnings("MethodTypeParameterName")
|
|
|
|
|
public static <States extends Num, Inputs extends Num> boolean isStabilizable(
|
|
|
|
|
Matrix<States, States> A, Matrix<States, Inputs> B) {
|
2020-12-29 22:45:16 -08:00
|
|
|
return WPIMathJNI.isStabilizable(A.getNumRows(), B.getNumCols(), A.getData(), B.getData());
|
2020-08-14 23:40:33 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert a {@link Pose2d} to a vector of [x, y, theta], where theta is in radians.
|
|
|
|
|
*
|
|
|
|
|
* @param pose A pose to convert to a vector.
|
|
|
|
|
* @return The given pose in vector form, with the third element, theta, in radians.
|
|
|
|
|
*/
|
|
|
|
|
public static Matrix<N3, N1> poseToVector(Pose2d pose) {
|
2020-12-29 22:45:16 -08:00
|
|
|
return VecBuilder.fill(pose.getX(), pose.getY(), pose.getRotation().getRadians());
|
2020-08-14 23:40:33 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clamp the input u to the min and max.
|
|
|
|
|
*
|
2020-12-29 22:45:16 -08:00
|
|
|
* @param u The input to clamp.
|
2020-08-14 23:40:33 -07:00
|
|
|
* @param umin The minimum input magnitude.
|
|
|
|
|
* @param umax The maximum input magnitude.
|
2020-12-29 22:45:16 -08:00
|
|
|
* @param <I> The number of inputs.
|
|
|
|
|
* @return The clamped input.
|
2020-08-14 23:40:33 -07:00
|
|
|
*/
|
|
|
|
|
@SuppressWarnings({"ParameterName", "LocalVariableName"})
|
2020-12-29 22:45:16 -08:00
|
|
|
public static <I extends Num> Matrix<I, N1> clampInputMaxMagnitude(
|
|
|
|
|
Matrix<I, N1> u, Matrix<I, N1> umin, Matrix<I, N1> umax) {
|
2020-08-14 23:40:33 -07:00
|
|
|
var result = new Matrix<I, N1>(new SimpleMatrix(u.getNumRows(), 1));
|
|
|
|
|
for (int i = 0; i < u.getNumRows(); i++) {
|
2020-12-29 22:45:16 -08:00
|
|
|
result.set(i, 0, MathUtil.clamp(u.get(i, 0), umin.get(i, 0), umax.get(i, 0)));
|
2020-08-14 23:40:33 -07:00
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Normalize all inputs if any excedes the maximum magnitude. Useful for systems such as
|
|
|
|
|
* differential drivetrains.
|
|
|
|
|
*
|
2020-12-29 22:45:16 -08:00
|
|
|
* @param u The input vector.
|
2020-08-14 23:40:33 -07:00
|
|
|
* @param maxMagnitude The maximum magnitude any input can have.
|
2020-12-29 22:45:16 -08:00
|
|
|
* @param <I> The number of inputs.
|
2020-08-14 23:40:33 -07:00
|
|
|
* @return The normalizedInput
|
|
|
|
|
*/
|
2020-12-29 22:45:16 -08:00
|
|
|
public static <I extends Num> Matrix<I, N1> normalizeInputVector(
|
|
|
|
|
Matrix<I, N1> u, double maxMagnitude) {
|
2020-08-14 23:40:33 -07:00
|
|
|
double maxValue = u.maxAbs();
|
|
|
|
|
boolean isCapped = maxValue > maxMagnitude;
|
|
|
|
|
|
|
|
|
|
if (isCapped) {
|
|
|
|
|
return u.times(maxMagnitude / maxValue);
|
|
|
|
|
}
|
|
|
|
|
return u;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-28 17:35:35 -05:00
|
|
|
/**
|
2020-12-29 22:45:16 -08:00
|
|
|
* Convert a {@link Pose2d} to a vector of [x, y, cos(theta), sin(theta)], where theta is in
|
|
|
|
|
* radians.
|
2020-11-28 17:35:35 -05:00
|
|
|
*
|
|
|
|
|
* @param pose A pose to convert to a vector.
|
2021-06-10 20:46:47 -07:00
|
|
|
* @return The given pose in as a 4x1 vector of x, y, cos(theta), and sin(theta).
|
2020-11-28 17:35:35 -05:00
|
|
|
*/
|
|
|
|
|
public static Matrix<N4, N1> poseTo4dVector(Pose2d pose) {
|
|
|
|
|
return VecBuilder.fill(
|
2020-12-29 22:45:16 -08:00
|
|
|
pose.getTranslation().getX(),
|
|
|
|
|
pose.getTranslation().getY(),
|
|
|
|
|
pose.getRotation().getCos(),
|
|
|
|
|
pose.getRotation().getSin());
|
2020-11-28 17:35:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert a {@link Pose2d} to a vector of [x, y, theta], where theta is in radians.
|
|
|
|
|
*
|
|
|
|
|
* @param pose A pose to convert to a vector.
|
|
|
|
|
* @return The given pose in vector form, with the third element, theta, in radians.
|
|
|
|
|
*/
|
|
|
|
|
public static Matrix<N3, N1> poseTo3dVector(Pose2d pose) {
|
|
|
|
|
return VecBuilder.fill(
|
2020-12-29 22:45:16 -08:00
|
|
|
pose.getTranslation().getX(),
|
|
|
|
|
pose.getTranslation().getY(),
|
|
|
|
|
pose.getRotation().getRadians());
|
2020-11-28 17:35:35 -05:00
|
|
|
}
|
2020-08-14 23:40:33 -07:00
|
|
|
}
|