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.system; 006 007import edu.wpi.first.math.Matrix; 008import edu.wpi.first.math.Num; 009import edu.wpi.first.math.Pair; 010import org.ejml.simple.SimpleMatrix; 011 012@SuppressWarnings({"ParameterName", "MethodTypeParameterName"}) 013public final class Discretization { 014 private Discretization() { 015 // Utility class 016 } 017 018 /** 019 * Discretizes the given continuous A matrix. 020 * 021 * @param <States> Num representing the number of states. 022 * @param contA Continuous system matrix. 023 * @param dtSeconds Discretization timestep. 024 * @return the discrete matrix system. 025 */ 026 public static <States extends Num> Matrix<States, States> discretizeA( 027 Matrix<States, States> contA, double dtSeconds) { 028 return contA.times(dtSeconds).exp(); 029 } 030 031 /** 032 * Discretizes the given continuous A and B matrices. 033 * 034 * @param <States> Nat representing the states of the system. 035 * @param <Inputs> Nat representing the inputs to the system. 036 * @param contA Continuous system matrix. 037 * @param contB Continuous input matrix. 038 * @param dtSeconds Discretization timestep. 039 * @return a Pair representing discA and diskB. 040 */ 041 @SuppressWarnings("LocalVariableName") 042 public static <States extends Num, Inputs extends Num> 043 Pair<Matrix<States, States>, Matrix<States, Inputs>> discretizeAB( 044 Matrix<States, States> contA, Matrix<States, Inputs> contB, double dtSeconds) { 045 var scaledA = contA.times(dtSeconds); 046 var scaledB = contB.times(dtSeconds); 047 048 int states = contA.getNumRows(); 049 int inputs = contB.getNumCols(); 050 var M = new Matrix<>(new SimpleMatrix(states + inputs, states + inputs)); 051 M.assignBlock(0, 0, scaledA); 052 M.assignBlock(0, scaledA.getNumCols(), scaledB); 053 var phi = M.exp(); 054 055 var discA = new Matrix<States, States>(new SimpleMatrix(states, states)); 056 var discB = new Matrix<States, Inputs>(new SimpleMatrix(states, inputs)); 057 058 discA.extractFrom(0, 0, phi); 059 discB.extractFrom(0, contB.getNumRows(), phi); 060 061 return new Pair<>(discA, discB); 062 } 063 064 /** 065 * Discretizes the given continuous A and Q matrices. 066 * 067 * @param <States> Nat representing the number of states. 068 * @param contA Continuous system matrix. 069 * @param contQ Continuous process noise covariance matrix. 070 * @param dtSeconds Discretization timestep. 071 * @return a pair representing the discrete system matrix and process noise covariance matrix. 072 */ 073 @SuppressWarnings("LocalVariableName") 074 public static <States extends Num> 075 Pair<Matrix<States, States>, Matrix<States, States>> discretizeAQ( 076 Matrix<States, States> contA, Matrix<States, States> contQ, double dtSeconds) { 077 int states = contA.getNumRows(); 078 079 // Make continuous Q symmetric if it isn't already 080 Matrix<States, States> Q = contQ.plus(contQ.transpose()).div(2.0); 081 082 // Set up the matrix M = [[-A, Q], [0, A.T]] 083 final var M = new Matrix<>(new SimpleMatrix(2 * states, 2 * states)); 084 M.assignBlock(0, 0, contA.times(-1.0)); 085 M.assignBlock(0, states, Q); 086 M.assignBlock(states, 0, new Matrix<>(new SimpleMatrix(states, states))); 087 M.assignBlock(states, states, contA.transpose()); 088 089 final var phi = M.times(dtSeconds).exp(); 090 091 // Phi12 = phi[0:States, States:2*States] 092 // Phi22 = phi[States:2*States, States:2*States] 093 final Matrix<States, States> phi12 = phi.block(states, states, 0, states); 094 final Matrix<States, States> phi22 = phi.block(states, states, states, states); 095 096 final var discA = phi22.transpose(); 097 098 Q = discA.times(phi12); 099 100 // Make discrete Q symmetric if it isn't already 101 final var discQ = Q.plus(Q.transpose()).div(2.0); 102 103 return new Pair<>(discA, discQ); 104 } 105 106 /** 107 * Discretizes the given continuous A and Q matrices. 108 * 109 * <p>Rather than solving a 2N x 2N matrix exponential like in DiscretizeQ() (which is expensive), 110 * we take advantage of the structure of the block matrix of A and Q. 111 * 112 * <p>The exponential of A*t, which is only N x N, is relatively cheap. 2) The upper-right quarter 113 * of the 2N x 2N matrix, which we can approximate using a taylor series to several terms and 114 * still be substantially cheaper than taking the big exponential. 115 * 116 * @param <States> Nat representing the number of states. 117 * @param contA Continuous system matrix. 118 * @param contQ Continuous process noise covariance matrix. 119 * @param dtSeconds Discretization timestep. 120 * @return a pair representing the discrete system matrix and process noise covariance matrix. 121 */ 122 @SuppressWarnings("LocalVariableName") 123 public static <States extends Num> 124 Pair<Matrix<States, States>, Matrix<States, States>> discretizeAQTaylor( 125 Matrix<States, States> contA, Matrix<States, States> contQ, double dtSeconds) { 126 // Make continuous Q symmetric if it isn't already 127 Matrix<States, States> Q = contQ.plus(contQ.transpose()).div(2.0); 128 129 Matrix<States, States> lastTerm = Q.copy(); 130 double lastCoeff = dtSeconds; 131 132 // Aᵀⁿ 133 Matrix<States, States> Atn = contA.transpose(); 134 Matrix<States, States> phi12 = lastTerm.times(lastCoeff); 135 136 // i = 6 i.e. 5th order should be enough precision 137 for (int i = 2; i < 6; ++i) { 138 lastTerm = contA.times(-1).times(lastTerm).plus(Q.times(Atn)); 139 lastCoeff *= dtSeconds / ((double) i); 140 141 phi12 = phi12.plus(lastTerm.times(lastCoeff)); 142 143 Atn = Atn.times(contA.transpose()); 144 } 145 146 var discA = discretizeA(contA, dtSeconds); 147 Q = discA.times(phi12); 148 149 // Make Q symmetric if it isn't already 150 var discQ = Q.plus(Q.transpose()).div(2.0); 151 152 return new Pair<>(discA, discQ); 153 } 154 155 /** 156 * Returns a discretized version of the provided continuous measurement noise covariance matrix. 157 * Note that dt=0.0 divides R by zero. 158 * 159 * @param <O> Nat representing the number of outputs. 160 * @param R Continuous measurement noise covariance matrix. 161 * @param dtSeconds Discretization timestep. 162 * @return Discretized version of the provided continuous measurement noise covariance matrix. 163 */ 164 public static <O extends Num> Matrix<O, O> discretizeR(Matrix<O, O> R, double dtSeconds) { 165 return R.div(dtSeconds); 166 } 167}