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.

Programming style

In addition to mathematical operators, LocalSolver’s modeler provides some elements of an imperative programming language for building easily and quickly your models. This language, called LSP, has all characteristics of a scripting language but is dedicated to problem modeling and solving. Note that the LSP language is case-sensitive. This modeling language has its own manual available at LocalSolver’s Modeler (LSP). Only the main features are presented here.

Primitives types and assignments

The declaration of variables is done on the fly. Thus, the statement x = v assigns the value v to the variable x. The language is dynamically typed. Thus, the type is hold by the value, not the variable. Values can be written as the combination of the two literal types: integers (x = 12) and strings (x = "foo"). The boolean type is implicitly defined through integers 0 and 1 coding for false and true respectively. If the value v corresponds to a modeling expression, then the leftward arrow <- must be used instead of =.

In this way, the following statements are valid:

a = true;   // a = 1
b = 9;      // b = 9

c = a + b;  // c = 10
c = a * b;  // c = 9

c = a == b; // c = 0
c = a < b;  // c = 1

Maps

Maps are generic containers that can be used as arrays or dictionnaries. More precisely, a map is a data structure matching some values to some keys. Keys and values can be of any type except nil that has a special meaning. The value associated to a key is set or retrieved by using the “bracket” notation: a[9] = "abc", a["abc"] = 9. A map is implicitly declared when inserting its first element. Alternatively, an empty map can be defined using map() function or the braces {}, as follows:

a = map("z", 9); // a[0] = "z", a[1] = 9
a = {"z", 9};    // a[0] = "z", a[1] = 9

a["a"] = "abc";  // a[0] = "z", a[1] = 9, a["a"] = abc
a[-3] = "xyz";   // a[0] = "z", a[1] = 9, a["a"] = abc, a[-3] = "xyz"
a[0] = -2;       // a[0] = -2, a[1] = 9, a["a"] = abc, a[-3] = "xyz"
a[-3][1] = 0;   // a[0] = -2, a[1] = 9, a["a"] = abc, a[-3] = {1: 0}
print(a[42]);    // display nil

Note that a[k] returns nil if no value is assigned to key k in map a. Setting the value of an existing key (with the = operator) will overwrite the previous value associated to this one. It is possible to assign another map as a value of a key to create nested maps.

Conditions

if (C) S_true; else S_false; is a conditional statement: the statement S_true is executed if the condition C is true (that is, if C is equal to 1) while statement S_false is executed if C is false (that is, if C is equal to 0). Note that the else branch is optional. A block of statements can be declared instead of a sole statement by using the braces {}. The conditional ternary operator ? : can also be used as shortcut:

if (1 < 2) c = 3; else c = 4;
c = 1 < 2 ? 3 : 4;

if (0) c = "ok";
if (true) c = "ok";
if (2) c = "error"; // ERROR: invalid condition

c = 0 * 9; // c = 0
if (c) {
    a = "L";
    b = 0;
} else { // executed block
    a = "S";
    b = 1;
}

Loops

while (C) S; iteratively executes the statement S while the condition C is true (that is, is equal to 1). The syntax do S; while (C); first executes the statement S once regardless of the condition C and then repeatedly executes S while the condition is true.

for [v in V] S; is an iteration loop where the statement S is iteratively executed with v taking all values in V, where V can be a range, a map or any iterable type. A range is declared with the from...to syntax where to is excluded. When a map is given, the iteration is performed on the values of the map following the increasing order of the keys. The pairs (key, value) of the map can also be iterated by using the syntax for [k,v in M]:

for [i in 0...3] a[i] = i + 1; // a[0] = 1, a[1] = 2, a[2] = 3
s = 0; for [v in a] s = s + v; // s = 6
s = 0; for [k,v in a] s = s + k + v; // s = 9

for [v in V : C] is a filtered iteration loop: v takes only the values in V satisfying the condition C:

for[i in 1...201 : i % 21 == 0] {
    println(i + " is a multiple of 21");
}

The nested loops can be written in a compact way, as follows:

for[i in 0...10]
    for[j in i+1...10]
        for[k in j+2...10]
            a[i][j][k] = i + j + k;

for[i in 0...10][j in i+1...10][k in j+2...10] // compact
    a[i][j][k] = i + j + k;

For all kinds of loops, a block of statements can be declared instead of a sole statement by using the braces {}:

for[i in 0...10][j in i+1...10][k in j+2...10] {
    a[i][j][k] = i + j + k;
    b[i][j][k] = i * j * k;
}

Iterated assignment

A novelty provided by the LSP language is the “iterated assignment”, which will help you to make your models shorter and clearer. Indeed, the code for [v in V] a[v] = f(v); can be written in a very compact way: a[v in V] = f(v);. Similarly to the for statement, iterations can be nested and filtered:

for[i in 0...10][j in i+1...10][k in j+2...10]
    a[i][j][k] = i + j + k;

a[i in 0...10][j in i+1...10][k in j+2...10] = i + j + k; // compact alternative

The iterated assignment is also allowed for declaring modeling expressions: x[i in 0...N][j in 0...M] <- bool();.

Functions

Functions can be declared outside other functions using the function keyword. The parameters are given in parenthesis, and its code is encapsulated between braces:

function isEven(v) {
  if (v % 2 == 0) return true;
  else return false;
}

A value can be returned from any point in the function using the return statement. By default, all variables have a global scope and can be accessed from anywhere in the program, to the notable exception of parameters of functions and iteration variables (introduced in for statements) which are local to their block. The local keyword can be used to make a variable local to its block:

function computeSumOfEvenNumbers(a,b) {
    local total = 0;
    for [v in a...b : isEven(v)]
        total = total + v;
    return total;
}