Marcos Vainer Loeff

Machine Learning and PI System: Part 1 - Introduction to F# using PI AF SDK

Blog Post created by Marcos Vainer Loeff Employee on Dec 11, 2015

Introduction

 

The purpose of the following blog posts is to show new ways of using the PI System data in Machine Learning. The majority of the PI Dev Club developers are used to writing application in C#. Although I love C#, F# is a more suitable choice for solving machine learning problems since it is simple and productive.

 

What is F#?

 

According to this MSDN web page:

 

F# is a programming language that provides support for functional programming in addition to traditional object-oriented and imperative (procedural) programming. The Visual F# product provides support for developing F# applications and extending other .NET Framework applications by using F# code. F# is a first-class member of the .NET Framework languages and retains a strong resemblance to the ML family of functional language..

 

One of the great benefits of using F# compared to C# is that it reduces the time required to run the application. In C#, whenever you need to edit the code, you must recompile your project, load OSIsoft.AFSDK.dll library, connect to the PI System, get the data, store values on memory and then process it. This operation can take a long time which means part of your time is waiting for data to load. As it will be demonstrated on the next section,

 

F# Interactively

 

Visual Studio comes with F# Interactive (FSI) which is great for testing your model and functions that process PI data. FSI window accepts user input and two semicolons (;;) should be used to let the compiler know that the statement is finished. Figure 1 shows FSI dock on Visual Studio.

 

 

 

Figure 1 - F# Interactive in Visual Studio

 

The problem of writing commands on FSI is that IntelliSense won’t be available. In order to use this feature, we will create an F# Script.

 

The PI AF SDK code snippet that we will migrate to F# is below:

 

        static void Main(string[] args)
        {
            PISystem piSystem = new PISystems()["MARC-PI2014"];
            AFDatabase db = piSystem.Databases["AFSDK Test"];
            AFElement element = db.Elements["Cdt158"];
            AFAttribute attribute = element.Attributes["Sinusoid"];
            AFValues values = attribute.Data.RecordedValues(new OSIsoft.AF.Time.AFTimeRange("*-1d", "*"), OSIsoft.AF.Data.AFBoundaryType.Inside, null, string.Empty, false);
            foreach (AFValue value in values)
            {
                Console.WriteLine(string.Format("{0} - {1}", value.ValueAsDouble(), value.Timestamp.LocalTime));
            }
        }

 

Open Visual Studio and create a new F# Console Application.

 

 

Figure 2 - Creating a new F# Console Application solution

 

 

Visual Studio will create a project with the Program.fs. Add the PI AF SDK reference as you are used to doing in C#. As we want to use our data to make a lot of tests, let's create an F# script file named test.fsx with the following content.

 

 

#r "OSIsoft.AFSDK"

let piSystems = new OSIsoft.AF.PISystems()
let piSystem = piSystems.Item("marc-pi2014")
let db = piSystem.Databases.Item("AFSDK Test")
let element = db.Elements.Item("Cdt158")
let attribute = element.Attributes.Item("Sinusoid")
let values = attribute.Data.RecordedValues(new OSIsoft.AF.Time.AFTimeRange("*-1d", "*"), OSIsoft.AF.Data.AFBoundaryType.Inside, null, "", false)

 

Please take a look at the F# code version. Although it may not be clear at first, F# is a strongly typed language as C# even though F# does not need an explicitly defined typed. There reason is F# is capable of inferring a type from the assigned value. If you are familiar with var from C#, let from F# is quite similar.

 

Although OSIsoft. AFSDK.dll was already referenced on the project,  #r needs to be used as well to reference the assembly when running the F# Script on FSI.

 

After selecting all the code snippet from the F# script file and right-clicking on it, select the "Execute In Interactive" option as shown on Figure 2.

 

fix.png

Figure 3 - Executing F# Script in interactive

 

This will run the code as a script like Windows PowerShell. All the values retrieved from the PI System are stored on this variable. Under F# Interactive, typing values.Count();; will display the number of values that the variable values has.

 

Figure 4 - Viewing the results on F# Interactive

 

 

Using For

 

Let's take this opportunity to show how to use the equivalent of for and foreach from C# in F#.

 

 

for i=0 to values.Count-1 do
    System.Console.WriteLine(values.[i].ValueAsDouble())


for value in values do
    System.Console.WriteLine(value.ValueAsDouble())

 

 

Seq

 

A sequence is F# is a logical series of elements of one type. Any type of variable that implements the IEnumerable interface can be used as a sequence. F# also has Lists and Arrays but in this blog post, we will work as Sequences only.

 

This is another way to find how many values there are within values.

 

values |> Seq.length

 

The exists function can check if there is at least one element of the sequence that satifies a certain condition. For instance, is there any value higher than 200 or 100?

 

values |> Seq.exists (fun n->n.ValueAsDouble()>200.0)
values |> Seq.exists (fun n->n.ValueAsDouble()>100.0)

 

The forall function can check if all elements of the sequence satisfies a certain condition. For instance, are all values greater than 3 or 0?

 

values |> Seq.forall (fun n->n.ValueAsDouble()>3.0)
values |> Seq.forall (fun n->n.ValueAsDouble()>0.0)

 

Filter all AFValue in values, whose numeric value is greater than 50. After that show the number of AFValue objects which fit this condition.

 

let filteredValues = values |> Seq.filter (fun n-> n.ValueAsDouble() > 50.0)
filteredValues  |> Seq.length

 

 

The map function is used to generate a new collection with the Sequence collection as an input. For each element of the Sequence AFValues collection,

 

let mappedValues = values |> Seq.map (fun n-> n.ValueAsDouble())
mappedValues  |> Seq.length

 

You can also find the max, min, sum and average using sequence functions as:

 

mappedValues |> Seq.max
mappedValues |> Seq.average
mappedValues |> Seq.min
mappedValues |> Seq.sum

 

The great advantage of using F# is that you don't need to compile every time you want to test new functions. When you are still developing your model with PI data, using F# scripts is very productive choice!

 

Functions

 

Finally, let's define functions f and g. The first one increments a given float number by 5.0 and the second function doubles a float value. It becomes more interesting to composite f an g to get a new function fg. If x=3.0, f(x)= 8, g(f(x)) = 16.

 

 

let f(x:float) = x+5.0
let g(x:float) = 2.0*x
let fg = f >> g


mappedValues |> Seq.map(fg)

 

Using the map function from the Seq, each element of the sequence can be converted using the fg function generating a new collection.

 

Conclusions

 

F# is a programming language for machine learning. As it is also a .NET Framework language, it is compatible with PI AF SDK which is currently the best option to get PI Data into this environment. Once data is on memory, F# makes your life easier when testing different model using PI data as input.

 

Stay tunned for the upcoming blog posts about machine learning and the PISystem.

Outcomes