Using LINDO API Optimization Software from APLX

About LINDO API

LINDO API is a software library available from Lindo Systems. It is designed to solve a wide range of optimization problems, including linear programs, mixed integer programs, quadratic programs, and general nonlinear non-convex programs.

LINDO API can be called via a .NET wrapper supplied by Lindo, and so can be used from .NET-capable APLs like APLX and Dyalog. The following article discusses calling LINDO API from APLX.

(Readers might like to note that other calling methods are available which don't use .NET. For example, the package also includes a Java interface and so could be used by APLX running on OS X or Linux).

Compiling the wrapper code

The main LINDO API code is shipped as a set of Windows DLLs in binary form, but the .NET wrapper is provided as C# source code. The first step is to compile it to produce a .NET class library.

Compiling the wrapper - Method #1

If you have a copy of Microsoft's Visual Studio, or the free Visual Studio Express, you can use it to compile the LINDO API C# wrapper.

To do this, create a new project and select the "Class Library" template. Add the LINDO API wrapper file to the project. If you installed LINDO API into the default location, this can be found in C:\Lindoapi\include\lindo.cs. Then build the project in the normal way.

In order to use the new class library you need to add it to the APLX .NET search path. This is normally done once, when APLX launches:

      s← '.net' ⎕SETUP 'using'
      s←s,⊂'Lindo,C:\Users\Simon\Documents\LindoAPL\LindoAPL\bin\Release\LindoLibrary.dll'
      '.net' ⎕SETUP (⊂'using'), s  

Finally, create an instance of the LINDO API class with the following:

      lindo←'.net' ⎕new 'lindo'

For more information on compiling C# class libraries for use with APLX, see here.

Compiling the wrapper - Method #2

If you don't have Visual Studio installed you can compile the code dynamically from with APLX. This incurs a slight overhead, but it's barely noticeable.

Here is an APL function to compile the code and return an instance of the 'lindo' class. It makes use of a function MakeNetArray which we'll define in a moment.

R←CompileLindo;CSharpCodeProvider;loParameters;code;codeArray;results

CSharpCodeProvider← '.net' ⎕new 'Microsoft.CSharp.CSharpCodeProvider'

⍝ Create parameters for compiler
loParameters← '.net' ⎕new 'System.CodeDom.Compiler.CompilerParameters'
⊣ loParameters.ReferencedAssemblies.Add 'System.dll'
⊣ loParameters.ReferencedAssemblies.Add 'System.Windows.Forms.dll'
loParameters.GenerateInMemory←0


⍝ Create code to compile
code←⎕IMPORT 'C:\Lindoapi\include\lindo.cs' 'txt'
 
⍝ Convert code to array containing single string
codeArray← 'System.String' MakeNetArray ,⊂code

⍝ Compile it
results←CSharpCodeProvider.CompileAssemblyFromSource loParameters codeArray

⍝ Any errors?
:If 0 ≠ results.Errors.Count 
:OrIf results.Errors.HasErrors
  R←⎕NULL
  :Return
:EndIf
 
⍝ Create an instance of the 'lindo' class
R←results.CompiledAssembly.CreateInstance 'lindo'  

See here for more information on dynamic compilation of C# code in APLX.

Utility Function

In the following code we need to construct several .NET arrays of various types. The following utility function can help us.

R←type MakeNetArray values;objType;arrayType
⍝ Create a .NET array of the specified type containing the specified values, e.g.
⍝ 'System.Double' MakeNetArray 1.0 2.0 3.0

⎕IO←0
values←,values

⍝ Create array of correct type and length
objType←'.net' ⎕GETCLASS type
arrayType←'.net' ⎕GETCLASS 'System.Array'
R←arrayType.CreateInstance.⎕REF objType (↑⍴values)

⍝ Fill in values
⊣ R.SetValue ¨⊂[1]values,[0.5]⍳⍴values

Using the LINDO API wrapper

Lindo Systems provides several examples of using the LINDO API .NET wrapper from code written in C#. Here we will take C:\Lindoapi\samples\dotnet\cs\ex_lp1\ex_lp1.cs and present a rough APL equivalent. Note that it doesn't provide a callback function, unlike the C# version. This is currently not possible in APLX, but should not be a problem here because LINDO API callbacks are mainly used for progress reporting.

Example1;⎕IO;nVars;nCons;lindo;LicenseKey;nErrorCode;string;pEnv;pModel;nDir;dObjConst;adC;adB;acConTypes;nNZ;anBegCol;pnLenCol;adA;anRowX;pdLower;pdUpper;varnames;connames;method;nSolStatus;adX;adR;adS;adY;namebuf;i
⍝ Example of calling Lindo API from APLX
⍝ Example based on C:\Lindoapi\samples\dotnet\cs\ex_lp1\ex_lp1.cs
⎕IO←0
⊣ '.net' ⎕SETUP 'byref' 0

⍝ Number of constraints
nCons←3

⍝ Number of variables
nVars←2
 
⍝ Create instance of Lindo class
⍝ The method of doing this depends on how you compile the C# wrapper
⍝    Method1: Wrapper compiled into class library by Visual Studio
⍝         lindo←'.net' ⎕new 'lindo'
⍝    Method2: Compile wrapper dynamically
⍝         lindo←CompileLindo
lindo←'.net' ⎕new 'lindo'

⍝ >>> Step 1 <<< Create a LINDO environment
LicenseKey←'.net' ⎕NEW 'StringBuilder' lindo.LS_MAX_ERROR_MESSAGE_LENGTH
nErrorCode←lindo.LSloadLicenseString 'C:\Lindoapi\license\lndapi60.lic' LicenseKey

string←LicenseKey.ToString
⊣ '.net' ⎕SETUP 'byref' 1
pEnv←lindo.LScreateEnv nErrorCode string
nErrorCode←↑ '.net' ⎕setup 'args'
⊣ '.net' ⎕SETUP 'byref' 0

⍝ >>> Step 2 <<< Create a model in the environment
⊣ '.net' ⎕SETUP 'byref' 1
pModel←lindo.LScreateModel pEnv nErrorCode
nErrorCode←↑ ¯1↑'.net' ⎕setup 'args'
⊣'.net' ⎕SETUP 'byref' 0

⍝ >>> Step 3 <<< Specify the model

⍝ The direction of optimization
nDir ← lindo.LS_MAX

⍝ The objective's constant term
dObjConst←0.0

⍝ The coefficients of the objective function
adC←'System.Double' MakeNetArray 20.0 30.0

⍝ The right-hand sides of the constraints
adB←'System.Double' MakeNetArray 120.0, 60.0, 50.0

⍝ The constraint types
acConTypes←'LLL'

⍝ The number of nonzeros in the constraint matrix
nNZ←4

⍝ The indices of the first nonzero in each column
anBegCol←'System.Int32' MakeNetArray 0 2 nNZ

⍝ The length of each column
pnLenCol←'System.Int32' MakeNetArray 2 2

⍝The nonzero coefficients
adA←'System.Double' MakeNetArray 1.0, 1.0, 2.0, 1.0

⍝ The row indices of the nonzero coefficients
anRowX←'System.Int32' MakeNetArray 0 1 0 2

⍝ Simple upper and lower bounds on the variables
pdLower←'System.Double' MakeNetArray 0 0
pdUpper←'System.Double' MakeNetArray lindo.LS_INFINITY,lindo.LS_INFINITY

varnames←'System.String' MakeNetArray 'Variable1' 'Variable2'
connames←'System.String' MakeNetArray 'Constraint1' 'Constraint2' 'Constraint3'

⍝ We have now assembled a full description of the model.
⍝ We pass this information to LSloadLPData with the
⍝ following call
nErrorCode←lindo.LSloadLPData  pModel  nCons  nVars  nDir dObjConst adC adB acConTypes nNZ anBegCol pnLenCol adA anRowX pdLower pdUpper

nErrorCode←lindo.LSloadNameData pModel 'MyTitle' 'MyObj' '' '' '' connames varnames ('System.String' MakeNetArray '')

⍝ >>> Step 4 <<< Perform the optimization
method←lindo.LS_METHOD_PSIMPLEX
⊣ '.net' ⎕SETUP 'byref' 1
nSolStatus←0
nErrorCode←lindo.LSoptimize pModel method nSolStatus
nSolStatus←↑¯1↑'.net' ⎕SETUP 'args'

⊣ '.net' ⎕SETUP 'byref' 0

⍝ Step 5 <<< Retrieve the solution
adX←'System.Double' MakeNetArray 0 0
adR←'System.Double' MakeNetArray 0 0
adS←'System.Double' MakeNetArray 0 0 0
adY←'System.Double' MakeNetArray 0 0 0

⍝ Get the variable values
nErrorCode←lindo.LSgetPrimalSolution pModel adX

⍝ Get the slack values
nErrorCode←lindo.LSgetSlacks pModel adS

⍝ Get the variable values
nErrorCode←lindo.LSgetDualSolution pModel adY

⍝ Get the slack values
nErrorCode←lindo.LSgetReducedCosts pModel adR

'Primal solution'
namebuf←'.net' ⎕new 'StringBuilder' lindo.LS_MAX_ERROR_MESSAGE_LENGTH

:For i :In ⍳nVars
  nErrorCode ← lindo.LSgetVariableNamej pModel i namebuf 
  namebuf.ToString, adX.⎕val[i], adR.⎕val[i]
:EndFor

''
'Dual solution'
:For i :In ⍳nCons
  nErrorCode ← lindo.LSgetConstraintNamei pModel i namebuf 
  namebuf.ToString, adY.⎕val[i], adS.⎕val[i]
:EndFor

⍝ >>> Step 6 <<< Delete the LINDO environment
⊣ '.net' ⎕SETUP 'byref' 1
nErrorCode ← lindo.LSdeleteModel pModel
nErrorCode ← lindo.LSdeleteEnv pEnv
⊣ '.net' ⎕SETUP 'byref' 0  

Output of the function

Here's the output of the function

      Example1
Primal solution
Variable1 60 0
Variable2 30 0

Dual solution
Constraint1 15 0
Constraint2 5 0
Constraint3 0 20

An explanation of ⎕SETUP 'byref'

Most of the code in the example above is a straight translation from Lindo's C# example into the APLX equivalent. If you compare the two, the correspondence should be clear.

However there's one step that may need explaining, which is the way APLX handles the C# keywords 'ref' and 'out'. These are used in C# to specify that a parameter is both an input and an output to a function (ref), or is only an output (out). For example, in the C# call:

    pModel = lindo.LScreateModel ( pEnv, ref nErrorCode);

...the explicit result is assigned to pModel, but an additional result is returned in nErrorCode.

APLX uses a pass-by-value calling convention, and functions can only have an explicit result. In order to call the LScreateModel routine, APLX needs to be told to use a different calling convention for .NET by using ⎕SETUP 'byref' 1

⊣ '.net' ⎕SETUP 'byref' 1
pModel←lindo.LScreateModel pEnv nErrorCode

After the call the function arguments are available by calling ⎕setup 'args'. Note that this returns a list of all the arguments, modified or not. We can pick off the nErrorCode from the end:

nErrorCode←↑ ¯1↑'.net' ⎕setup 'args'

Finally we need to restore the normal pass-by-value calling convention so future .NET calls work as expected:

⊣'.net' ⎕SETUP 'byref' 0

Sample Workspace

lindo.aws

A sample workspace containing the code above is attached to this Wiki page


Author: SimonMarsden


CategoryDotNet CategoryAplx CategoryTechnologies

lindooptimizationsoftware (last edited 2010-03-23 15:38:28 by SimonMarsden)