|
| 1 | +## rocket will take 100 time steps |
| 2 | +ts.length <- 100 |
| 3 | + |
| 4 | +## the acceleration will drive the motion |
| 5 | +a <- rep(0.5, ts.length) |
| 6 | + |
| 7 | +## position and velocity start at 0 |
| 8 | +x <- rep(0, ts.length) |
| 9 | +v <- rep(0, ts.length) |
| 10 | +for (ts in 2:ts.length) { |
| 11 | + x[ts] <- v[ts - 1] * 2 + x[ts - 1] + 1/2 * a[ts - 1] ^ 2 |
| 12 | + x[ts] <- x[ts] + rnorm(1, sd = 20) ## stochastic component |
| 13 | + v[ts] <- v[ts - 1] + 2 * a[ts - 1] |
| 14 | +} |
| 15 | + |
| 16 | +par(mfrow = c(3, 1)) |
| 17 | +plot(x, main = "Position", type = 'l') |
| 18 | +plot(v, main = "Velocity", type = 'l') |
| 19 | +plot(a, main = "Acceleration", type = 'l') |
| 20 | + |
| 21 | +## simulate noisy sensor measurement |
| 22 | +z <- x + rnorm(ts.length, sd = 300) |
| 23 | +par(mfrow = c(1, 1)) |
| 24 | +plot(x, ylim = range(c(x, z))) |
| 25 | +lines(z) |
| 26 | + |
| 27 | +## apply a Kalman filter |
| 28 | +kalman.motion <- function(z, Q, R, A, H) { |
| 29 | + dimState = dim(Q)[1] |
| 30 | + |
| 31 | + xhatminus <- array(rep(0, ts.length * dimState), |
| 32 | + c(ts.length, dimState)) |
| 33 | + xhat <- array(rep(0, ts.length * dimState), |
| 34 | + c(ts.length, dimState)) |
| 35 | + |
| 36 | + Pminus <- array(rep(0, ts.length * dimState * dimState), |
| 37 | + c(ts.length, dimState, dimState)) |
| 38 | + P <- array(rep(0, ts.length * dimState * dimState), |
| 39 | + c(ts.length, dimState, dimState)) |
| 40 | + |
| 41 | + K <- array(rep(0, ts.length * dimState), |
| 42 | + c(ts.length, dimState)) # Kalman gain |
| 43 | + |
| 44 | + # initial guesses = starting at 0 for all metrics |
| 45 | + xhat[1, ] <- rep(0, dimState) |
| 46 | + P[1, , ] <- diag(dimState) |
| 47 | + |
| 48 | + for (k in 2:ts.length) { |
| 49 | + # time update |
| 50 | + xhatminus[k, ] <- A %*% matrix(xhat[k-1, ]) |
| 51 | + Pminus[k, , ] <- A %*% P[k-1, , ] %*% t(A) + Q |
| 52 | + |
| 53 | + K[k, ] <- Pminus[k, , ] %*% H %*% |
| 54 | + solve( t(H) %*% Pminus[k, , ] %*% H + R ) |
| 55 | + xhat[k, ] <- xhatminus[k, ] + K[k, ] %*% |
| 56 | + (z[k] - t(H) %*% xhatminus[k, ]) |
| 57 | + P[k, , ] <- (diag(dimState) - K[k, ] %*% t(H)) %*% Pminus[k, , ] |
| 58 | + } |
| 59 | + |
| 60 | + ## we return both the forecast and the smoothed value |
| 61 | + return(list(xhat = xhat, xhatminus = xhatminus)) |
| 62 | +} |
| 63 | + |
| 64 | +## noise parameters |
| 65 | +R <- 10^2 ## measurement variance - this value should be set |
| 66 | + ## according to known physical limits of measuring tool |
| 67 | + ## we set it consistent with the noise we added to x |
| 68 | + ## to produce x in the data generation above |
| 69 | +Q <- 10 ## process variance - usually regarded as hyperparameter |
| 70 | + ## to be used to maximize performance |
| 71 | + |
| 72 | +## dynamical parameters |
| 73 | +A <- matrix(1) ## x_t = A * x_t - 1 (how prior x affects later x) |
| 74 | +H <- matrix(1) ## y_t = H * x_t (translating state to measurement) |
| 75 | + |
| 76 | +## run the data through the Kalman filtering method |
| 77 | +xhat <- kalman.motion(z, diag(1) * Q, R, A, H)[[1]] |
| 78 | +xhatminus <- kalman.motion(z, diag(1) * Q, R, A, H)[[2]] |
| 79 | + |
| 80 | +## visualization |
| 81 | +plot(z, ylim = range(c(x, z)), type = 'l', |
| 82 | + col = "black", lwd = 2) |
| 83 | +lines(x, col = "red", lwd = 2, lty = 2) |
| 84 | +lines(xhat, col = "orange", lwd = 1, lty = 3) |
| 85 | +lines(xhatminus, col = "blue", lwd = 1, lty = 4) |
| 86 | +legend("topleft", |
| 87 | + legend = c("Measured", "Actual", "Filtered", "Forecast"), |
| 88 | + col = c("black", "red", "orange", "blue"), |
| 89 | + lty = 1:4) |
0 commit comments