LocalSolver logo
is now
Hexaly logo

We're excited to share that we are moving forward. We're leaving behind the LocalSolver brand and transitioning to our new identity: Hexaly. This represents a leap forward in our mission to enable every organization to make better decisions faster when faced with operational and strategic challenges.

This page is for an old version of Hexaly Optimizer. We recommend that you update your version and read the documentation for the latest stable release.

Modeler

Object-oriented C#/.NET APIs are provided for the virtual machine powering the LSP language. With only a few classes, you can load modules, interact with their variables and execute them. If you are not familiar with the LSP language, please have a look at the language reference for more information.

Create a module

First, you have to create a LSPModeler environment. As with LocalSolver, we recommend using a “using” statement that will ensure the native memory allocated for the modeler is correctly released when the environment is discarded. LSPModeler is the main class of the Modeler library and allows you to create a module in one of two ways:

Both of these methods return an instance of LSPModule which will enable you to interact with the module variables and functions.

Launch a module

There are two launch modes for a module:

  • Optimization mode: This is the classic and default mode for the modeler. At the very least, you must implement a model method in your LSP module that will build an LSModel. You can then call LSPModule.Run to start the execution of the module. This function takes as first argument a LocalSolver instance that you have to create beforehand using the LSPModeler.CreateSolver method. After the LocalSolver model has been built, the resolution process will be started automatically.

    Note that the run method also accepts as a parameter a list of arguments that will be passed as global variables to your module:

    using (LSPModeler modeler = new LSPModeler())
    {
        LocalSolver solver = modeler.CreateSolver();
        LSPModule module = modeler.LoadModule("my_lsp_file.lsp");
        module.Run(solver, "lsIterationLimit=100", "lsTimeLimit=10");
    }
    
  • Main mode: In this mode, the modeler behaves like a classical programming language. To use this mode, you have to implement a function named main in which you are free to do anything you want without being limited by the formalism of the functions input, model, param, display and output. You can then call LSPModule.RunMain to start the execution of the module.

    Note that unlike the optimization mode, it is your responsibility to manually create the solver instances, close your models and launch the resolutions. In return, you are free to run several successive resolutions or none at all if you just want to use LSP for its pure programming features.

For more details on the differences between the optimization and the main mode, read Main mode.

Interacting with variables

You can interact with the variables inside a module thanks to getters and setters on the LSPModule class. Values can be obtained in their native C# type or retrieved as LSPValue which is a container that can hold any type of value inside a module. For more information on value types available in the modeler you can look at LSPType.

You can create maps from the modeler instance with LSPModeler.CreateMap. A map is a data structure holding (key, value) pairs that can also be used as an array. For more information on maps you can look at the map module.

Using external functions

You can use your own C# functions as LSP functions in the modeler thanks to the method LSPModeler.CreateFunction. The signature of the external function must conform to the delegate LSPFunctor:

LSPValue MyLspFunction(LSPModeler modeler, IList<LSPValue> arguments) {
    double result = arguments[0].AsDouble() + arguments[1].AsDouble();
    return modeler.CreateDouble(result);
}

void Main() {
    LSPFunction lspFunc = modeler.CreateFunction(MyLspFunction);
    List<LSPValue> arguments = new List<LSPValue>();
    arguments.Add(modeler.CreateDouble(1.5));
    arguments.Add(modeler.CreateDouble(2.3));
    LSPValue result = lspFunc.Call(arguments);
    Console.WriteLine("result = " + result.AsDouble()); // prints "result = 3.8"
}

In the snippet above we declare an LSP function that takes two LSP doubles as input, add them together and returns the result. We can then call the function with LSPFunction.Call to execute the function and retrieve the result.

You can also assign the function to a variable in a module with LSPModule.SetFunction. After doing so, the function will be callable within any function of the module.

Memory management

When you access a modeler object from the C# API, your C# program takes a new reference to that modeler object (LSPMap, LSPModule, LSPValue or LSPFunction are examples of references to modeler objects).

A modeler object can only be destroyed if no more C# reference points to it. In other words, a C# reference on a modeler object prevents the object from being freed even if it is no longer used in the modeler. If you have to create, access and destroy many objects in your LSP programs, you may keep in memory objects that have become inaccessible within your LSP programs. You have 3 ways to limit this phenomenon:

  1. Wait for the destruction of your LSPModeler object. All references are automatically freed when the modeler is released by using the LSPModeler.Dispose method on the LSPModeler instance. This is the simplest solution and the recommended solution if the interactions between the modeler and your LSP programs are limited to a few exchanges at the beginning/end of the resolution.

  2. Release specific references at any time by calling LSPMap.Dispose, LSPModule.Dispose, LSPFunction.Dispose, LSPValue.Dispose or localsolver.LocalSolver.Dispose.

  3. Release all references attached to a LSPReferenceScope. When you instantiate a new reference scope object by doing new LSPReferenceScope(modeler), all references created from that point anywhere in the API will be automatically associated with that scope. Finally, a call to LSPReferenceScope.Dispose will release all references registered in the scope and will restore the previous scope as the current one.

Note that invoking any method on a released reference will throw an exception. It is therefore important not to release your references too early and to keep them alive as long as you need them.

Also note that several references can point to the same object if you call methods like LSPMap.GetMap or LSPModule.GetFunction several times. At each call, a new reference is created. This is not a problem in itself, but try to minimize the number of calls to functions that return objects of type LSPMap, LSPModule, LSPValue or LSPFunction to reduce the number of references.