Computer Science Experimentation

Sunday, January 30, 2011

Linear Programming in F#

Linear Programming in F#


Celso Axelrud
Jan/30/2011



Introduction

This document presents a Linear Programming function developed in F# and executed in the interactive environment.
I am targeting small LP programs on very fast computers.
I selected the simplest and shortest code from all references. It means that has no linear algebra operation and not subroutine calls.

The F# code presented here is based on the subroutine Linpro.
Fortran version at:
http://www.sie.arizona.edu/faculty/addenda/yak/SIE270/for/linpro
Pascal version at:
http://www.sie.arizona.edu/faculty/addenda/yak/SIE270/pas/linpro.pas

There are other references to a subroutine Linpro but it is not the same code.
Fortran version at:
http://www.slac.stanford.edu/accel/ilc/codes/lcopt/source/linpro.f
Python version at:
http://adorio-research.org/wordpress/?p=194

Source Code and Results
(*
 Linpro - this function computes the solution of a linear programming     
           problem by using the Simplex method.  The Objective function    
           is maximized.  Parameters a,b, and c are presumed transmitted   
           as  global variables  declared in the calling program           
  Usage:                                                                   
           Linpro n m1 m2 m3                                

  Parmeters:                                                               
           a = matrix of coefficients (global)                             
           b = right hand side vector (global)                             
           c = coefficients of the objective function(global)              
           Input:                                                          
                 n = number of variables                                   
                 m1 = number of constraints of the Le type                 
                 m2 = number of constraints of the Eq type                 
                 m3 = number of constraints of the Ge type                 
           Output:                                                         
                 x.[i] = i-th component of the optimal solution             
                        for i = 1, ... ,N                                  
                 x.[n+1] = optimal value of the objective function          
                 Kod = key showing the category of the Lp problem          
                       if Kod = 1 then an optimal exists                   
                       if Kod = 2 then no feasible solution exists         
                       if Kod = 3 then the objective function is not       
                                bounded                                    
  Remarks:                                                                 
           matrix a needs to be re-assign before calling the function again    
  Conditions:                                                              
           M1+M2+M3 must be less then or equal to 50                       
           N must not be larger then 50 unless the subroutine is           
           redimenstion                                                    
  Example:
    Maximize Z = f(x,y) = 3x + 2y 
    subject to: 2x + y ≤ 18 
        2x + 3y ≤ 42 
        3x + y ≤ 24 
        x ≥ 0 , y ≥ 0 
  result:
    (x,y) = (3,12)
    Z=33
*)


let a:float[,]= Array2D.zeroCreate 52 151
let b:float[]= Array.zeroCreate 50
let c:float[]= Array.zeroCreate 50



