Friday, September 3, 2010

Fuzzy Logic F# Reference Module: Fuzzy0

I want to post some more fuzzy logic examples, but first I want to devote a single post to containing the fuzzy logic core code. I could have done that using CodePlex or something, but that seemed too grandiose for such a small block of code. So I’ll place the code here, and if I make changes to it, I’ll post those changes here with a link back from the later blog post where I indicate the changes.

I’m calling this module “Fuzzy0” in anticipation of doing experiments using other models of fuzzy logic. As I’ve indicated, the techniques used in this module represent only a tiny fraction of the vast family of techniques in the domain of fuzzy engineering.

The code below is a cleanup and slight refactoring of the earlier examples. Of particular note, I have stuck with the paired height function/output function model, but have made it even more general. This makes it possible to add things like conjunction, disjunction, different defuzzification techniques, etc., by extending the code rather than changing it.

I’d also like to give a shout out to Alec Zorab, who posted a comment which helped me clean up the earlier code considerably and make it more readable.

Below is the fuzzy logic reference code for module Fuzzy0. Tomorrow I’ll post an example of its use that explores some extensions of earlier techniques. As always, all the code here is presented "as-is" and without warranty or implied fitness of any kind; use at your own risk.

(Note: updated 2010.09.11)

module Fuzzy0
// The input functions are trapezoids.
// (One is a triangle: a degenerate trapezoid.)
// The precomputations and closures
// make them look more complicated than
// they really are.
/// Infinite to the left.
let inMin x0 x1 =
  let m = 1.0/(x0-x1)
  let b = 1.0-x0*m 
    | x when x<=x0 -> 1.0
    | x when x<=x1 -> x*m+b
    | _ -> 0.0)
/// In the middle.
/// Simplification of the original
/// is courtesy of Alex Zorab.
/// (Ditto similar functions elsewhere.)
let inMid x0 x1 x2 x3 =
  let ml = 1.0/(x1-x0)
  let mh = 1.0/(x2-x3)
  let bl = 1.0-x1*ml
  let bh = 1.0-x2*mh
    | x when x<x0 -> 0.0
    | x when x<x1 -> x*ml+bl
    | x when x<=x2 -> 1.0
    | x when x<=x3 -> x*mh+bh
    | _ -> 0.0)
/// Simplified definition for triangles.
let inTri xl xc xh =
  let ml = 1.0/(xc-xl)
  let mh = 1.0/(xc-xh)
  let bl = 1.0-xc*ml
  let bh = 1.0-xc*mh
    | x when x<xl -> 0.0
    | x when x<xc -> x*ml+bl
    | x when x<=xh -> x*mh+bh
    | _ -> 0.0)
/// Infinite to the right.
let inMax x0 x1 =
  let m = 1.0/(x1-x0)
  let b = 1.0-x1*m 
    | x when x>=x1 -> 1.0
    | x when x>=x0 -> x*m+b
    | _ -> 0.0)
/// A constant height (infinite line).
let inConst h = (fun _->h)
// Modifier narrows towards the peak.
let very (f:float->float) = 
  (fun x ->
    let y = f x
// Modifier widens out from the peak.
let somewhat (f:float->float) = 
  (fun x ->
    let y = f x
    sqrt y)
/// Works like: 
/// List.map2 (fun f x->f x) fl al |> min
/// but short-circuits on 0.0 for efficiency.
let conjoin =
  let rec f acc (fl:(float->float) list) (al:float list) =
    match fl with
    | [] -> acc
    | h::t ->
    match h al.Head with
    | 0.0 -> 0.0  // Short circuit.
    | n -> f (min n acc) t al.Tail 
  f 1.0
/// Output as a symmetric triangle.
/// This function returns the centroid and area.
let outSym xc dx h = 
/// Output as a trapzoid.
/// This function returns the centroid and area.
/// Can also be used to create 
/// asymmetric triangles.
let outTrap x0 x1 x2 x3 = 
  let x3x0 = x3+x0
  let dx1x0 = x1-x0
  let dx3x2 = x3-x2
  let dx3x0 = x3-x0
  (fun h ->
    let x0n = h*dx1x0+x0
    let x2n = h*dx3x2+x2
    let a = (dx3x0+x2n-x0n)*h/2.0
    // This is a quick approximation of
    // the centroid.  It can skew towards
    // the peak in some cases, which is OK.
    let c = (x3x0+x0n+x2n)/4.0
    c,a )
/// Output as a constant centroid 
/// and proportional area.
let outConst (c:float) (a:float) h = 
/// Some simple, common functions
/// to help clean up the syntax.
/// Defuzzify by weighted centroid.
/// Note: will return NaN if all sets have zero area.
/// This is by design, since different implementations
/// may mandate different behavior in this situation.
let inline weightedCentroid xl =
  List.fold (fun (cc,aa)(c,a)->(cc+a*c,aa+a)) (0.0,0.0) xl
  ||> (/)
/// Maps a scalar onto list of functions.
/// Useful for multi-output rules.
let inline mapScalar xl h = (fun f->f h) xl
/// Fire a rule 1 to 1.
let inline fire x (inSet,outSet) =
  inSet x |> outSet
/// Fire a ruleset,
/// but don't defuzzify the result.
/// Allows for custom defuzzification.
let inline fireAll sets x = (fire x) sets 
/// Fire and defuzzify a ruleset.
/// Note: will return NaN if all sets have zero area.
/// This is by design, since different implementations
/// may mandate different behavior in this situation.
let inline fireAllDef sets x = 
  fireAll sets x |> weightedCentroid 

No comments: