(* :Title: Particle Filter *) (* :Author: Mark Fisher *) (* :Context: ParticleFilter` *) (* :Mathematica Version: 5.0 *) (* :History: Version 0.0 April 2005 *) (* :Summary: SIR (Sampling Importance Resampling) particle filter and backward simulation particle smoother *) (* :Discussion: This package implements ParticleFilter (an SIR particle filter) and ParticleSmoother (a backward simulation particle smoother that takes the output from ParticleFilter as input). In particular, given {loglikelihood, states} = ParticleFilter[data, {x0, w0}, f, gp, IncludeStates -> True]; the marginal loglikelihood of the parameters (implicit in f and gp) is given by loglikelihood. To compute n draws of paths from the smoothed distribution, use collected = CollectStates[states]; smoothed = ParticleSmoother[collected, fp, n]; *) (* :References: Doucet, de Freitas, and Gordon (editors), "Sequenctial Monte Carlo Methods in Practice", Springer, 2001. Arulampalam, Maskell, Gordon, and Clapp, "A tutorial on particle filters for online nonlinear/non-Gaussian Bayesian tracking", IEEE Transactions on Signal Processing, vol. 50, no. 2, February 2002. Godsill, Doucet, and West, "Monte Carlo smoothing for nonlinear time series", Journal of the American Statistical Association, vol. 99, no. 465, pp. 156-168, March 2004. *) (* :Example: (* This is the now classic nonlinear example. The functions are compiled for speed. The parameters are accessible via the functions. *) Clear[signal, data, f, fCompiled, gp, gpCompiled, fp, fpCompiled, swarmsize, prior, loglikelihood, states, collected, smoothed] signal = Rest @ FoldList[ {.5#1[[1]] + 25#1[[1]]/(1 + #1[[1]]^2) + 8 Cos[1.2#1[[2]]] + #2, #1[[2]] + 1} &, {Random[NormalDistribution[0, Sqrt[10]]], 0}, RandomArray[NormalDistribution[0, Sqrt[10]], 100]]; data = Partition[signal[[All, 1]]^2/20, 1] + RandomArray[NormalDistribution[0, 1], 100]; f[x_, {a_, b_, c_, s2v_}] := fCompiled[ x[[All, 1]], x[[1, 2]], RandomArray[NormalDistribution[0, Sqrt[s2v]], Length[x]], a, b, c ] fCompiled = Compile[ {{x, _Real, 1}, {t, _Real, 0}, {v, _Real, 1}, {a, _Real, 0}, {b, _Real, 0}, {c, _Real, 0}}, MapThread[{a #1 + b #1/(1 + #1^2) + c*Cos[1.2t] + #2, t + 1} &, {x, v}]]; gp[x_, y_, {d_, s2w_}] := gpCompiled[x[[All, 1]], y[[1]], d, s2w] gpCompiled = Compile[ {{x, _Real, 1}, {y, _Real, 0}, {d, _Real, 0}, {s2w, _Real, 0}}, E^-((y - d*#1^2)^2/(2*s2w^2))/(Sqrt[2*Pi]*s2w) & /@ x ]; fp[x1_, x0_, {a_, b_, c_, s2v_}] := fpCompiled[x1[[1]], x0[[All, 1]], x0[[1, 2]], a, b, c, s2v] fpCompiled = Compile[ {{x1, _Real, 0}, {x0, _Real, 1}, {t, _Real, 0}, {a, _Real, 0}, {b, _Real, 0}, {c, _Real, 0}, {s2v, _Real, 0}}, 1/(E^((x1 - c*Cos[1.2*t] - a*#1 - (b*#1)/(1 + #1^2))^2/(2*s2v^2))*s2v) & /@ x0 ]; swarmsize = 10^4; prior = Thread[{RandomArray[NormalDistribution[0, Sqrt[10]], swarmsize], 0}]; {loglikelihood, states} = ParticleFilter[data, prior, f[#, {.5, 25, 8, 10}]&, gp[##, {.05, 1}]&, IncludeStates -> True]; collected = CollectStates[states]; smoothed = ParticleSmoother[collected, fp[##, {.5, 25, 8, 10}] &, 10]; *) BeginPackage["ParticleFilter`"] ParticleFilter::usage = "ParticleFilter[data, x0, f, gp] takes a list of data vectors, a list of state vectors x0 that represent the prior information (the particle swarm), a transition function f that takes the list state vectors as an argument and returns the list of predictions for the new state vectors, and a function gp that takes two arguments, the list of state vectors and the current a single data point, that returns likelihood of the data given the state from the measurement equation. ParticleFilter returns the log of the marginal likelihood of the (implicit) parameters. By setting the option IncludeStates -> True, ParticleFilter also returns the list of states, suitable as input to ParticleSmoother. ParticleFilter also takes the option MCMCSmooth." IncludeStates::usage = "IncludeStates is an option for ParticleFilter, which specifies whether to include the filtered particle swarms in the output. The default setting is IncludeStates -> False." ParticleSmoother::usage = "ParticleSmoother[states, fp, n] takes the list of states produced by ParticleFilter and a function fp that takes two arguments (the first of which is a single current state vector and the second of which is a list of previous state vectors) and returns a list of likelihoods of the previous states given the value of the current state. ParticleSmoother returns n paths from the joint smoothed distribution of the state vectors." FilterStep::usage = "FilterStep[{x0, loglike0}, y, f, gp] is called by ParticleFilter and returns {x1, loglike1}." SmootherStep::usage = "SmootherStep[x1, {x0, w0}, fp] is called by ParticleSmoother. SmootherStep returns a list of previous states sampled from x0 corresponding to the list of current states x1." Resample::usage = "Resample[{x, w}, n] returns a list of n samples drawn with replacement from x with probabilities proportional to the nonnegative weights w (made with SystematicResampling). Resample[{x, w}] returns a single draw (made with BinarySerach). Resample is called by ParticleFilter and ParticleSmoother." SystematicResampling::usage = "SystematicResampling[weights, n] returns a list of n positions computed from the nonnegative weights according to systematic resampling. SystematicResampling is called by FilterStep (within ParticleFilter)." BinarySearch::usage = "BinarySearch[weights] returns a single position computed from the nonnegative weights using binary search. BinarySearch is called by SmootherStep (within ParticleSmoother)." CollectStates::usage = "CollectStates is an option for ParticleSmoother that specifies whether to run the function CollectStates on the input. CollectStates[states] take a list of states (as repreented by particle swarms such as can be produced by ParticleFilter) and returns a list of the union of the particles paired with their weights. The output is suitable as input for ParticleSmoother." MCMCSmooth::usage = "MCMCSmooth is an option for ParticleFilter that specifies whether to run the function MCMCSmooth at the end of FilterStep after resampling. The default setting is MCMCSmooth -> False. MCMCSmooth[{x1, w}, y, x0, f, gp] compares the likelihood each element of x1 with a draw from f[x0] and replaces it with the draw according to the ususal Metropolis criterion. The purpose is to obtain more distinct values while maintaining the same distribution." PriorToPosterior::usage = "PriorToPosterior[prior, likelihood] takes a list of points that represent a prior distribution and a likelihood function and returns a list of points that represents the posterior distribution." Begin["`Private`"] PriorToPosterior[prior_, Lfun_] := prior[[ SystematicResampling[Lfun /@ prior, Length[prior]] ]] (***** filtering *****) Options[ParticleFilter] = {IncludeStates -> False, MCMCSmooth -> False} ParticleFilter[data_, x0_, f_, gp_, opts___?OptionQ] := If[TrueQ[IncludeStates /. {opts} /. Options[ParticleFilter]], (* then *) {#[[-1, -1]], #[[All, 1]]}& @ Rest[FoldList[FilterStep[##, f, gp, opts]&, {x0, 0}, data]], (* else *) Fold[FilterStep[##, f, gp, opts]&, {x0, 0}, data][[2]] ] FilterStep[{x0_, loglike_}, y_, f_, gp_, opts___] := Module[{x1, w, pos}, x1 = f[x0]; w = gp[x1, y]; pos = SystematicResampling[w, Length[w]]; {If[TrueQ[MCMCSmooth /. {opts} /. Options[ParticleFilter]], (* then *) (* MCMCSmooth[{x1[[ pos ]], w[[ pos ]]}, y, x0, f, gp], *) MCMCChoose[x1[[ pos ]], w[[ pos ]], x1, w], (* else *) x1[[ pos ]] ], Log[Mean[w]] + loglike} ] (* c[[i]] is the number of children of the i-th parent p[[j]] is the position of the parent of the j-th child w is the list of nonnegative weights (need not be normalized) M is the desired number of children *) SystematicResampling = Compile[{{w, _Real, 1}, {M, _Integer, 0}}, Module[{ c = (Rest[#] - Most[#])& @ Floor[FoldList[Plus, 0, M * w/(Plus@@w)] - Random[]], p = Table[0, {M}], j = 1}, Do[ Do[p[[j]] = i; j++, {c[[i]]}], {i, Length[w]}]; p ]] (* use {x1[[ pos ]], w[[ pos ]]} as first argument, where pos = SystematicResampling[w, Length[w]] *) MCMCSmooth[{x1_, w_}, y_, x0_, f_, gp_] := With[{proposal = f[x0]}, MCMCChoose[x1, w, proposal, gp[proposal, y]] ] MCMCChoose = Compile[ {{x1, _Real, 2}, {w, _Real, 1}, {xp, _Real, 2}, {wp, _Real, 1}}, With[{alpha = If[# >= 0, 1, 0] & /@ (wp - w * Table[Random[], {Length[w]}])}, alpha * xp + (1 - alpha) * x1 ]] (***** smoothing *****) Options[ParticleSmoother] = {CollectStates -> False} ParticleSmoother[states_, fp_, n_Integer:1, opts___?OptionQ] := Module[{ collected = Reverse @ If[TrueQ[CollectStates /. {opts} /. Options[ParticleSmoother]], (* then *) CollectStates[states], (* else *) states], result}, result = Transpose @ Reverse @ FoldList[ SmootherStep[##, fp]&, Resample[First[collected], n], Rest[collected] ]; If[n == 1, result[[1]], result] ] SmootherStep[x1_, {x0_, w0_}, fp_] := x0[[ BinarySearch[w0 * fp[#, x0]] ]]& /@ x1 CollectStates[states_] := Transpose[{First[#], Length[#]}& /@ Split[Sort[#]]]& /@ states Resample[{x_, w_}, n_] := x[[ SystematicResampling[w, n] ]] Resample[{x_, w_}] := x[[ BinarySearch[w] ]] BinarySearch = Compile[{{w, _Real, 1}}, Module[{ lo = 0, hi = Length[w], mid = 0, r = Random[], c = Most@FoldList[Plus, 0, w/Plus @@ w]}, While[hi - lo > 1, mid = lo + Floor[(hi - lo)/2]; If[r > c[[mid + 1]], lo = mid, hi = mid ] ]; hi ]] End[] EndPackage[]