//Linpro
let Linpro n m1 m2 m3  =
    let x:float array = Array.zeroCreate 50
    let mutable i,i0,i1,i2=0,0,0,0
    let mutable j,j0,j1,j2,L=0,0,0,0,0
    let mutable L1,L2,L3,Kod=0,0,0,0
    let mutable m4,m5,mm,n1,n5,nm,nn=0,0,0,0,0,0,0
    let mutable key=0
    let mutable ifirst=0 
    let mutable u=0.0
    let mutable Key=0
    let eps=1.0e-3
    let kk:int array = Array.zeroCreate 150
    //Load up the initial tableau with slack and artificial
    //variables, original and secondary objective functions
    L1<- n + 1
    L2<- m1+m2+m3+1
    n1<- n + 1
    //set the size of the appended matrix
    mm<- m1+m2+m3+2
    nn<- n+m1+m2+(2*m3)+1
    //initialize both objectives to zero 
    for i= L2 to mm do
        for j= 1 to nn do
            a.[i,j]<- 0.0
    L3<- m1+m2+m3
    // initialize all slack and artificial coefficients to zero
    for i= 1 to L3 do
        for j= L1 to nn do
              a.[i,j]<- 0.0
    L<-n
    if m1 > 0 then
        // Fix slack coefficients for Le constraints
        for i= 1 to m1 do
            a.[i,L+i]<- 1.0
        L<- L + m1
    if m3 > 0 then 
        // Fix slack coefficients for Ge constraints
        for i= 1 to m3 do 
            i1<-m1+m2+i
            a.[i1,L+i]<- -1.0
        L<- L + m3
    m4<- m2 + m3
    if (m4 > 0) then
       // Fix artificial coefficient for for Eq and Ge constraints 
        for i= 1 to m4 do
            a.[m1+i,L+i]<- 1.0
    m5<- mm - 1
    // Place the objective function 
    for j= 1 to n do
        a.[m5,j]<- c.[j]
    i2<- m1+m2+m3;
    if (m4 > 0) then
        i1<- m1 + 1
        //construct the secondary objective function 
        for j= 1 to n do
            for i= i1 to i2 do
                a.[mm,j]<- a.[mm,j] + a.[i,j]
            j1<- n+m1+1
            j2<- n+m1+m3
            if (m3 > 0) then
                for j= j1 to j2 do
                    a.[mm,j]<- -1.0
            for i= i1 to i2 do
                a.[mm,nn]<- a.[mm,nn] + b.[i]
    for i= 1 to i2 do
        a.[i,nn]<- b.[i]
    a.[m5,nn]<- 0.0
    // Load up the initial basic solution 
    if (m1 > 0) then
        for i= 1 to m1 do
            kk.[i]<- i + n
    if (m4 > 0) then 
        i1<- m1 + 1
        for i= i1 to i2 do
            kk.[i]<- n+m1+m3+i-i1+1
    else
        //If no constraint of Eq and Gt type, then no secondary
        //  objective function is needed 
        mm<- mm - 1
    ifirst<- 0
    u<- 0.0
    while (ifirst = 0) || (u > eps) do 
        ifirst<- 1
        n5<- nn - 1;
        // Check for the pivot element find the largest
        // coefficient of the objective 
        u<-a.[mm,1]
        j0<- 1
        for j= 1 to n5 do
            if (a.[mm,j] > u) then
                u<- a.[mm,j]
                j0<- j
        if (u > eps) then 
            Key<- 0
            i0<- 0
            while (Key = 0) && (i0 <= i2) do
                i0<- i0 + 1
                if (a.[i0,j0] >= eps) then Key<- 1
            // Test if there is a positive a[i,j] in this column
            // if not then the objective function is bounded
            if (Key = 0) then
                Kod<- 3
                //goto 10;
            else
                if (i0 < i2) then
                    i1<- i0 + 1
                    u<- a.[i0,nn]/a.[i0,j0]
                    for i= i1 to i2 do
                        if (a.[i,j0] >= eps) then
                            if ((a.[i,nn]/a.[i,j0]) < u) then
                                i0<- i
                                u<- a.[i,nn]/a.[i,j0]
                //Perform the elimination step 
                u<- a.[i0,j0]
                for j= 1 to nn do
                    a.[i0,j]<- a.[i0,j]/u
                for i= 1 to mm do
                    if (i <> i0) then
                        for j= 1 to nn do
                            if (j <> j0) then
                                a.[i,j]<- a.[i,j]-a.[i0,j]*a.[i,j0]
                for i= 1 to mm do
                    if (i <> i0) then
                        a.[i,j0]<- 0.0;
                //Register the newest basis vector and go back to
                //perform the next step of elimination 
                kk.[i0]<- j0
        else if (m4 > 0) then
                L<- n+m1+m3;
                // Check for feasible solution from the secondary
                //objective 
                for i= 1 to i2 do
                    if (kk.[i] > L) then
                        Kod<- 2
                        //goto 10;
                if Kod<>2 then     
                    mm<- mm - 1
                    // Remove artificail variables and the secondary
                    //objective function
                    m4<- 0
                    nm<- nn-m2-m3
                    for i= 1 to mm do
                        a.[i,nm]<- a.[i,nn]
                    nn<- nm
                    ifirst<- 0
                //From here we go back to continue elimination 
    if Kod<>2 || Kod<>3 then Kod<- 1
        // Set up the optimal solution 
    for i= 1 to n do
        x.[i]<- 0.0
    for i= 1 to i2 do
        j<- kk.[i];
        x.[j]<- a.[i,nn]
    x.[n+1]<- -a.[mm,nn]
    x,Kod
    
