Reputation: 570
I am looking for an idiomatic approach to programming filters in F#. For clarity, I refer to a filter as a function that uses a series of measurements over time and produces evolving estimates. This implies that the function be able to maintain state. For example, in Python one could use coroutines to maintain state in a very clean way.
What I'm looking for is an idiomatic approach to programming filters in F#. Given that my mind is thoroughly polluted with OOP and procedural principles, naturally I came up with classes to express them. Is there a more idiomatic approach to filtering in F#, one that could perhaps open up other benefits of the functional paradigm?
open System
open MathNet.Numerics.LinearAlgebra
open MathNet.Numerics.Random
open MathNet.Numerics.Distributions
open MathNet.Numerics.Statistics
open FSharp.Charting
type ScalarKalman (A : float, H : float, Q : float, R : float) = class
let mutable A = A
let mutable H = H
let mutable Q = Q
let mutable R = R
let mutable p = 0.
let mutable x = 0.
let mutable k = 0.
let mutable result = 0.
member this.X
with get() = x
and set(value) = x <- value
member this.P
with get() = p
and set(value) = p <- value
member this.K
with get() = k
and set(value) = k <- value
member this.update(newVal : float) =
let xp = A * this.X
let Pp = A * this.P * A + Q
this.K <- Pp * H / (H * Pp * H + R)
this.X <- xp + this.K * (newVal - H * xp)
this.P <- Pp - this.K * H * Pp
end
let n = 100
let obsv = [|for i in 0 .. n do yield 0.|]
let smv = [|for i in 0 .. n do yield 0.|]
let kal = new ScalarKalman(1., 1., 0., 5.)
kal.P <- 4.
kal.X <- 6.
for i in 0 .. n do
obsv.[i] <- Normal.Sample(10., 5.)
kal.update(obsv.[i])
smv.[i] <- kal.X
Chart.Combine([obsv |> Chart.FastLine
smv |> Chart.FastLine]) |> Chart.Show
Upvotes: 3
Views: 153
Reputation: 80744
In your case, the terms "functional" and "F# idiomatic" would consist of two things: immutable data and separation of data from code.
Immutable data: you would have one data structure representing the filter parameters (i.e. A
, H
, Q
, and R
), and another structure representing the filter's current state (i.e. X
, K
, and P
). Both immutable. Instead of mutating the state, you would produce a new one.
Separation of data from code: the filter itself would consist of a single function that takes parameters, current state, next observation value, and produces next state. This next state will then be fed back into the function along with the next observation value, thus producing next+1 state, and so on. The parameters always stay constant, so they can be passed in just once, using partial application (see below).
Once you have such function, you can "apply" it to the list of observations as a "rolling projection", - as described above, - taking each observation and feeding it into the function along with the last state, producing the next state. This "rolling projection" operation is a very common thing in functional programming, and is usually called scan
. F# does provide implementations of scan
for all standard collections - list
, seq
, etc.
As a result of scan
, you will have a list of filter's successive states. Now all that's left to do is to fish the X
value out of each state.
Here is the complete solution:
module ScalarKalman =
type Parameters = { A : float; H : float; Q : float; R : float }
type State = { K: float; X: float; P: float }
let initState (s: State) = s
let getX s = s.X
let update parms state newVal =
let xp = parms.A * state.X
let Pp = parms.A * state.P * parms.A + parms.Q
let newK = Pp * parms.H / (parms.H * Pp * parms.H + parms.R)
{ K = newK
X = xp + newK * (newVal - parms.H * xp)
P = Pp - newK * parms.H * Pp }
let n = 100
let obsv = [for i in 0 .. n -> Normal.Sample(10., 5.)]
let kal = ScalarKalman.update { A = 1.; H = 1.; Q = 0.; R = 5. }
let initialState = ScalarKalman.initState { X = 6.; P = 4.; K = 0. }
let smv =
obsv
|> List.scan kal initialState
|> List.map ScalarKalman.getX
A note on design
Note the initState
function declared in the module. This function may seem silly on the surface, but it has important meaning: it lets me specify state fields by name without open
ing the module, thus avoiding namespace pollution. Plus, the consuming code now looks more readable: it says what it does, no comments required.
Another common approach to this is to declare a "base" state in the module, which consuming code could then amend via the with
syntax:
module ScalarKalman =
...
let zeroState = { K = 0.; X = 0.; P = 0. }
...
let initialState = { ScalarKalman.zeroState with X = 6.; P = 4. }
A note on collections
F# lists are fine on small amounts of data and small processing pipelines, but become expensive as these two dimensions grow. If you're working with a lot of streaming data, and/or if you're applying multiple filters in succession, you might be better off using lazy sequences - seq
. To do so, simply replace List.scan
and List.map
with Seq.scan
and Seq.map
respectively. If you do, you will get a lazy sequence as the ultimate result, which you will then need to somehow consume - either convert it to a list, print it out, send it to the next component, or whatever your larger context implies.
Upvotes: 8