Initializing help system before first use

Introduction

Applications written in a .NET language such as C# or Visual Basic.NET, can use the Mosel library via the .NET wrapper library on Windows or Linux. The best way to get started using the wrapper is to open up the example projects included with the Xpress distribution (see the subdirectory "examples/mosel/Library") and experiment with them; the auto-completion feature in Visual Studio can be used to obtain a full list of class methods, properties and prototypes. Alternatively, full documentation for the .NET wrapper classes can be found in this document.

Referencing Mosel from a .NET project

The Mosel .NET wrapper library is distributed in a package called "XPRMdn" in the lib/nuget folder of the Xpress installation directory. This package targets .NET Framework 3.5 and .Net Standard 2.0. If using Visual Studio, you can add this folder as a NuGet package source by choosing "NuGet Package Manager" -> "Package Manager Settings" from the Tools menu, then select Package Sources. Click the ‘+’ icon to add a new source, name it “Xpress local packages” and select the folder lib\nuget within the Xpress installation directory. Click Update then OK to save the changes.

Then, to add the wrapper to a Visual Studio project, select Manage NuGet Packages from the Project menu. Select “Xpress local packages” from the “Package source” drop-down in the top right, then select “FICO.Xpress.XPRMdn” from the Browse tab, and click “Install” to add it to the project.

Or if using the .NET CLI, you can add the Mosel .NET wrapper library to your project as follows:

dotnet add package -s <XPRESS_INSTALL_DIRECTORY>/lib/nuget FICO.Xpress.XPRMdn

In all cases, the wrapper does not include the Xpress Mosel runtime and so a local installation of Xpress will be required to use the wrapper library.