(*
//Example 1
let n=2
let m1=3
let m2,m3=0,0

c.[1..2]<-[|3.0;2.0|]
b.[1..3]<-[|18.0;42.0;24.0|]

a.[1..3,1..2]<-array2D [[2.0;1.0];[2.0;3.0];[3.0;1.0]]

let r=Linpro n m1 m2 m3

printfn "LP category: %i" (snd r)
printfn "LP Objective Function: %A" ((fst r).[3])
printfn "LP Varaibles: %A" ((fst r).[1..2])

(*
> 
LP category: 0
LP Objective Function: 33.0
LP Varaibles: [|3.0; 12.0|]
*)
*)

//Example 2
(*
R = –2x + 5y, subject to:  
x <= 200
y <= 170
x >= 100
y >= 80  
y + x >= 200 
Solution: x=100,y=170
*)
let n=2
let m1=2
let m2=0
let m3=3

c.[1..2]<-[|-2.0;5.0|]
b.[1..3]<-[|200.0;170.0;100.0;80.0;200.0|]

a.Initialize()
a.[1..5,1..2]<-array2D [[1.0;0.0];[0.0;1.0];[1.0;0.0];[0.0;1.0];[1.0;1.0]]

let r=Linpro n m1 m2 m3

printfn "LP category: %i" (snd r)
printfn "LP Objective Function: %A" ((fst r).[3])
printfn "LP Varaibles: %A" ((fst r).[1..2])


Sunday, January 9, 2011

OPC.NET client in F#


Introduction



This document presents an example of OPC client in F# interactive using OPC foundation OPC .NET API 1.
The website http://www.opcconnect.com/dotnet.php describes several options on how to use OPC with .NET. Here is part of the text:
"Using .NET for client development
Microsoft doesn't expect us to throw out all our COM code just yet. In fact there are well defined mechanisms for bridging from the .NET virtual machine (the Common Language Runtime) to the old world of COM servers.
An early option, using the OPC Automation interfaces, was to add a reference to the Automation server or DLL directly to a .NET project. Coding with C# or another .NET language then became reasonably straightforward. However, see this discussion (via Google Groups) about problems with the OPC Automation wrapper.
OPC Foundation .NET API and Runtime Callable Wrappers
OPC Foundation supplies a set of Runtime Callable Wrappers (RCWs), allowing OPC custom interfaces to be accessed from .NET clients.
These .NET wrappers are available to all as part of the OPC Core Components set. RCWs are provided for all published specifications, but still leave much COM interop work to be done by the developer.
A better option for Foundation members is to download the OPC .NET API, which supports DA 2 and 3, DX and HDA. The .NET API provides a unified set of interfaces for accessing both COM and SOAP/XML servers, and also includes C# and VB.NET clients which exploit these interfaces.
See this thread from the OPC Foundation Message Board for a comparison of the Runtime Callable Wrappers and the .NET API. This article from Advosol, outlining the requirements of a .NET API, may also be useful.
The Message Board also has this interesting thread on the background to the OPC .NET API. Note that the .NET API does not have the status of a full OPC specification - it is simply an implementation provided as a convenience to OPC Foundation members.
The .NET API requires the .NET Framework version 2.0.
For the low-down on the techniques involved in interfacing COM with .NET, take a look at Adam Nathan's book .NET and COM: The Complete Interoperability Guide."

Example    



This example tests most of the functionalities described in "OPC .NET API Overview Version 1.00 Draft 2 December 1, 2003" from OPC Foundation http://www.opcfoundation.org/ .
#r
"C:\cs\Others\NET API 2.00\Source\Bin\OpcNetApi.dll"

#r
"C:\cs\Others\NET API 2.00\Source\Bin\OpcNetApi.Com.dll"



open Opc
open OpcCom




//5.2 Opc.IDiscovery
//5.2.1 EnumerateHosts Method
let se= new OpcCom.ServerEnumerator()


//5.2.2 GetAvailableServers Method
// Show Servers--------------------------------------------------
let servers=se.GetAvailableServers(Opc.Specification.COM_DA_30)
for i in servers do
printfn "(%s) " i.Name


