LocalSolver: Data & Model Separation. Read & Write External Files.
Following up our previous article, Build Your First Mathematical Model with LocalSolver for Dummies, we will learn how to separate data from model, read external data and write into external file in this article.
Separate Data from Model
We can separate the model (.lsp) from the data (.in).
LSP enables a clean separation between the model and the accompanying data.
We have put all the coefficients into the model.
However, this would kill the value of a programming language.
First, we extract all the coefficients out of the mathematical equations.
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.
This can be translated as following:
/lsp/test1.lsp
/* Declares the optimisation model. */ function model() { // Parameters nBoxes = 300; capacityTruck30 = 30; capacityTruck40 = 40; costTruck30 = 400; costTruck40 = 500; // Decision Variables nTruck30 <- int(0,10); nTruck40 <- int(0,10); // Constraints capacityBox <- capacityTruck30 * nTruck30 + capacityTruck40 * nTruck40; constraint capacityBox >= nBoxes; // Objective Function totalCost <- costTruck30 * nTruck30 + costTruck40 * nTruck40; minimize totalCost; } function output() { println(nTruck30.value); println(nTruck40.value); } /* Parameterises the solver. */ function param() { lsTimeLimit = 10; }
We will get the same result with a cost of $3800.
We can change the number of boxes, capacities and the cost of the two trucks near the Parameters section to solve the same model.
Since it can be reused many times, especially sometimes we will deal with other trucks with different capacities and costs, we should change the names of parameters to be more general.
For example, we can change the name of nTruck40, originally for the capacity of 40 boxes, into nTruckX.
Same with nTruck30 into nTruckY.
This can be translated as following:
/lsp/test1.lsp
/* Declares the optimisation model. */ function model() { // Parameters capacityTruckX = 30; capacityTruckY = 40; costTruckX = 400; costTruckY = 500; nBoxes = 300; // Decision Variables nTruckX <- int(0,10); nTruckY <- int(0,10); // Constraints capacityBox <- capacityTruckX * nTruckX + capacityTruckY * nTruckY; constraint capacityBox >= nBoxes; // Objective Function totalCost <- costTruckX * nTruckX + costTruckY * nTruckY; minimize totalCost; } function output() { println(nTruckX.value); println(nTruckY.value); } /* Parameterises the solver. */ function param() { lsTimeLimit = 10; }
Reading External Input Data
So far, we have built the model without any reference to the external data.
We want to separate the model from the data, so without changing the same model can be solved by swapping different input data near .in file with little extra effort.
/lsp/test1.in
2 30 40 400 500 300
L1 Define the total amount of decision variables (nDV), which is 2.
L2 Define the parameters of constraints, which are 30 and 40.
L3 Define the parameters of objective function, which are 400 and 500.
L4 Define the bound of the constraint, which is 300.
The model will be translated as following (without mathematical functions):
/lsp/test1.lsp
use io; /* Reads instance data. */ function input() { local inFile = io.openRead(inFileName); nDV = inFile.readInt(); capacities[i in 0..nDV-1] = inFile.readInt(); costs[i in 0..nDV-1] = inFile.readInt(); nBoxes = inFile.readInt(); } /* Declares the optimisation model. */ function model() { // Decision Variables nTruck[i in 0..nDV-1] <- int(0,10); // Constraints capacityBox <- capacities[0] * nTruck[0] + capacities[1] * nTruck[1]; constraint capacityBox >= nBoxes; // Objective Function totalCost <- costs[0] * nTruck[0] + costs[1] * nTruck[1]; minimize totalCost; } function output() { println(nTruck[0].value); println(nTruck[1].value); } /* Parameterises the solver. */ function param() { if (lsTimeLimit == nil) lsTimeLimit = 20; }
we merely read integers one after another in the file (regardless of line breaks).
Solve
By launching it with the following command line, we will obtain an output similar to the previous model.
Note that for more flexibility, we have defined some of our variables in command line (inFileName, lsTimeLimit).
$ localsolver test1.lsp inFileName=test1.in lsTimeLimit=10
OR
if you put the data file into /myfolder under /lsp folder.
$ localsolver test1.lsp inFileName=myfolder/test1.in lsTimeLimit=10
And get the same result.
Mathematical Functions
All mathematical operators can be both used for modelling and for programming.
For instance, the statement c <- a * b means that we declare a modelling expression c corresponding to the product of the modelling expressions a and b.
While c = a * b means that we assign to the programming variable c the result of the product of the programming variables a and b.
For n-ary operators, the n-ary functional form (for example sum(a, b, c)) allows to declare expressions with an arbitrary number of operands.
When the number of operands is fixed, the binary infix form (for example a + b + c) can also be used.
For example, the statement:
capacityBox <- sum[i in 0..nDV-1](capacities[i] * nTruck[i]);
declares the expression capacityBox as the sum of the product of expressions capacities[i] and nTruck[i] for all items from 0 to nDV-1.
use io; /* Reads instance data. */ function input() { local inFile = io.openRead(inFileName); nDV = inFile.readInt(); capacities[i in 0..nDV-1] = inFile.readInt(); costs[i in 0..nDV-1] = inFile.readInt(); nBoxes = inFile.readInt(); } /* Declares the optimisation model. */ function model() { // Decision Variables nTruck[i in 0..nDV-1] <- int(0,10); // Constraints capacityBox <- sum[i in 0..nDV-1](capacities[i] * nTruck[i]); constraint capacityBox >= nBoxes; // Objective Function totalCost <- sum[i in 0..nDV-1](costs[i] * nTruck[i]); minimize totalCost; } function output() { for [i in 0..nDV-1] { println(nTruck[i].value); } } /* Parameterises the solver. */ function param() { if (lsTimeLimit == nil) lsTimeLimit = 20; }
Output the solution in an external file
You can define an output() function that will be called by LocalSolver at the end of the search. For instance we chose here to define a function writing the solution of the problem to a file whose name must be assigned (for instance in the command line) to some variable solFileName. It writes the solution in the following format:
- First line = the objective function
- Second line = the classes at positions 1 to nDV
Writing to a file is done with the usual print and println functions:
/lsp/test1.lsp
use io; /* Reads instance data. */ function input() { local inFile = io.openRead(inFileName); nDV = inFile.readInt(); capacities[i in 0..nDV-1] = inFile.readInt(); costs[i in 0..nDV-1] = inFile.readInt(); nBoxes = inFile.readInt(); } /* Declares the optimisation model. */ function model() { // Decision Variables nTruck[i in 0..nDV-1] <- int(0,10); // Constraints capacityBox <- sum[i in 0..nDV-1](capacities[i] * nTruck[i]); constraint capacityBox >= nBoxes; // Objective Function totalCost <- sum[i in 0..nDV-1](costs[i] * nTruck[i]); minimize totalCost; } /* Writes the solution in a file */ function output() { if(solFileName == nil) return; local solFile = io.openWrite(solFileName); solFile.println(totalCost.value); for [i in 0..nDV-1] solFile.print(nTruck[i].value + " "); solFile.println(); } /* Parameterises the solver. */ function param() { if (lsTimeLimit == nil) lsTimeLimit = 20; }
Solve
Note that we have defined an extra variable, i.e. solFileName, in command line.
$ localsolver test1.lsp inFileName=test1.in solFileName=sol.txt lsTimeLimit=10
Output Errors
Note that undefined variables are equal to nil what allows checking the existence of a variable (here we throw an error if inFileName or solfileName is not defined.
The model will be translated as following:
/lsp/test1.lsp
use io; /* Reads instance data. */ function input() { local usage = "Usage: localsolver test1.lsp " + "inFileName=inputFile [solFileName=outputFile] [lsTimeLimit=timeLimit]"; if (inFileName == nil) throw usage; local inFile = io.openRead(inFileName); nDV = inFile.readInt(); capacities[i in 0..nDV-1] = inFile.readInt(); costs[i in 0..nDV-1] = inFile.readInt(); nBoxes = inFile.readInt(); } /* Declares the optimisation model. */ function model() { // Decision Variables nTruck[i in 0..nDV-1] <- int(0,10); // Constraints capacityBox <- sum[i in 0..nDV-1](capacities[i] * nTruck[i]); constraint capacityBox >= nBoxes; // Objective Function totalCost <- sum[i in 0..nDV-1](costs[i] * nTruck[i]); minimize totalCost; } /* Writes the solution in a file */ function output() { if(solFileName == nil) return; local solFile = io.openWrite(solFileName); solFile.println(totalCost.value); for [i in 0..nDV-1] solFile.print(nTruck[i].value + " "); solFile.println(); } /* Parameterises the solver. */ function param() { if (lsTimeLimit == nil) lsTimeLimit = 20; }
Our next tutorial will be discussing about Modeling with sets, Lexicographic objective function and MIP to LSP migration.
If you're interested to learn more, please leave your comment below or private message me.
A good short series how to start using LocalSolver from scratch.?
Monitoring & Optimizing Lightning Routes to Fuel Bitcoin Liquidity
5 年Full article is available at?https://www.dhirubhai.net/pulse/localsolver-data-model-separation-read-write-external-chew-/ #localsolver?