Using Mosel from a .NET language (C#)

The below example code uses C#, but can easily be adapted to VB.NET and other .NET languages. Each source file that uses Mosel should import the "Mosel" namespace. and be part of a project with a reference to "XPRMdn", as described above.

In your code, you first need to call XPRM.Init() to get a reference to an XPRM object - you can use this object to access Mosel. Then you can call XPRM.LoadModel to load a .bim file, and use the methods and properties of the XPRMModel object it returns to execute and interrogate the model. For example:

XPRM mosel = XPRM.Init();
XPRMModel myModel = mosel.LoadModel("chess.bim");
myModel.Run();
Console.WriteLine(
  "Optimal solution is to build {0} small and {1} large sets",
  ((XPRMReference) myModel.Identifiers["num_small"]).Value.AsMPVar().Solution,
  ((XPRMReference) myModel.Identifiers["num_large"]).Value.AsMPVar().Solution 
);

You can call Dispose() on the XPRMModel and XPRM objects to immediately release the structures in Mosel that underlie them; if you don’t do this then the structures will be released when the objects are garbage-collected.

Most of the functions in the C interface to Mosel use an integer return code to indicate if an error occurred. In the .NET library, methods will instead throw an XPRMException when an unexpected event occurs. This makes error-handling easier, since a check is not required with every function call. Also, because the return code is no longer used to indicate error status, it is often used to supply data that would otherwise be specified using an output parameter.

Identifiers and Parameters

Many of the properties of a model and the Mosel environment are implemented as enumerable collections, so you can easily iterate over them. For example:

foreach (XPRMIdentifier ident in myModel.Identifiers) { 
  Console.WriteLine("Identifier: {0}", ident)
}

Most of the XPRM objects provide a ToString() implementation that summarises the object’s state - even sets and arrays:

Console.WriteLine("My Array: {0}", myModel.Identifiers["my_array"]);

You can access a list of a model’s parameters through XPRMModel.Parameters, but if you want to set one of them you should pass it to the model's SetExecParam method, e.g.:

myModel.SetExecParam("BASE_PROFIT",1.9);
Connecting .NET Streams with Mosel

Using the dotnet: I/O driver, you can link a Mosel output stream to any .NET object derived from System.IO.TextWriter or System.IO.Stream, and any Mosel input stream to a .NET object derived from System.IO.TextReader or System.IO.Stream. This allows you to connect your Mosel model to .NET resources in various powerful ways.

When using this driver, file names in the Mosel model are replaced by references to objects, using names defined via the XPRM.Bind or XPRMModel.Bind methods, which establishes a link between a name and an object. This name can be used as a reference in the dotnet: driver.

For example, if you want to divert the standard Mosel output streams to a string:

mosel = new XPRM();
StringWriter outAsString = new StringWriter();
mosel.Bind("out", outAsString);
mosel.SetDefaultStream(XPRMStreamType.F_OUTPUT_LINBUF, "dotnet:out");
mosel.SetDefaultStream(XPRMStreamType.F_ERROR, "dotnet:out");
Using .NET Initialization Callbacks from Mosel

You can also use the dotnet: I/O driver in an initializations block, passing a delegate of type either XPRMInitializationFrom or XPRMInitializationTo - this delegate will then be called once for each label within the initializations block. For example, when executing an initializations-from block, the XPRMInitializationFrom callback is passed the label and the expected type, and an initialization context context to which you send the basic type values to populate the Mosel object(s), in a similar fashion to an ASCII initialization file. For example,if you have in your Mosel code:

declarations
  I: integer
  S: set of real
  A: array(range) of string
end-declarations
initializations from "dotnet:initfromfunc"
  I S A
end-initializations

In your Mosel code, you would bind your XPRMInitializationFrom delegate to whatever name you used in your Mosel initializations-from block, e.g.:

model.Bind("initfromfunc", new XPRMInitializationFrom(initfrom));

And then implement the function to initialize the structures as follows:

public bool initfrom(XPRMInitializeContext ictx, string label, XPRMTyped type) {
  switch (label) {
    case "I":
      ictx.Send(10);
      return true; /* Return 'true' on success */
    case "S":
       ictx.Send(XPRMInitializeControl.OpenList);      /* Indicate start of a collection of values */
       for (int i=1;i<=3;i++)
         ictx.Send((double)i);  
       ictx.Send(XPRMInitializeControl.CloseList);     /* Indicate end of a collection of values */
       return true; /* Return 'true' on success */
    case "A":
      ictx.Send(XPRMInitializeControl.OpenList);       /* Indicate start of a collection of values */
      for (int idx=1;idx<=5;idx++) {
        ictx.Send(XPRMInitializeControl.OpenIndices);  /* Indicate start of indices */
        ictx.Send(idx);
        ictx.Send(XPRMInitializeControl.CloseIndices); /* Indicate end of indices */
        ictx.Send(string.Format("String number #{0}", idx));
      }
      ictx.Send(XPRMInitializeControl.CloseList);      /* Indicate end of a collection of values */
      return true; /* Return 'true' on success */
    default:
      return false; /* Return 'false' on error */
  }
}

When using an initializations-to block, you similarly bind your XPRMInitializationTo delegate to a name and pass it to the dotnet: I/O driver as above; for each label, the delegate will receive the label string and a corresponding XPRMValue object that can be interrogated using the normal Mosel API functions.

Using .NET Objects to initialize Mosel structures

The dotnetraw: I/O driver can only be used in 'initializations' blocks, and allows initialization of Mosel structures directly from/to .NET objects. You reference .NET objects to this driver by binding them to a name using the XPRM.Bind method, which establishes a link between a name and an object.

In the opening part of the initializations block, no filename is provided, but general options can be stated at this point: they will be applied to all labels. Two options are supported:

all

Forces output of all cells of an array even if it is dynamic (by default only existing cells are considered).

noindex

Indicates that only data (no indices) are transfered between the .NET objects and Mosel. By default, the first fields of each object are interpreted as index values for the array to be transfered. This behavior is changed by this option.

Within the initializations block, each label entry is understood as an object reference to use for the actual processing, which comprises a name previously established using XPRM.Bind, optionally followed by a sequence of period-separated field/property names, optionally followed by bracketed field specifiers. Note that, before the object reference, one can add further options separated by commas, that are effective to the particular entry.

Some rough guides for initializing certain Mosel types using the 'dotnetraw' driver are:

Scalars

Give the bound object and the name of the field from which to initialize, e.g.:

C#:

public class ScalerValues { int intval; }
...
XPRMModel model;
...
model.Bind("scalers", new ScalerValues());
model.Bind("strval", "hello world");

Mosel:

declarations
  i : integer
  s : string
end-declarations
...
initializations from "dotnetraw:"
  i as "scalers.intval"
  s as "strval"
end-initializations
Records

A record can be initialized from an object by following the bound object name with a comma-separated set of field/property names, e.g.:

C#:

public class Employee {
  string Name;
  Date DateOfBirth;
  ...
  public int Age {
    get { /* age calculation goes here */ }
  }
}
...
XPRMModel model;
...
Employee fred = new Employee();
...
model.Bind("fred", fred);

Mosel:

declarations
  myrecordtype = record
    name: string
    age : integer
  end-record
  fred_bloggs : myrecordtype
end-declarations
...
initializations from "dotnetraw:"
  fred_bloggs as "fred(Name,Age)"
end-initializations
Sets & Lists

Sets and Lists are initialized in a similar way to records; by giving the name of a bound object that is either an array or something that implements the IEnumerable interface (such as an ArrayList), followed by an optional field name in brackets (for the case, for example, where you want to use just one field of the object in your set).

C#:

public class Employee {
  string Name;
  Date DateOfBirth;
}
...
XPRMModel model;
Employee[] allEmployees;
double[] taxRates = new double[] {0.10, 0.20, 0.40};
...
model.Bind("employees", allEmployees);
model.Bind("taxRates", taxRates);

Mosel:

declarations
  employeeNames: set of string
  taxRates: set of real
end-declarations
...
initializations from "dotnetraw:"
  employeeNames as "employees(Name)"
  taxRates as "taxRates"
end-initializations
Arrays

If you specify the 'noindex' option, the driver expects the name of a bound 1-dimensional .NET array (or IEnumerable), optionally followed by a fieldname in brackets, and the Mosel array being initialized must be indexed by one set of integers.

Where the 'noindex' option is not used, the driver again expects the name of a 1-dimensional .NET array (or IEnumerable), and which must be followed in brackets by N+1 comma-separated fields or property names, where N is the number of dimensions of the .NET array; the first N fields are used for the values of the array indexes, while the final field is used for the array element itself.

For example:

C#:

public class Employee {
  string Name;
  string OfficeLocation;
  int RoomNumber;
}
...
XPRM mosel;
Employee[] allEmployees;
double[] taxRates = new double[] {0.10, 0.20, 0.40};
...
mosel.Bind("employees", allEmployees);
mosel.Bind("taxRates", taxRates);

Mosel:

declarations
  employees: array(offices:set of string,room_numbers:set of integer) of string
  taxRates: array(range) of integer
end-declarations
...
initializations from "dotnetraw:"
  employees as "employees(OfficeLocation,RoomNumber,Name)"
  taxRates as "noindex,taxRates"
end-initializations
Note Note

A few notes about using the dotnetraw: driver with "initializations to":

  • You cannot use "initializations to" to initialize the fields of a struct, only fields of an object or members of an array.
  • "initializations to" will not create new objects (other than strings); so any arrays you initialize-to must already be created to the required size, and each element of an array of objects must contain an instance of the required object.
  • You cannot use "initializations to" to populate a non-array collection class.

© 2001-2024 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.