> (Advosol.DA3CBCS)
(ICONICS.SimulatorOPCDA)


//5.1 Opc.Factory
let mFactory = new OpcCom.Factory()
let mURL = new Opc.URL("opcda://localhost/ICONICS.SimulatorOPCDA");;
> val mFactory : Factory
val mURL : URL = opcda://localhost/ICONICS.SimulatorOPCDA


//5.3 Opc.Server
//5.3.1 Constructor
//5.3.2 Properties
//5.3.3 Duplicate Method
//5.3.4 Connect Method
//5.3.5 Disconnect Method


//5.4 Opc.Da.Server
//5.4.1 Constructor
let mserver = new Opc.Da.Server(mFactory, mURL)


//5.4.2 Properties
mserver;;
> val it : Da.Server =
Opc.Da.Server {Filters = 9;
IsConnected = false;
Locale = null;
Name = "ICONICS.SimulatorOPCDA";
Subscriptions = seq [];
SupportedLocales = null;
Url = opcda://localhost/ICONICS.SimulatorOPCDA;}


//5.4.3 Connect Method
let mCredentials = new System.Net.NetworkCredential()
let mConnectData = new Opc.ConnectData(mCredentials);
mserver.Connect(mURL, mConnectData)


// 4.1 Opc.IServer Interface
//4.1.3 GetSupportedLocale Method

//let l=mserver.GetSupportedLocales

//4.1.2 SetLocale Method

//mserver.SetLocale("en-US")

//4.1.1 GetLocale Method

//mserver.GetLocale()


//mserver.GetErrorText

//4.7 Opc.Da.IServer Interface
//4.7.2 SetResultsFilters Method
mserver.SetResultFilters(0x09) //Minimal
//4.7.1 GetResultsFilters Method
mserver.GetResultFilters()


//4.7.3 GetStatus Method
let Sstat=mserver.GetStatus()
Sstat;;
> val it : Da.ServerStatus =
Opc.Da.ServerStatus
{CurrentTime = 1/9/2011 9:57:36 PM;
LastUpdateTime = 1/1/0001 12:00:00 AM;
ProductVersion = "3.12.0";
ServerState = running;
StartTime = 1/9/2011 3:15:22 PM;
StatusInfo = "The server is running normally.";
VendorInfo = "ICONICS Simulator OPC-DA Server and Simulator OPC-AE Server";}


//4.7.5 Read Method

//4.3 Opc>Da.Item Class

let someItems : Opc.Da.Item array = Array.zeroCreate 2
someItems.[0] <- new Opc.Da.Item()
someItems.[0].ItemName <- "TAG_0000";
someItems.[0].ClientHandle <- 0
someItems.[1] <- new Opc.Da.Item()
someItems.[1].ItemName <- "TAG_0001";
someItems.[1].ClientHandle <- 1


mserver.Read(someItems)
mserver.Read([|someItems.[0]|]);;
> val it : Da.ItemValueResult [] =
[|Opc.Da.ItemValueResult {ClientHandle = null;
DiagnosticInfo = null;
ItemName = "TAG_0000";
ItemPath = null;
Key = "TAG_0000
null";
Quality = good;
QualitySpecified = true;
ResultID = S_OK;
ServerHandle = null;
Timestamp = 1/9/2011 5:00:22 PM;
TimestampSpecified = true;
Value = 20.0;}|]


//4.7.5 Write Method
let v1=new Opc.Da.ItemValue(ItemName="TAG_0000",Value=10.0)
let v2=new Opc.Da.ItemValue(ItemName="TAG_0001",Value=11.0)


mserver.Write([|v1;v2|])
mserver.Read([|someItems.[0];someItems.[1]|])


//4.7.6 CreateSubscription (Group)
let g1State = new Opc.Da.SubscriptionState()
g1State.Name<-"Group1"
g1State.ClientHandle<-1
g1State;;
> val it : Da.SubscriptionState = Opc.Da.SubscriptionState {Active = true;
ClientHandle = 1;
Deadband = 0.0f;
KeepAlive = 0;
Locale = null;
Name = "Group1";
ServerHandle = null;
UpdateRate = 0;}


let g1 = mserver.CreateSubscription(g1State)
printfn "Number of groups:%i" mserver.Subscriptions.Count
seq { for i in mserver.Subscriptions -> i.Name }|>Seq.iter (printfn "%s")
> Number of groups:1
Group1


//4.7.8 Browse Method

//4.5 Opc.Da.BrowseElement Class

//4.7.9 Browse Next Method


//4.7.10 GetProperties Method

//4.2 Opc.ItemIdentifier Class

let i0=Opc.ItemIdentifier("TAG_0000")
let i1=Opc.ItemIdentifier("TAG_0001")


let r=mserver.GetProperties([|i0;i1|],null,true)
printfn "DataType:%A" r.[0].[0].Value
printfn "Value: %A" r.[0].[1].Value
printfn "Quality: %A" r.[0].[2].Value
printfn "Timestamp: %A" r.[0].[3].Value;;
> DataType:System.Double
Value: 10.0
Quality: good
Timestamp: 1/9/2011 4:01:33 PM


//4.8 Opc.Da.ISubscription
//4.8.1 DataChanged Event
//4.8.2 GetResultsFilter Method
//4.8.3 SetResultsFilter Method


//4.8.4 GetState Method
let r2=g1.GetState()
r2;;
> val it : Da.SubscriptionState = Opc.Da.SubscriptionState {Active = true;
ClientHandle = 1;
Deadband = 0.0f;
KeepAlive = 0;
Locale = "";
Name = "Group1";
ServerHandle = 17;
UpdateRate = 50;}


//4.8.5 ModifyState Method


//4.8.6 AddItems Method
let r3=g1.AddItems(someItems)
let g1SH=Seq.toList(seq { for i in r3 -> i.ServerHandle })
seq { for i in r3 -> i.ResultID }|>Seq.iter (printfn "%A");;
> S_OK S_OK


//4.8.7 ModifyItems Method
//4.8.8 RemoveItems Method


//4.8.9 Read Method
let someItems_1 : Opc.Da.Item array = Array.zeroCreate 2
someItems_1.[0] <- new Opc.Da.Item()
someItems_1.[0].ServerHandle <- g1SH.[0]
someItems_1.[1] <- new Opc.Da.Item()
someItems_1.[1].ServerHandle <- g1SH.[1]


let r4=g1.Read(someItems_1)

//Opc.Da.ItemProperty

printfn "Value: %A" r4.[0].Value
printfn "Quality: %A" r4.[0].Quality
printfn "Timestamp: %A" r4.[0].Timestamp;;
> Value: 10.0
Quality: good
Timestamp: 1/9/2011 10:01:33 PM


printfn "Value: %A" r4.[1].Value
printfn "Quality: %A" r4.[1].Quality
printfn "Timestamp: %A" r4.[1].Timestamp;;
> Value: 11.0
Quality: good
Timestamp: 1/9/2011 10:01:33 PM


//4.8.10 Write Method

//4.4 Opc.Da.ItemValue Class

let someValues_1 : Opc.Da.ItemValue array = Array.zeroCreate 2


someValues_1.[0]<-new Opc.Da.ItemValue(ItemName="TAG_0000",Value=20.0)
someValues_1.[0].ServerHandle <- g1SH.[0]
someValues_1.[1]<-new Opc.Da.ItemValue(ItemName="TAG_0001",Value=21.0)
someValues_1.[1].ServerHandle <- g1SH.[1]


let r5=g1.Write(someValues_1)
seq { for i in r5 -> i.ResultID }|>Seq.iter (printfn "%A");;
> S_OK S_OK


//4.8.11 BeginReadMethod
//4.8.12 BeginWriteMethod
//4.8.13 CancelMethod
//4.8.14 Refresh Method
//4.8.15 SetEnable Method
//4.8.16 GetEnable Method


//5 Client API
//5.1.2 System Type Property
//5.1.3 UseRemoting Property
//5.1.4 CreateInstance Method
//5.4.5 CreateSubscription Method


//5.5 Opc.Da.Subscription
//5.5.1 Constructor
//5.5.2 Properties




//4.7.7 CancelSubscription
mserver.CancelSubscription(g1)


//5.4.3 Disconnect Method
mserver.Disconnect();;