Language extensions
Topics covered in this chapter:
- Generalized file handling
- Multiple models and parallel solving with mmjobs
- Graphics and GUIs
- Solvers
- Date and time data types
- Text handling and regular expressions
It has been said before that the functionality of the Mosel language can be extended by means of modules, that is, dynamic libraries written in C/C++. All through this manual we have used the module mmxprs to access Xpress Optimizer. Other modules we have used are mmsheet and mmodbc (access to spreadsheets and databases, see Section Reading data from spreadsheets and databases), and mmsystem (Sections Modules and Cut generation).
The full distribution of Mosel includes other functionality (modules and I/O drivers) that has not yet been mentioned in this document. In the following sections we give an overview with links where to find additional information.
Generalized file handling
The notion of (data) file encountered repeatedly in this user guide seems to imply a physical file. However, Mosel language statements (such as initializations from / to, fopen and fclose, exportprob) and the Mosel library functions (e.g., XPRMcompmod, XPRMloadmod, or XPRMrunmod) actually work with a much more general definition of a file, including (but not limited to)
- a physical file (text or binary)
- a block of memory
- a file descriptor provided by the operating system
- a function (callback)
- a database
The type of the file is indicated by adding to its name the name of the I/O driver that is to be used to access it. In Section Reading data from spreadsheets and databases we have used mmodbc.odbc:blend.mdb to access an MS Access database via the ODBC driver provided by the module mmodbc. If we want to work with a file held in memory we may write, for instance, mem:filename. The default driver (no driver prefix) is the standard Mosel file handling.
More generally, an extended file name has the form driver_name:file_name or module_name.driver_name:file_name if the driver is provided by the module module_name. The structure of the file_name part of the extended file name is specific to the driver, it may also consist of yet another extended file name (e.g. zlib.gzip:tmp:myfile.txt).
Displaying the available I/O drivers
The Mosel core drivers can be displayed from the command line with the following command (the listing will also include any drivers that are provided by currently loaded modules):
mosel exam -i
The drivers provided by modules are displayed by the exam command for the corresponding module (in this example: mmodbc)
mosel exam -i mmodbc
Library drivers (in particular the Java module mmjava that is embedded in the Mosel core and also the mmdotnet module on Windows platforms) can be displayed with the help of the corresponding program mmdispdso.[c|cs|java] in the subdirectory examples/mosel/Library of the Xpress distribution. The command for running the Java version might look as follows (please refer to the provided makefile):
java -classpath $XPRESSDIR/xprm.jar:. mmdispdso mmjava
List of I/O drivers
The standard distribution of Mosel defines the following I/O drivers (tee, null, bin, and tmp are documented in the 'Mosel Language Reference Manual'; the drivers sysfd, mem, cb, and raw that mainly serve when interfacing Mosel with a host application are documented in the `Mosel Libraries Reference Manual'):
- bin
-
Write (and read)
data files in a platform independent binary format. The
bin driver can only be used for
initializations blocks as a replacement of the default driver. Files in
bin format are generally smaller than the ASCII equivalent and preserve accuracy of floating point numbers.
Example: the following outputs data to a text file in binary formatinitializations to "bin:results.txt"
Another likely use of bin is in combination with mem or shmem for exchanging data in memory (see Section Exchanging data between models):initializations to "bin:shmem:results"
- cb
- Use a (callback) function as a file ( e.g., for reading and writing dynamically sized data in initializations blocks, see the examples in Section Dynamic data, or to write your own output or error stream handling functions when working with the Mosel libraries, see Section Redirecting the Mosel output for an example).
- mem
-
Use memory instead of physical files for reading or writing data (
e.g., for exchanging data between a model and the calling C application as shown in Section
Exchanging data between an application and a model or for compiling/loading a model to/from memory when working with the Mosel libraries).
Example: the following lines will compile the Mosel model burglar2.mos to memory and then load it from memory (full example in file ugcompmem.c).XPRMmodel mod; char bimfile[2000]; /* Buffer to store BIM file */ char bimfile_name[64]; /* File name of BIM file */ XPRMinit()) /* Initialize Mosel */ /* Prepare file name for compilation using 'mem' driver: */ /* "mem:base_address/size[/actual_size_of_pointer]" */ sprintf(bimfile_name, "mem:%p/%d", bimfile, (int)sizeof(bimfile)); /* Compile model file to memory */ XPRMcompmod(NULL, "burglar2.mos", bimfile_name, "Knapsack example")) /* Load a BIM file from memory */ mod = XPRMloadmod(bimfile_name, NULL);
- null
-
Disable a
stream.
Example: adding the linefopen("null:", F_OUTPUT)
in a Mosel model will disable all subsequent output by this model (until the output stream is closed or a new output stream is opened). - raw
- Implementation of the initializations block in binary mode, typically used in combination with mem for data exchange with a C host application (see Section Exchanging data between an application and a model).
- sysfd
-
Working with operating system file descriptors (for instance, file descriptors returned by the C function
open).
Example: in a C program, the lineXPRMsetdefstream(NULL, XPRM_F_ERROR, "sysfd:1");
will redirect the Mosel error stream to the default output stream. - tee
-
Output into up to 6 files simultaneously (
e.g., to display a log on
screen and write it to a file at the same time).
Example: adding the linefopen("tee:result.txt&tmp:log.txt&", F_OUTPUT)
in a Mosel model will redirect all subsequent model output to the files result.txt and tmp:log.txt, and at the same time display the output on the default output (screen), the latter is denoted by the & sign at the end of the filename string. The output to both locations is terminated by the statementfclose(F_OUTPUT)
after which output will again only go to default output. - tmp
-
Extension to the default driver that locates the specified file in the temporary directory
used by Mosel.
Example: adding the linefopen("tmp:log.txt", F_OUTPUT+F_APPEND)
redirects all subsequent output from a models to the file log.txt that is located in Mosel's temporary directory. It is equivalent tofopen(getparam("TMPDIR") + "/log.txt", F_OUTPUT+F_APPEND)
Some modules, listed below in alphabetical order, define additional I/O drivers. The drivers are documented with the corresponding module in the `Mosel Language Reference Manual':
- mmdotnet
- dotnet
-
Use a C# stream or object in place of a file in Mosel.
Example: The following C# program extract uses the dotnet driver to send data in standard initializations text format via a stream from the C# host program to a model (the model file burglar13.mos is the same as in Section Dynamic data, it uses the parameter DATAFILE as the filename for an initializations from block that expects to read data with the label 'DATA'.)// String containing initialization data for the model const string BurglarDat = "DATA: [(\"camera\") [15 2] (\"necklace\") [100 20] " + "(\"vase\") [90 20] (\"picture\") [60 30] (\"tv\") [40 40] " + "(\"video\") [15 30] (\"chest\") [10 60] (\"brick\") [1 10] ]\n"; static void Main(string[] args) { // Initialize Mosel XPRM mosel = XPRM.Init(); // Compile and load the Mosel model XPRMModel model = mosel.CompileAndLoad("burglar13.mos"); // Bind a stream based on the BurglarDat data to the name 'BurglarIni' // where the model will expect to find its initialization data model.Bind("BurglarIni", new StringReader(BurglarDat)); // Pass data location as a parameter to the model model.SetExecParam("DATAFILE","dotnet:BurglarIni"); // Run the model model.Run(); }
- dotnetraw
-
Exchange of data between a Mosel model and the C# application running the model using C# array structures; C# version of
raw.
Example: Send the data held in the two arrays vdata and wdata to a Mosel and retrieve solution data into the array solution. We use the option noindex with the dotnetraw driver to indicate that all data are saved in dense format ( i.e. as arrays containing just the data values, without any information about the indices).// Arrays containing initialization data for the model static double[] vdata = new double[] {15,100,90,60,40,15,10, 1}; static double[] wdata = new double[] { 2, 20,20,30,40,30,60,10}; // Main entry point for the application static void Main(string[] args) { // Initialize Mosel XPRM mosel = XPRM.Init(); // Compile and load the Mosel model XPRMModel model = mosel.CompileAndLoad("burglar8d.mos"); // Associate the .NET objects with names in Mosel model.Bind("vdat", vdata); model.Bind("wdat", wdata); // Create a new array for solution data and bind it to the name 'sol' double[] solution = new double[8]; mosel.Bind("sol", solution); // Pass data location as a parameter to the model model.ExecParams = "VDATA='noindex,vdat',WDATA='noindex,wdat',SOL='noindex,sol'"; // Run the model model.Run(); // Print the solution Console.WriteLine("Objective value: {0}", model.ObjectiveValue); for (int i=0;i<8;i++) Console.Write(" take({0}): {1}", (i+1), solution[i]); Console.WriteLine(); }
The model file burglar8d.mos uses the driver name as the file name in the initializations sections:initializations from 'dotnetraw:' VALUE as VDATA WEIGHT as WDATA end-initializations ... initializations to 'dotnetraw:' soltake as SOL end-initializations
- mmetc
- diskdata
- Access data in text files in diskdata format (see Sections Data input with diskdata and Data output with diskdata).
- mmhttp
- url
-
Access files that are stored on an HTTP enabled file serverfor reading, writing, or deletion (via
fdelete).
Example 1: the following command downloads and executes the Mosel BIM file mymodel.bim that is stored on the web server myserver:mosel run mmhttp.url:http://myserver/mymodel.bim
Example 2: the following lines of Mosel code save data held in the model object results to an XML format file on the server myserver that needs to be able to accept HTTP PUT requests.uses "mmxml" declarations results: xmldoc end-declarations save(results, "mmhttp.url:http://myserver/myresults.xml")
- mmjava
- java
-
Use a Java stream or a
ByteBuffer in place of a file in Mosel (
e.g. for redirecting default Mosel streams to Java objects, see the example in Section
Redirecting the Mosel output).
Example 1: in a Java program, the linemosel.setDefaultStream(XPRM.F_ERROR, "java:java.lang.System.out");
(where mosel is an object of class XPRM) will redirect the Mosel error stream to the default output stream of Java.
Example 2: the following lines will compile the Mosel model burglar2.mos to memory and then load it from memory (full example in the file ugcompmem.java).XPRM mosel; XPRMModel mod; ByteBuffer bimfile; // Buffer to store BIM file mosel = new XPRM(); // Initialize Mosel // Prepare file names for compilation: bimfile=ByteBuffer.allocateDirect(2048); // Create 2K byte buffer mosel.bind("mybim", bimfile); // Associate Java obj. with a // Mosel name // Compile model to memory mosel.compile("", "burglar2.mos", "java:mybim", ""); bimfile.limit(bimfile.position()); // Mark end of data in buffer bimfile.rewind(); // Back to the beginning mod=mosel.loadModel("java:mybim"); // Load BIM file from memory mosel.unbind("mybim"); // Release memory bimfile=null;
- jraw
- Exchange of data between a Mosel model and the Java application running the model; Java version of raw. See Section Exchanging data between an application and a model for examples.
- mmjobs
- shmem
- Use shared memory instead of physical files for reading or writing data ( e.g., for exchanging data between several models executed concurrently—one model writing, several models reading—as shown in Section Exchanging data between models, or for compiling/loading a model to/from memory from within another model, see Section Compiling to memory).
- mempipe
- Use memory pipes for reading or writing data ( e.g., for exchanging data between several models executed concurrently—one model reading, several models writing; see Section 'Dantzig-Wolfe decomposition' of the whitepaper 'Multiple models and parallel solving with Mosel' for an example).
- rcmd
-
Starts the specified command in a new process and connects its standard input and output streams to the calling Mosel instance.
Example: The following line starts a Mosel instance on a remote computer with the name some_other_machine connecting to it via rsh. The command mosel -r starts Mosel in remote mode.rcmd:rsh some_other_machine mosel -r
- rmt
-
Can be used with any routine expecting a physical file for accessing files on remote instances. A particular instance can be specified by prefixing the file name by its node number enclosed in square brackets. See the distributed computing versions of the models in the whitepaper
Multiple models and parallel solving with Mosel for further examples.
Example: the following loads the BIM file mymodel.bim that is located at the parent node (-1) of the current instance into the model myModel of the Mosel instance myInst.load(myInst, myModel, "rmt:[-1]mymodel.bim")
The rmt driver can be combined with cb, sysfd, tmp or java, such asfopen("rmt:tmp:log.txt", F_OUTPUT)
- xsrv
-
Connects to the specified host running the Mosel Remote Launcher
xprmsrv. Optionally, the port number, a context name, a password, and environment variable settings can be specified.
Example: the following starts a new Mosel instance (possibly using a different Xpress version) on the current machine, redefining Mosel's current working directory and the environment variable MYDATA.xsrv:localhost/myxpress|MOSEL_CWD=C:\workdir|MYDATA=${MOSEL_CWD}\data
- mmoci
- oci
-
Access an Oracle database for reading and writing in
initializations blocks (see the whitepaper
Using ODBC and other database interfaces with Mosel for further examples).
Example: the OCI driver is used with a connection string that contains the database name, user name and password in the following formatinitializations from "mmoci.oci:myusername/mypassword@dbname
- mmodbc
- odbc
- Access data in external data sources via an ODBC connection (see Section Reading data from spreadsheets and databases for an example).
- mmsheet
- csv
-
Access spreadsheets in CSV format. In addition to the standard options supported by other Mosel spreadsheet drivers (such as
grow for dynamic sizing of output ranges and
noindex for dense data format), this driver can be configured with field and decimal separators and also with the values representing 'true' and 'false'.
Example: the following Mosel code for reading data from the file mydata.csv sets the separator sign to ';', a comma is used as decimal separator, and the Boolean values true and false are represented by 'y' and 'n' respectively:initializations from "mmsheet.csv:dsep=,;fsep=;;true=y;false=n;mydata.csv A as "[B2:D8]" B as "[E2:Z10](#3,#1)" ! 3rd and 1st column from the range end-initializations
Notice that with CVS format files there is no notion of range or field names and the cell positions need to be used to specify the data location. - excel
- Access data in MS Excel spreadsheets directly (see the example in Section Excel spreadsheets).
- xls
- Access spreadsheets in Excel's XLS format (see Section Generic spreadsheet example for an example).
- xlsx
- Access spreadsheets in Excel's XLSX and XLSM formats (usage in analogy to the XLS example shown in Section Generic spreadsheet example).
- mmsystem
- pipe
-
Open a pipe and start an external program which is used as input or output stream for a Mosel model.
Example: the following will start gnuplot and draw a 3-dimensional sphere with data for the radius R and position (X,Y,Z) defined in the Mosel model:fopen("mmsystem.pipe:gnuplot -p", F_OUTPUT+F_LINBUF) writeln('set parametric') writeln('set urange [0:2*pi]') writeln('set vrange [0:2*pi]') writeln('set pm3d depthorder hidden3d 3') writeln("splot cos(u) * cos(v) *", R, "+", X, ", sin(u) * cos(v)*", R, "+", Y, ", sin(v)*", R, "+", Z, " notitle with pm3d") writeln("pause mouse keypress,close")
- text
-
Use a (multiline) text as a file (see Section
Text handling and regular expressions for further detail on the type 'text').
Example: the following will compile the model source held in the text source_of_model to a BIM file in a temporary directory (file ugcompfrommem.mos):public declarations source_of_model=`SUBMODELSOURCE model Burglar uses 'mmxprs' declarations WTMAX = 102 ! Maximum weight allowed ITEMS = 1..8 ! Index range for items VALUE: array(ITEMS) of real ! Value of items WEIGHT: array(ITEMS) of real ! Weight of items take: array(ITEMS) of mpvar ! 1 if we take item i; 0 otherwise end-declarations VALUE :: [15, 100, 90, 60, 40, 15, 10, 1] WEIGHT:: [ 2, 20, 20, 30, 40, 30, 60, 10] ! Objective: maximize total value MaxVal:= sum(i in ITEMS) VALUE(i)*take(i) ! Weight restriction sum(i in ITEMS) WEIGHT(i)*take(i) <= WTMAX ! All variables are 0/1 forall(i in ITEMS) take(i) is_binary maximize(MaxVal) ! Solve the problem writeln("Solution:\n Objective: ", getobjval) end-model SUBMODELSOURCE` end-declarations ! Compile the model from memory if compile("", "text:source_of_model", "tmp:burglar.bim")<>0 then exit(1) end-if
- zlib
- deflate
-
Handles files compressed using the
zlib compression format.
Example: decompress the file myfile.gz to myfile:fcopy("zlib.deflate:myfile.gz", "myfile")
- gzip
-
Handles files compressed using the
gzip compression format.
Example: the following statement creates the compressed file myfile.gz from myfile.txt:fcopy("myfile.txt", "zlib.gzip:myfile.gz")
The reader is referred to the whitepaper Generalized file handling in Mosel that is provided as a part of the Xpress documentation in the standard distribution and also on the Xpress website under `Product Documentation' for further explanation of this topic and a documented set of examples, including some user-written I/O drivers.
Multiple models and parallel solving with mmjobs
The module mmjobs makes it possible to exchange information between models running concurrently—locally or in a network. Its functionality includes facilities for handling Mosel instances (e.g. connecting and disconnecting Mosel instances, access to remote files), model management (e.g. compiling, running, or interrupting a model from within a second model), synchronization of concurrent models based on event queues, and a shared memory I/O driver for an efficient exchange of data between models that are executed concurrently.
Several complete examples (including examples of Benders decomposition and Dantzig-Wolfe decomposition) of the use of module mmjobs are described in the whitepaper Multiple models and parallel solving with Mosel that is provided as a part of the Xpress documentation and also on the 'Product Documentation' page of the Xpress website. We show here how to use the basic functionality for executing a model from a second model.
Running a model from another model
As a test case, we shall once more work with model prime.mos from Section Working with sets. In the first instance, we now show how to compile and run this model from a second model, runprime.mos:
model "Run model prime" uses "mmjobs" declarations modPrime: Model event: Event end-declarations ! Compile 'prime.mos' if compile("prime.mos")<>0: exit(1) load(modPrime, "prime.bim") ! Load bim file run(modPrime, "LIMIT=50000") ! Start execution and wait(2) ! wait 2 seconds for an event if isqueueempty then ! No event has been sent... writeln("Model too slow: stopping it!") stop(modPrime) ! ... stop the model, wait ! ... and wait for the termination event end-if ! An event is available: model finished event:=getnextevent writeln("Exit status: ", getvalue(event)) writeln("Exit code : ", getexitcode(modPrime)) unload(modPrime) ! Unload the submodel end-model
The compile command generates the BIM file for the given submodel; the command load loads the binary file into Mosel; and finally we start the model with the command run. The run command is not used in its basic version (single argument with the model reference): here its second argument sets a new value for the parameter LIMIT of the submodel.
In addition to the standard compile–load–run sequence, the model above shows some basic features of interaction with the submodel: if the submodel has not terminated after 2 seconds (that is, if it has not sent a termination message) it is stopped by the controlling (parent) model. After termination of the submodel (either by finishing its calculations within less than 2 seconds or stopped by the parent model) its termination status and the exit value are retrieved (functions getvalue and getexitcode). Unloading a submodel explicitly as shown here is only really necessary in larger applications that continue after the termination of the submodel, so as to free the memory used by it.
Note: our example model shows an important property of submodels—they are running in parallel to the parent model and also to any other submodels that may have been started from the parent model. It is therefore essential to insert wait at appropriate places to coordinate the execution of the different models.
Compiling to memory
The model shown in the previous section compiles and runs a submodel. The default compilation of a Mosel file filename.mos generates a binary model file filename.bim. To avoid the generation of physical BIM files for submodels we may compile the submodel to memory, making use of the concept of I/O drivers introduced in Section Generalized file handling.
Compiling a submodel to memory is done by replacing the standard compile and load commands by the following lines (model runprime2.mos):
if compile("","prime.mos","shmem:bim")<>0: exit(1) load(modPrime,"shmem:bim") ! Load bim file from memory... fdelete("shmem:bim") ! ... and release the memory block
The full version of compile takes three arguments: the compilation flags (e.g., use "g" for debugging), the model file name, and the output file name (here a label prefixed by the name of the shared memory driver). Having loaded the model we may free the memory used by the compiled model with a call to fdelete (this subroutine is provided by the module mmsystem).
Exchanging data between models
When working with submodels we are usually not just interested in executing the submodels, we also wish to retrieve their results in the main model. This is done most efficiently by exchanging data in (shared) memory as shown in the model runprimeio.mos below. Besides the retrieval and printout of the solution we have replaced the call to stop by sending the user event `STOPMOD' to the submodel: instead of simply terminating the submodel this event will make it interrupt its calculations and write out the current solution. To make sure that the submodel is actually running at the point where we sent the `STOPMOD' event, we have also introduced an event sent from the submodel to its parent model to indicate the point of time when it starts the calculations (with heavy operating system loads the actual submodel start may be delayed). Once the submodel has terminated (after sending the `STOPMOD' event we wait for the model's termination message) we may read its solution from memory, using the initializations block with the drivers raw (binary format) and shmem (read from shared memory).
model "Run model primeio" uses "mmjobs" declarations modPrime: Model NumP: integer ! Number of prime numbers found SetP: set of integer ! Set of prime numbers STOPMOD = 2 ! "Stop submodel" user event MODREADY = 3 ! "Submodel ready" user event end-declarations ! Compile 'prime.mos' if compile("primeio.mos")<>0: exit(1) load(modPrime, "primeio.bim") ! Load bim file ! Disable model output setdefstream(modPrime,"","null:","null:") run(modPrime, "LIMIT=35000") ! Start execution and wait ! ... wait for an event if getclass(getnextevent) <> MODREADY then writeln("Problem with submodel run") exit(1) end-if wait(2) ! Let the submodel run for 2 seconds if isqueueempty then ! No event has been sent... writeln("Model too slow: stopping it!") send(modPrime, STOPMOD, 0) ! ... stop the model, then wait wait end-if dropnextevent ! Discard end events initializations from "bin:shmem:resdata" NumP SetP as "SPrime" end-initializations writeln(SetP) ! Output the result writeln(" (", NumP, " prime numbers.)") unload(modPrime) end-model
We now have to modify the submodel (file primeio.mos) correspondingly: at its start it sends the `MODREADY' event to trigger the start of the time measurement in the parent model and it further needs to intercept the `STOPMOD' event interrupting the calculations (via an additional test isqueueempty for the repeat-until loop) and write out the solution to memory in the end:
model "Prime IO" uses "mmjobs" parameters LIMIT=100 ! Search for prime numbers in 2..LIMIT OUTPUTFILE = "bin:shmem:resdata" ! Location for output data end-parameters declarations SNumbers: set of integer ! Set of numbers to be checked SPrime: set of integer ! Set of prime numbers MODREADY = 3 ! "Submodel ready" user event end-declarations send(MODREADY,0) ! Send "model ready" event SNumbers:={2..LIMIT} writeln("Prime numbers between 2 and ", LIMIT, ":") n:=2 repeat while (not(n in SNumbers)) n+=1 SPrime += {n} ! n is a prime number i:=n while (i<=LIMIT) do ! Remove n and all its multiples SNumbers-= {i} i+=n end-do until (SNumbers={} or not isqueueempty) NumP:= getsize(SPrime) initializations to OUTPUTFILE NumP SPrime end-initializations end-model
Note: since the condition isqueueempty is tested only once per iteration of the repeat-until loop, the termination of the submodel is not immediate for large values of LIMIT. If you wish to run this model with very large values, please see Section Efficient modeling through the Mosel Profiler for an improved implementation of the prime number algorithm that considerably reduces its execution time.
Distributed computing
The module mmjobs not only allows the user to start several models in parallel on a given machine, it also makes it possible to execute models remotely and to coordinate their processing. With only few additions, the model from Section Running a model from another model is extended to form model version runprimedistr.mos that launches the submodel prime.mos on another Mosel instance (either on the local machine as in the present example or on some other machine within a network specified by its name or IP address in the connect statement): we need to create a new Mosel instance (through a call to connect) and add an additional argument to the load statement to specify the Mosel instance we wish to use. All else remains the same as in the single-instance version.
model "Run model prime remotely" uses "mmjobs" declarations moselInst: Mosel modPrime: Model event: Event end-declarations ! Compile 'prime.mos' locally if compile("prime.mos")<>0: exit(1) ! Start a remote Mosel instance: ! "" means the node running this model if connect(moselInst, "")<>0: exit(2) ! Load bim file into remote instance load(moselInst, modPrime, "rmt:prime.bim") run(modPrime, "LIMIT=50000") ! Start execution and wait(2) ! wait 2 seconds for an event if isqueueempty then ! No event has been sent... writeln("Model too slow: stopping it!") stop(modPrime) ! ... stop the model, then wait wait end-if ! An event is available: model finished event:=getnextevent writeln("Exit status: ", getvalue(event)) writeln("Exit code : ", getexitcode(modPrime)) unload(modPrime) ! Unload the submodel end-model
This model can be extended to include data exchange between the parent and the submodel exactly in the same way as in the example of Section Exchanging data between models. The main difference (besides the connection to a remote instance) lies in the use of the driver rmt to denote the Mosel instance where data is to be saved. In our case, we wish to save data on the instance running the parent model, meaning that we need to use the rmt: prefix when writing output within the submodel. The new output file name is passed into the submodel via the runtime parameter OUTPUTFILE:
run(modPrime, "LIMIT=35000,OUTPUTFILE=bin:rmt:shmem:resdata")
The parent model then simply reads as before from its own instance:
initializations from "bin:shmem:resdata" NumP SetP as "SPrime" end-initializations
The Mosel model for the extended version including data exchange is provided in the file runprimeiodistr.mos.
Graphics and GUIs
Different components of FICO Xpress Optimization provide graphics and GUI functionality for Mosel models:
- Users may enrich their Mosel models with graphical output using the module mmsvg.
- Xpress Insight embeds Mosel models into a multi-user application for deploying optimization models in a distributed client-server architecture. Through the Xpress Insight GUI, business users interact with Mosel models to evaluate different scenarios and model configurations without directly accessing to the model itself.
- XML is a widely used data format, particularly in the context of web-based applications. The Mosel module mmxml provides functionality for generating and handling XML documents. mmxml can also be used to produce HTML format output from Mosel that can be incorporated into Xpress Insight applications.
The functionality of modules mmxml and mmsvg is documented in the Mosel Language Reference Manual. Xpress Insight has several manuals and guides for developers and GUI users, most importantly the 'Xpress Insight Developer Guide' and 'Xpress Insight Web Client User Guide'; the corresponding examples are located in the subdirectory examples/insight of the Xpress distribution.
Drawing user graphs with mmsvg
The graphic in Figure User graph for the transport problem is an example of using mmsvg to produce a graphical representation of the solution to the transport problem from Section A transport example.
It was obtained by calling the following procedure draw_solution at the end of the model file (that is, after the call to minimize).
procedure draw_solution declarations YP: array(PLANT) of integer ! y-coordinates of plants YR: array(REGION) of integer ! y-coordinates of sales regions end-declarations ! Scale the size of the displayed graph svgsetgraphviewbox(0,0,4,getsize(REGION)+1) svgsetgraphscale(100) ! Determine y-coordinates for plants and regions ct:= 1+floor((getsize(REGION)-getsize(PLANT))/2) forall(p in PLANT, ct as counter) YP(p):= ct ct:=1 forall(r in REGION, ct as counter) YR(r):= ct ! Draw the plants svgaddgroup("PGr", "Plants", svgcolor(0,63,95)) forall(p in PLANT) svgaddtext(0.55, YP(p)-0.1, p) ! Draw the sales regions svgaddgroup("RGr", "Regions", svgcolor(0,157,169)) forall(r in REGION) svgaddtext(3.1, YR(r)-0.1, r) ! Draw all transport routes svgaddgroup("TGr", "Routes", SVG_GREY) forall(p in PLANT, r in REGION | exists(TRANSCAP(p,r)) ) svgaddline(1, YP(p), 3, YR(r)) ! Draw the routes used by the solution svgaddgroup("SGr", "Solution", SVG_ORANGE) forall(p in PLANT, r in REGION | exists(flow(p,r)) and getsol(flow(p,r)) > 0) svgaddarrow(1, YP(p), 3, YR(r)) ! Save graphic in SVG format svgsave("transport.svg") ! Display the graphic svgrefresh svgwaitclose end-procedure

Figure 21.1: User graph for the transport problem
XML and HTML
HTML files are simple text files—their contents can be generated as free-format output from Mosel (see for example Section File output). However, more elegantly we can use Mosel's XML module mmxml to generate HTML documents.
mmxml
The module mmxml provides an XML parser and generator for the manipulation of XML documents from Mosel models. An XML document is stored as a list of nodes. mmxml supports the node types 'element', 'text', 'comment', CDATA, 'processing instruction' and DATA (see section mmxml of the Mosel Language Reference Manual for further detail). Each node is characterized by a name and a value. Element nodes have also an ordered list of child nodes. The root node is a special element node with no name, no parent and no successor that includes the entire document as its children.
The type xmldoc represents an XML document stored in the form of a tree. Each node of the tree is identified by a node number (an integer) that is attached to the document (i.e. a node number cannot be shared by different documents and in two different documents the same number represents two different nodes). The root node of the document has number 0. Nodes can be retrieved using a path similar to a directory path used to locate a file (usually called XML path).
Reading and writing XML data
Data for the 'Transport' problem has so far been given as a text data file in initializations format. We now wish to read in the same data from the XML file transprt.xml shown here:
<transport fuelcost="17"> <demand> <region name="Scotland">2840</region> <region name="North">2800</region> <region name="SWest">2600</region> <region name="SEast">2820</region> <region name="Midlands">2750</region> </demand> <plantdata> <plant name="Corby"> <capacity>3000</capacity> <cost>1700</cost> </plant> <plant name="Deeside"> <capacity>2700</capacity> <cost>1600</cost> </plant> <plant name="Glasgow"> <capacity>4500</capacity> <cost>2000</cost> </plant> <plant name="Oxford"> <capacity>4000</capacity> <cost>2100</cost> </plant> </plantdata> <routes> <route from="Corby" to="North" capacity="1000" distance="400"/> <route from="Corby" to="SWest" capacity="1000" distance="400"/> <route from="Corby" to="SEast" capacity="1000" distance="300"/> <route from="Corby" to="Midlands" capacity="2000" distance="100"/> <route from="Deeside" to="Scotland" capacity="1000" distance="500"/> <route from="Deeside" to="North" capacity="2000" distance="200"/> <route from="Deeside" to="SWest" capacity="1000" distance="200"/> <route from="Deeside" to="SEast" capacity="1000" distance="200"/> <route from="Deeside" to="Midlands" capacity="300" distance="400"/> <route from="Glasgow" to="Scotland" capacity="3000" distance="200"/> <route from="Glasgow" to="North" capacity="2000" distance="400"/> <route from="Glasgow" to="SWest" capacity="1000" distance="500"/> <route from="Glasgow" to="SEast" capacity="200" distance="900"/> <route from="Oxford" to="North" capacity="2000" distance="600"/> <route from="Oxford" to="SWest" capacity="2000" distance="300"/> <route from="Oxford" to="SEast" capacity="2000" distance="200"/> <route from="Oxford" to="Midlands" capacity="500" distance="400"/> </routes> </transport>
The Mosel code (file transport_xml.mos) for reading this XML file first loads the entire document (load). We then need to retrieve the nodes we are interested in from the XML document tree structure. This is achieved by selecting lists of nodes that satisfy some specified condition (here: a specific XML path that describes the location of the desired nodes in the document, such as transport/demand/region). XML documents are saved as text, we therefore use functions like getrealvalue or getstrattr to retrieve data and indices of the desired type into the model data structures.
uses "mmxml" declarations REGION: set of string ! Set of customer regions PLANT: set of string ! Set of plants DEMAND: array(REGION) of real ! Demand at regions PLANTCAP: array(PLANT) of real ! Production capacity at plants PLANTCOST: array(PLANT) of real ! Unit production cost at plants TRANSCAP: dynamic array(PLANT,REGION) of real ! Capacity on each route plant->region DISTANCE: dynamic array(PLANT,REGION) of real ! Distance of each route plant->region FUELCOST: real ! Fuel cost per unit distance AllData: xmldoc ! XML document NodeList: list of integer ! List of XML nodes end-declarations load(AllData, "transprt.xml") ! Load the entire XML document getnodes(AllData, "transport/demand/region", NodeList) forall(l in NodeList) ! Read demand data DEMAND(getstrattr(AllData,l,"name")):= getrealvalue(AllData, l) getnodes(AllData, "transport/plantdata/plant", NodeList) forall(l in NodeList) do ! Read plant data PLANTCAP(getstrattr(AllData,l,"name")):= getrealvalue(AllData, getnode(AllData,l,"capacity")) PLANTCOST(getstrattr(AllData,l,"name")):= getrealvalue(AllData, getnode(AllData,l,"cost")) end-do ! Read routes data getnodes(AllData, "transport/routes/route", NodeList) forall(l in NodeList) do DISTANCE(getstrattr(AllData,l,"from"),getstrattr(AllData,l,"to")):= getrealattr(AllData,l,"distance") TRANSCAP(getstrattr(AllData,l,"from"),getstrattr(AllData,l,"to")):= getrealattr(AllData,l,"capacity") end-do ! Read 'fuelcost' attribute FUELCOST:= getrealattr(AllData, getnode(AllData, "transport"), "fuelcost")
In the model extract above we have used several simple XML path specifications to retrieve lists of nodes from the XML document. Such queries can take more complicated forms, including tests on node values (all plants with capacity > 3500) or attributes (all routes to 'Scotland')—see the chapter on mmxml in the Mosel Language Reference Manual for further detail.
getnodes(AllData, "transport/plantdata/plant/capacity[number()>3500]/..", NodeList) getnodes(AllData, "transport/routes/route[@to='Scotland']", NodeList)
We now also want to output the optimization results in XML format. As a first step, we create a root element 'solution' in the XML document ResData. In a well-formed XML document, all elements need to form a tree under the root element. All following nodes (containing the solution information per plant) are therefore created as element nodes under the 'solution' node. The objective function solution value and the execution date of the model are saved as attributes to the 'solution' tag. And finally, we use save to write an XML file or display a node with the (sub)tree formed by its children.
declarations ResData: xmldoc ! XML document Sol,Plant,Reg,Total: integer ! XML nodes end-declarations Sol:=addnode(ResData, 0, XML_ELT, "solution") ! Create root node "solution" setattr(ResData, Sol, "Objective", MinCost.sol) ! Obj. value as attribute setattr(ResData, Sol, "RunDate", text(datetime(SYS_NOW))) forall(p in PLANT) do Plant:=addnode(ResData, Sol, XML_ELT, "plant") ! Add a node to "solution" setattr(ResData, Plant, "name", p) ! ... with attribute "name" forall(r in REGION | flow(p,r).sol>0) do Reg:=addnode(ResData, Plant, XML_ELT, "region") ! Add a node to "plant" setattr(ResData, Reg, "name", r) ! ... with attribute "name" setvalue(ResData, Reg, flow(p,r).sol) ! ... and solution value end-do Total:=addnode(ResData, Plant, "total", sum(r in REGION)flow(p,r).sol) ! Add node with total flow end-do save(ResData, "transportres.xml") ! Save solution to XML format file save(ResData, Sol, "") ! Display XML format solution on screen
The Mosel code printed above will create a file transportres.xml with the following contents:
<?xml version="1.0" encoding="iso-8859-1"?> <solution Objective="8.1018e+07" RunDate="2012-10-17T14:00:50,664"> <plant name="Corby"> <region name="North">80</region> <region name="SEast">920</region> <region name="Midlands">2000</region> <total>3000</total> </plant> <plant name="Deeside"> <region name="North">1450</region> <region name="SWest">1000</region> <region name="Midlands">250</region> <total>2700</total> </plant> <plant name="Glasgow"> <region name="Scotland">2840</region> <region name="North">1270</region> <total>4110</total> </plant> <plant name="Oxford"> <region name="SWest">1600</region> <region name="SEast">1900</region> <region name="Midlands">500</region> <total>4000</total> </plant> </solution>
Generating HTML
An HTML file is generated and written out just like XML documents. The name of the root element in this case usually is 'html'. Below follows an extract of the code (example file transport_html.mos) that generates the HTML page shown in Figure HTML page generated by Mosel. Notice the use of copynode to insert the same node/subtree at different positions in the XML document. By default, new nodes created with addnode are appended to the end of the node list of the specified parent node. This corresponds to using the value XML_LASTCHILD for the (optional) positioning argument of subroutines creating new nodes.
declarations ResultHTML: xmldoc Root, Head, Body, Style, Title, Table, Row, Cell, EmptyCell: integer end-declarations Root:= addnode(ResultHTML, 0, XML_ELT, "html") ! Root node Head:= addnode(ResultHTML, Root, XML_ELT, "head") ! Element node Style:= addnode(ResultHTML, Head, XML_ELT, "style", "body {font-family: Verdana, Geneva, Helvetica, Arial, sans-serif;" + " color: 003f5f; background-color: d8e3e9 }\n" + "table td {background-color: e9e3db; color: 003f5f; text-align: right }\n" + "table th {background-color: f7c526; color: 003f5f}") setattr(ResultHTML, Style, "type", "text/css") ! Set an attribute Body:= addnode(ResultHTML, Root, XML_ELT, "body") ! Body of HTML page Title:= addnode(ResultHTML, Body XML_ELT, "h2", "Transportation Plan") Table:= addnode(ResultHTML, Body, XML_ELT, "table") ! 'table' element setattr(ResultHTML, Table, "width", '100%') ! Set some attributes setattr(ResultHTML, Table, "border", 0) Row:= addnode(ResultHTML, Table, XML_ELT, "tr") ! Table row element EmptyCell:= addnode(ResultHTML, Row, XML_ELT, "td") ! Table cell element setattr(ResultHTML, EmptyCell, "width", '5%') ! Set an attribute Cell:= addnode(ResultHTML, Row, "td", "Total cost: " + textfmt(MinCost.sol,6,2)) ! Table cell element with contents Cell:= addnode(ResultHTML, Cell, XML_DATA, "£") ! DATA node Cell:= addnode(ResultHTML, Row, "td", text(datetime(SYS_NOW))) EmptyCell:= ! Node created by copying a node copynode(ResultHTML, EmptyCell, ResultHTML, Row, XML_LASTCHILD) ... save(ResultHTML, "transportres.html") ! Write the HTML file save(ResultHTML, Table, "") ! Display table def. on screen
The resulting HTML page might now look as shown in Figure HTML page generated by Mosel.

Figure 21.2: HTML page generated by Mosel
Xpress Insight
For embedding a Mosel model into Xpress Insight, we make a few edits to the model. All functionality that is needed to establish the connection between Mosel and Xpress Insight is provided by mminsight that now needs to be loaded. Since Xpress Insight manages the data scenarios, we only need to read in data from the original sources when loading the so-called baseline scenario into Xpress Insight (triggered by the test of insightgetmode=INSIGHT_MODE_LOAD in the model below), for model runs started from Xpress Insight (that is, in the case of insightgetmode=INSIGHT_MODE_RUN) the scenario data will be input directly from Xpress Insight at the insertion point marked with insightpopulate. For standalone execution (insightgetmode=INSIGHT_MODE_NONE) the model defaults to its original behavior, that is, reading the data from file followed by definition and solving of the optimization problem. Furthermore, the solver call to start the optimization is replaced by insightminimize / insightmaximize. Please also note that any model entities to be managed by Xpress Insight need to be declared as public—in the following code example this marker has been applied to the entire declarations block, alternatively it can be added to individual entity declarations.
model "Transport (Xpress Insight)" uses "mmxprs", "mminsight" public declarations REGION: set of string ! Set of customer regions PLANT: set of string ! Set of plants DEMAND: array(REGION) of real ! Demand at regions PLANTCAP: array(PLANT) of real ! Production capacity at plants PLANTCOST: array(PLANT) of real ! Unit production cost at plants TRANSCAP: dynamic array(PLANT,REGION) of real ! Capacity on each route plant->region DISTANCE: dynamic array(PLANT,REGION) of real ! Distance of each route plant->region FUELCOST: real ! Fuel cost per unit distance MaxCap: array(PLANT) of linctr ! Capacity constraints flow: dynamic array(PLANT,REGION) of mpvar ! Flow on each route end-declarations procedure readdata ! Data for baseline initializations from 'transprt.dat' DEMAND [PLANTCAP,PLANTCOST] as 'PLANTDATA' [DISTANCE,TRANSCAP] as 'ROUTES' FUELCOST end-initializations end-procedure case insightgetmode of INSIGHT_MODE_LOAD: do readdata ! Input baseline data and exit(0) ! stop the model run here end-do INSIGHT_MODE_RUN: insightpopulate ! Inject scenario data and continue INSIGHT_MODE_NONE: readdata ! Input baseline data and continue else writeln("Unknown run mode") exit(1) end-case ! Create the flow variables that exist forall(p in PLANT, r in REGION | exists(TRANSCAP(p,r)) ) create(flow(p,r)) ! Objective: minimize total cost MinCost:= sum(p in PLANT, r in REGION | exists(flow(p,r))) (FUELCOST * DISTANCE(p,r) + PLANTCOST(p)) * flow(p,r) ! Limits on plant capacity forall(p in PLANT) MaxCap(p):= sum(r in REGION) flow(p,r) <= PLANTCAP(p) ! Satisfy all demands forall(r in REGION) sum(p in PLANT) flow(p,r) = DEMAND(r) ! Bounds on flows forall(p in PLANT, r in REGION | exists(flow(p,r))) flow(p,r) <= TRANSCAP(p,r) insightminimize(MinCost) ! Solve the problem through Xpress Insight
The handling of model entities by Xpress Insight can be configured via annotations (see Chapter Annotations for detail), for example to define aliases to be displayed in the UI in place of the model entity names, or to select which data entities are to be treated as inputs or results respectively. The annotations defined by Xpress Insight form the category insight, the following model extract shows some example definitions for the 'Transport' problem, please refer to the Xpress Insight Mosel Interface Manual for a complete documentation.
!@insight.manage=input public declarations !@insight.alias Customer regions REGION: set of string ! Set of customer regions !@insight.alias Plants PLANT: set of string ! Set of plants !@insight.alias Demand DEMAND: array(REGION) of real ! Demand at regions !@insight.alias Production capacity PLANTCAP: array(PLANT) of real ! Production capacity at plants !@insight.alias Unit production cost PLANTCOST: array(PLANT) of real ! Unit production cost at plants !@insight.alias Capacity on each route TRANSCAP: dynamic array(PLANT,REGION) of real !@insight.alias Distance per route DISTANCE: dynamic array(PLANT,REGION) of real !@insight.alias Fuel cost per unit distance FUELCOST: real ! Fuel cost per unit distance end-declarations !@insight.manage=result public declarations !@insight.alias Production capacity limits MaxCap: array(PLANT) of linctr ! Capacity constraints !@insight.alias Amount shipped flow: dynamic array(PLANT,REGION) of mpvar ! Flow on each route !@insight.hidden=true MincostSol: real !@insight.alias Total pltotal: array(PLANT) of real ! Solution: production per plant end-declarations
Xpress Insight expects models to be provided in compiled form, that is, as BIM files. An Xpress Insight app archive is a ZIP archive that contains the BIM file and the optional subdirectories model_resources (data files), client_resources (custom view definitions), and source (Mosel model source files). When developing an Insight app with Xpress Workbench, select the button to create the app archive or
to publish the app directly to Insight.

Figure 21.3: Xpress Insight web view showing a VDL view for the transportation problem
If we wish to deploy an optimization app to the Xpress Insight Web Client we need to take into account that besides the 'Entity browser' view that lists all managed model entities there are no default views: any visualization of input or result data needs to be explicitly implemented as views. The screenshot in Figure Xpress Insight web view showing a VDL view for the transportation problem shows a VDL (View Definition Language) view for the transportation example with editable input data and a 'Run' button that triggers re-solving of the optimization problem with the scenario data displayed on screen. Such views can be created via a drag-and-drop editor in Xpress Workbench (the screenshot in Figure VDL view designer in Xpress Workbench shows the design view of the VDL file that defines the webview in the previous figure). The complete set of files is provided in the app archive transport_insight.zip. Please refer to the Xpress Insight Developer Manual for further detail on view definition using the VDL markup language or the Xpress Insight Javascript API.

Figure 21.4: VDL view designer in Xpress Workbench
Solvers
In this user guide we have explained the basics of working with Mosel, focussing on the formulation of Linear and Mixed Integer Programming problems and related solution techniques. However, the Mosel language is not limited to certain types of Mathematical Programming problems.
The module mmnl extends the Mosel language with functionality for handling general non-linear constraints. This module (documented in the Mosel Language Reference Manual) does not contain any solver on its own. In combination with mmxprs you can use it to formulate and solve QCQP (Quadratically Constrained Quadratic Programming) problems through the QCQP solvers within Xpress Optimizer.
A special case of non-linear constraints are the so-called general constraints, that is, specific constraint relations that are recognized by MIP solvers but they require the module mmxnlp for their formulation in Mosel—see the discussion in Section Integer Programming entities in Mosel.
The Mosel module mmrobust (documented in the whitepaper Robust Optimization with Xpress) extends the Mosel language with functionality for stating robust optimization problems and solving them via the Xpress Solvers.
Other solvers of FICO Xpress Optimization (Xpress NonLinear and Xpress Global for solving non-linear problems, and Xpress Kalis for Constraint Programming, CP) are provided with separate manuals and their own sets of examples. Please see the Xpress website for an overview of the available products.
With Mosel it is possible to combine several solvers to formulate hybrid solution approaches for solving difficult application problems. The whitepaper Hybrid MIP/CP solving with Xpress Optimizer and Xpress Kalis, available for download from the Xpress website, gives several examples of hybrid solving with LP/MIP and Constraint Programming.
Below follow a few examples for some of the solvers mentioned above.
QCQP solving with Xpress Optimizer
In Section Recursion we have solved a quadratically constrained problem by a recursion algorithm. One might be tempted to try solving this problem with a QCQP solver. Unfortunately, it does not meet the properties required by the definition of QCQP problems: problems solvable by QCQP must not contain equality constraints with quadratic terms (reformulation as two inequalities will not work either) since such constraints do not satisfy the convexity condition. We shall see below how to solve this problem with the general non-linear solver Xpress NonLinear (using SLP).
Let us therefore take a look at a different problem: the task is to distribute a set of points represented by tuples of x-/y-coordinates on a plane minimizing the total squared distance between all pairs of points. For each point i we are given a target location (CXi,CYi) and the (square of the) maximum allowable distance to this location, the (squared) radius Ri around this location.
In mathematical terms, we have two decision variables xi and yi for the coordinates of every point i. The objective to minimize the total squared distance between all points is expressed by the following sum.
N-1 |
∑ |
i=1 |
N |
∑ |
j=i+1 |
For every point i we have the following quadratic inequality.
The resulting Mosel model (file airport_qp.mos) looks thus.
model "airport" uses "mmxprs", "mmnl" declarations RN: range ! Set of airports R: array(RN) of real ! Square of max. distance to given location CX,CY: array(RN) of real ! Target location for each point x,y: array(RN) of mpvar ! x-/y- coordinates LimDist: array(RN) of nlctr end-declarations initialisations from "airport.dat" CY CX R end-initialisations ! Set bounds on variables forall(i in RN) do -10<=x(i); x(i)<=10 -10<=y(i); y(i)<=10 end-do ! Objective: minimise the total squared distance between all points TotDist:= sum(i,j in RN | i<j) ((x(i)-x(j))^2+(y(i)-y(j))^2) ! Constraints: all points within given distance of their target location forall(i in RN) LimDist(i):= (x(i)-CX(i))^2+(y(i)-CY(i))^2 <= R(i) setparam("XPRS_verbose", true); minimise(TotDist); writeln("Solution: ", getobjval); forall(i in RN) writeln(i, ": ", getsol(x(i)), ", ", getsol(y(i))) end-model
A QCQP matrix can be exported to a text file (option "" for MPS or "l" LP format) through the writeprob function of Xpress Optimizer. That is, you need to add the following lines to your model after the problem definition:
setparam("XPRS_loadnames", true) loadprob(TotDist) writeprob("airport.mat","l")
A graphical representation of the result with mmsvg, obtained with the following lines of Mosel code, is shown in Figure Result graphic in SVG format.
! Set the size of the displayed graph svgsetgraphviewbox(-10,-10,10,10) svgsetgraphscale(20) ! Draw the target locations svgaddgroup("T", "Target area", SVG_SILVER) svgsetstyle(SVG_FILL,SVG_CURRENT) forall(i in RN) svgaddcircle(CX(i), CY(i), sqrt(R(i))) ! Draw the solution points svgaddgroup("S", "Solution", SVG_BLUE) forall(i in RN) svgaddpoint(x(i).sol, y(i).sol) ! Output to file svgsave("airport.svg") ! Update the display svgrefresh svgwaitclose

Figure 21.5: Result graphic in SVG format
Xpress NonLinear and Xpress Global
The following example solves the non-linear financial planning problem from Section Recursion with Xpress NonLinear (using the SLP solver). The definition of constraints is straightforward (nonlinear constraints have the type nlctr with mmxnlp). Other functionality contributed by the module mmxnlp that appears in this model are the setinitval subroutine—used for setting start values for the decision variables— and the overloaded minimize subroutine for loading and solving nonlinear problems. Here, we simply need to become feasible and therefore use 0 as argument to minimize. Xpress used through the module mmxnlp automatically selects a solver among the installed solvers of the Xpress suite (Simplex, Barrier, Global, SLP, or Knitro) depending on the detected problem type. We know that this problem can be solved by recursion and therefore preselect the SLP solver by setting the parameters XPRS_NLPSOLVER and XNLP_SOLVER; without these settings the solver selection will default to Xpress Global if it is available.
model "Recursion (NLP)" uses "mmxnlp" ! Use Xpress NonLinear declarations NT=6 ! Time horizon QUARTERS=1..NT ! Range of time periods M,P,V: array(QUARTERS) of real ! Payments interest: array(QUARTERS) of mpvar ! Interest net: array(QUARTERS) of mpvar ! Net balance: array(QUARTERS) of mpvar ! Balance rate: mpvar ! Interest rate end-declarations M:: [-1000, 0, 0, 0, 0, 0] P:: [206.6, 206.6, 206.6, 206.6, 206.6, 0] V:: [-2.95, 0, 0, 0, 0, 0] setinitval(rate, 0) ! Set initial values for variables forall(t in QUARTERS) setinitval(balance(t), 1) ! net = payments - interest forall(t in QUARTERS) net(t) = (M(t)+P(t)+V(t)) - interest(t) ! Money balance across periods forall(t in QUARTERS) balance(t) = if(t>1, balance(t-1), 0) - net(t) ! Interest rate forall(t in 2..NT) -(365/92)*interest(t) + balance(t-1) * rate = 0 interest(1) = 0 ! Initial interest is zero forall(t in QUARTERS) net(t) is_free forall(t in 1..NT-1) balance(t) is_free balance(NT) = 0 ! Final balance is zero ! setparam("XNLP_VERBOSE",true) ! Uncomment to see detailed output setparam("XPRS_NLPSOLVER", 1) ! Use a local NLP solver setparam("XNLP_SOLVER", 0) ! Select the SLP solver minimize(0) ! Solve the problem (get feasible) ! Print the solution writeln("\nThe interest rate is ", getsol(rate)) write(strfmt("t",5), strfmt(" ",4)) forall(t in QUARTERS) write(strfmt(t,5), strfmt(" ",3)) write("\nBalances ") forall(t in QUARTERS) write(strfmt(getsol(balance(t)),8,2)) write("\nInterest ") forall(t in QUARTERS) write(strfmt(getsol(interest(t)),8,2)) writeln end-model
The results displayed by this model are exactly the same as those we have obtained from the recursion algorithm in Section Recursion.
Xpress Kalis
Constraint Programming is an entirely different method of representing and solving problems compared to the Mathematical Programming approaches we have employed so far in this manual. Consequently, the Mosel module kalis defines its own set of types that behave differently from their Mathematical Programming counterparts, including cpvar, cpfloatvar and cpctr for decision variables and constraints, but also cpbranching for search strategies (a standard constituent of CP models) and aggregate modeling objects such as cptask and cpresource.
Below we show the CP implementation of a binpacking example from the book `Applications of optimization with Xpress-MP' (Section 9.4 `Backing up files'). The problem is to save sixteen files of different sizes onto empty disks of the same fixed capacity minimizing the total number of disks that are used.
Our model formulation remains close to the Mixed Integer Programming formulation and yet shows some specifities of Constraint Programming. Two sets of decision variables are used, savef indicating the choice of disk for file f and usefd the amount of diskspace used by a file on a particular disk. Whereas the variables savef simply take integer values in a specified interval, each of the usefd variables may take only two values, 0 or SIZEf. For every file / disk combination we establish the logical relation
a constraint that cannot be stated in this form in a Mathematical Programming model. A second, so-called global constraint relation is used to state the maximum relation for calculating the number of disks used:
And finally, the constraint limiting the capacity available per disk is a linear inequality that could occur in the same form in a Mathematical Programming model.
This is the complete Mosel CP model for the binpacking problem.
model "D-4 Bin packing (CP)" uses "kalis" declarations ND: integer ! Number of floppy disks FILES = 1..16 ! Set of files DISKS: range ! Set of disks CAP: integer ! Floppy disk size SIZE: array(FILES) of integer ! Size of files to be saved end-declarations initializations from 'd4backup.dat' CAP SIZE end-initializations ! Provide a sufficiently large number of disks ND:= ceil((sum(f in FILES) SIZE(f))/CAP) DISKS:= 1..ND finalize(DISKS) setparam("kalis_default_lb", 0) declarations save: array(FILES) of cpvar ! Disk a file is saved on use: array(FILES,DISKS) of cpvar ! Space used by file on disk diskuse: cpvar ! Number of disks used end-declarations ! Set variable domains forall(f in FILES) setdomain(save(f), DISKS) forall(f in FILES, d in DISKS) setdomain(use(f,d), {0, SIZE(f)}) ! Correspondence between disk choice and space used forall(f in FILES, d in DISKS) equiv(save(f)=d, use(f,d)=SIZE(f)) ! Limit the number of disks used diskuse = maximum(save) ! Capacity limit of disks forall(d in DISKS) sum(f in FILES) use(f,d) <= CAP ! Minimize the total number of disks used if not cp_minimize(diskuse) then writeln("Problem infeasible") end-if ! Solution printing writeln("Number of disks used: ", getsol(diskuse)) forall(d in 1..getsol(diskuse)) do write(d, ":") forall(f in FILES) write( if(getsol(save(f))=d , " "+SIZE(f), "")) writeln(" space used: ", getsol(sum(f in FILES) use(f,d))) end-do end-model
This implementation is one of several possible formulations of this problem as a CP model. An alternative and generally more efficient model being the formulation as a cumulative scheduling problem, where the disks are represented by a single resource of discrete capacity, the files to save correspond to tasks of duration 1 with a resource requirement defined by the file size. The objective in this case is to minimize the duration of the schedule (= number of disks used). The interested reader is refered to the Xpress Kalis Mosel User Guide for a detailed discussion of this problem.
Date and time data types
The module mmsystem of the standard distribution of Mosel defines the types date (calendar day: day, month, and year), time (time of the day in milliseconds), and datetime (combination of the first two) for working with date and time related data in Mosel models. We show here some examples of
- reading and writing dates and times from/to file,
- formatting dates and times,
- using sets of constant dates and times for indexing arrays,
- transformation from/to the underlying numerical representation,
- applying operations (comparison, addition, difference, sorting),
- enumerating dates and times.
Initializing dates and times
The following line prints out the current date and time (using the default format):
writeln("Today: ", date(SYS_NOW), ", current local time: ", time(SYS_NOW), "UTC time: ", gettime(datetime(timestamp)) )
When we wish to read data from a file, the formatting of dates and times needs to be adapted to the format used in the file. For example, consider the following data file (datetime.dat)
Time1: "4pm" Time2: "16h00" Time3: "16:00:00" Date1: "2-20-2002" Date2: "20/02/02" Date3: "20-Feb-2002"
A Mosel model reading in this data file may look thus (file dates.mos).
declarations t: time d: date end-declarations setparam("timefmt", "%h%p") ! h: hours in 1-12, p: am/pm setparam("datefmt", "%m-%d-%y") ! m: month, d: day, y: year initializations from "datetime.dat" t as "Time1" d as "Date1" end-initializations writeln(d, ", ", t) setparam("timefmt", "%Hh%0M") ! H: hours in 0-23, M: minutes setparam("datefmt", "%0d/%0m/%0Y") ! Y: year in 0-99 ! 0: fill spaces with '0' initializations from "datetime.dat" t as "Time2" d as "Date2" end-initializations writeln(d, ", ", t) setparam("timefmt", "%H:%0M:%0S") ! S: seconds setparam("datefmt", "%d-%N-%y") ! N: use month names initializations from "datetime.dat" t as "Time3" d as "Date3" end-initializations writeln(d, ", ", t)
For the encoding of date and time format strings please refer to the documentation of the parameters datefmt and timefmt in the 'Mosel Language Reference Manual'.
Date3 in this example uses a month name, not a number. The default 3-letter abbreviations of month names can be changed (e.g., translated) by redefining the parameter monthnames. For instance, a date written in French, such as
Date4: "20 fevrier 2002"
is read by the following Mosel code:
setparam("datefmt", "%d %N %y") setparam("monthnames", "janvier fevrier mars avril mai juin juillet " + "aout septembre octobre novembre decembre") initializations from "datetime.dat" d as "Date4" end-initializations writeln(d)
In the examples of this section we have used Mosel's standard text format for reading and writing dates and times. These data types can also be used when accessing spreadsheets or databases through Mosel's ODBC connection or the software-specific interfaces for Oracle and MS Excel. The whitepaper Using ODBC and other database interfaces with Mosel documents some examples of accessing date and time data in spreadsheets and databases.
Note: When initializing or constructing dates Mosel does not control whether they correspond to an actual calendar day (e.g., 0-valued or negative day and month counters are accepted). The validity of a date or time can be tested with the function isvalid. For example, the following code extract
d:= date(2000,0,0) writeln(d, " is a valid date: ", if(isvalid(d), "true", "false"))
results in this output:
2000-00-00 is a valid date: false
Dates and times as constants
It is possible to use the types 'date', 'time', 'datetime' as index sets for arrays if the elements of the set are flagged as being constant. The effect of such a declaration as constant is illustrated by the following code snippet (taken from the example file dates.mos). Entities such as someday in the example below that receive a value directly in the declarations block via '=' also are constants, their value can not be changed or re-assigned.
declarations someday=date(2020,3,24) ! A constant date SD: set of date ! Set of date references SDC: set of constant date ! Set of constant date references AD: dynamic array(SDC) of real ! Array indexed by 'date' type end-declarations ! Operations on a set of dates SD:= {date(2020,3,24), date(2020,3,24)+1} writeln("Is someday in SD? ", someday in SD) ! Output: false writeln("Next day in SD? ", someday+1 in SD) ! Output: false SD+= {date(2020,3,24), date(2020,3,24)+1} writeln("SD after addition: ", SD, " size=", SD.size) ! Output: size=4 ! Operations on a set of constant dates SDC:= {date(2020,3,24),date(2020,3,24)+1} writeln("Is someday in SDC? ", someday in SDC) ! Output: true writeln("Next day in SDC? ", someday+1 in SDC) ! Output: true SDC+= {date(2020,3,24), date(2020,3,24)+1} writeln("SDC after addition: ", SDC, " size=", SDC.size) ! Output: size=2
The example shown here only mentions the type 'date', but the constant declaration is also applicable to the types 'time' and 'datetime'.
Conversion to and from numbers
In some cases it might be necessary to use the numerical representation in the place of a date or time. In the following Mosel extract we wish to define an array YEARS that is indexed by a set of dates. In this example we show how to use as index values the numerical representation that is obtained by applying getasnumber to the dates (this function returns an integer, the Julian Day number = number of days elapsed since 1/1/1970; if the argument is a time getasnumber returns the number of milliseconds since midnight). By applying date to the numerical representation it is converted back to the original date.
With this Mosel code
declarations Dates: set of date YEAR: array(NDates: set of integer) of integer end-declarations setparam("datefmt", "") ! Use the default format initializations from "datetime.dat" Dates end-initializations writeln("Dates: ", Dates) forall(dd in Dates) YEAR(getasnumber(dd)):= getyear(dd) writeln("YEAR: ", YEAR) forall(n in NDates) writeln(date(n)) ! Mapping back to original dates
and the following data
Dates: [ "1999-1-21" "2000-2-22" "2002-3-23" "2005-4-24" "2010-5-25"]
we obtain this output:
Dates: {1999-01-21,2000-02-22,2002-03-23,2005-04-24,2010-05-25} YEAR: [(10612,1999),(11009,2000),(11769,2002),(12897,2005),(14754,2010)] 1999-01-21 2000-02-22 2002-03-23 2005-04-24 2010-05-25
Similarly to what is shown here, function getasnumber can be used with 'time' and 'datetime', the backwards conversion being carried out by time or datetime respectively.
Operations and access functions
The following Mosel model extract (dates.mos) shows some operations on dates and times, including sorting lists of dates or times, difference between two dates or times, and addition of constants to obtain an enumeration of dates or times.
declarations t: time d: date now1,now2: datetime DNAMES: array(1..7) of string TList: list of time DList: list of date end-declarations ! Difference between dates writeln("February 2004 had ", date(2004,3,1)-date(2004,2,1), " days.") ! Retrieve the weekday DNAMES:: (1..7)["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] writeln("1st January 2000 was a ", DNAMES(getweekday(date(2000,1,1)))) ! Difference between times now1:= datetime(SYS_NOW) wait(1) ! Delay model execution for 1 second now2:= datetime(SYS_NOW) writeln("Elapsed time: ", now2-now1, "ms") ! Enumeration / addition to 'time' setparam("timefmt", "%.h.%0M%p") t:= time(11,0) forall(i in 1..5) do writeln(t) t+=30*60*1000 ! Add 30 minutes end-do ! Enumeration / addition to 'date' setparam("datefmt", "%.d/%0m/%0Y") d:= date(2005,12,20) forall(i in 1..5) do writeln(d) d+=14 ! Add 14 days end-do ! Sorting lists of dates and times setparam("datefmt", "") ! Revert to default date format DList:= [date(2021,1,1),date(1900,1,2),date(2020,3,24)] writeln("Orig. DL=", DList) qsort(SYS_UP, DList) writeln("Sorted DL=", DList) setparam("timefmt", "") ! Revert to default time format TList:= [time(12,0),time(10,30),time(16,15),time(8,45)] writeln("Orig. TL=", TList) qsort(SYS_UP, TList) writeln("Sorted TL=", TList)
Executing this model produces the following output.
February 2004 had 29 days. 1st January 2000 was a Saturday Elapsed time: 1.006ms 11.00am 11.30am 12.00pm 12.30pm 1.00pm 20/12/05 3/01/06 17/01/06 31/01/06 14/02/06 Orig. DL=[2021-1-1,1900-01-02,2020-03-24] Sorted DL=[1900-01-02,2020-03-24,2021-1-1] Orig. TL=[12:00:00,10:30:00,16:15:00,8:45:00] Sorted TL=[8:45:00,10:30:00,12:00:00,16:15:00]
Text handling and regular expressions
The module mmsystem provides a large set of text handling functionality, including
- the types text, parsectx, and textarea
- text formatting routines (number format, upper/lower case)
- parsing routines
- regular expressions
In the following subsections we show some examples of text handling with Mosel, for a full description of the available functionality please refer to the chapter mmsystem of the 'Mosel Language Reference Manual'.
text vs. string
Although apparently denoting similar concepts, the purpose and usage recommendations for the types string and text in Mosel models are quite distinct: any string defined in a model is added to the model's names dictionary and is only freed at termination of the model run, this is not the case for model objects of the type text. The type string therefore should be used whenever it is a question of identifying objects, so in particular for index sets.
The type text is in general the more appropriate choice for descriptive or editable texts, including reporting or logging messages, and any texts generated via (partial) copies or concatenation. A text object can be altered, allowing for a considerably wider set of operations (such as insertion, deletion) in comparison with strings. Furthermore, with the I/O driver text: a public text object can be used as input or output file in a model (see Section List of I/O drivers).
It is, however, not always possible to draw a clear line between where to use string or text. A number of module subroutines therefore define multiple versions, accepting both, string or text arguments. Note further that if required, Mosel automatically converts from the type string to text, but not the other way round.
Parsing text
In the example below we configure the global parser settings to read real numbers from a text that has fields separated by commas.
declarations values: list of real comma=getchar(",",1) ! ASCII value for "," end-declarations txt:= text(", , 123.4 , 345.6 ,") ! Parsing without context setparam("sys_sepchar", comma) ! Comma as separation character setparam("sys_trim", true) ! Trim blanks around separation character while (nextfield(txt)) do ! Get next field values+= [parsereal(txt)] ! Read a real number from the field writeln("Read up to position ", getparam("sys_endparse")) end-do writeln("Values read: ", values) ! Output: [0,0,123.4,345.6,0]
The same behavior can be achieved with a parser context—here we do not modify any global settings, which has the advantage of preventing possible interactions with other parser settings that may be used elsewhere in our model.
declarations pctx: parsectx values: list of real comma=getchar(",",1) ! ASCII value for "," end-declarations txt:= text(", , 123.4 , 345.6 ,") ! Parsing real numbers with context setsepchar(pctx, comma) ! Comma as separation character settrim(pctx, true) ! Trim blanks around separation character while (nextfield(txt,pctx)) do ! Get next field values+= [parsereal(txt, pctx)] ! Read a real number from the field writeln("Read up to position ", pctx.endparse) end-do writeln("Values read: ", values) ! Output: [0,0,123.4,345.6,0]
When implementing data handling for optimization applications, it is good practice to add error handling to the parsing loop, for example to check whether the fields are formatted as expected:
pctx.endparse:=0 ! Start at the beginning of text pctx.sepchar:=comma ! Comma as separation character pctx.trim:=true ! Trim blanks around separation character while (nextfield(txt,pctx)) do ! Get next field if getchar(txt, pctx.endparse)=comma or pctx.endparse>=txt.size then values+=[0.0] ! The field is empty else r:=parsereal(txt, pctx) ! Read a real number from the field if getsysstat=0 then values+= [r] else writeln("Malformed field contents at position ", pctx.endparse, " (", copytext(txt, pctx.endparse,pctx.endparse+2), ")") end-if end-if writeln("Read up to position ", pctx.endparse) end-do writeln("Values read: ", values) ! Output: [0,0,123.4,345.6,0]
One might also choose to work with multiple parser contexts (e.g. using an 'inner' context pctxi for reading some part of each field from the original text—here an integer number that is read from a string containing a real).
declarations pctx,pctx1: parsectx ivalues: list of integer comma=getchar(",",1) ! ASCII value for "," end-declarations txt:= text(", , 123.4 , 345.6 ,") setsepchar(pctx, comma) ! Comma as separation character settrim(pctx, true) ! Trim blanks around separation character while (nextfield(txt,pctx)) do ! Get next field tt:=parsetext(txt, pctx) ! Get contents of the field pctxi.endparse:=1 ! Reset start to beginning of the text i:=parseint(tt,pctxi) ! Read an integer number from the field if getsysstat=0: ivalues+= [i] writeln("Read up to position ", pctx.endparse) end-do writeln("Values read: ", ivalues) ! Output: [123,345]
Regular expressions
A regular expression (in the following abbreviated to regex) is a sequence of characters that form a search pattern. Regex are used to describe or match a set of strings according to certain syntax rules. Mosel supports the Basic Regular Expressions syntax (BRE) and the Extended Regular Expressions syntax (ERE) of the POSIX standard, the implementation of regular expression matching relies on the TRE library.
Here are some examples of regular expression matching and replacement with some explanations of the meaning of the employed regex—for a complete description of the supported regex syntax the reader is refered to the documentation of the TRE library (see http://laurikari.net/tre), another useful resource are the examples provided on the page en.wikipedia.org/wiki/Regular_expression.
The following example (regex.mos) displays all strings containing 'My' that occur in a text. The first matching statement uses BRE syntax, it displays all strings starting with 'My' irrespective of upper/lower case spelling (option REG_ICASE). The second matching statement uses ERE syntax (option REG_EXTENDED) to retrieve all strings containing 'My' other than at their beginning. We have chosen to retrieve different individual portions of the matching string (specified via the parantheses in the regular expression statement) the positions of which are stored in their order of occurrence into the array m (of type textarea)
declarations m: array(range) of textarea t: text end-declarations t:="MyValue=10,Sometext Mytext MoretextMytext2, MYVAL=1.5 mYtext3" m(0).succ:=1 while (regmatch(t, '\<My\(\w*\)', m(0).succ, REG_ICASE, m)) writeln("Word starting with 'My': ", copytext(t,m(0))) ! Output: MyValue Mytext MYVAL mYtext3 m(0).succ:=1 while (regmatch(t, '\w+((My)(\w*))', m(0).succ, REG_ICASE+REG_EXTENDED, m)) writeln("String containing 'My' (not at beginning): ", copytext(t,m(0)), " (", copytext(t,m(1)), "=", copytext(t,m(2)) , "+", copytext(t,m(3)), ")") ! Output: MoretextMytext2 (Mytext2=My+text2)
The special characters used in the formulation of the regular expressions above have the following meaning: \< marks the beginning of a word, \w denotes alphanumeric or underscore characters, * means 0 or more times and + stands for 1 or more times.
The following Mosel code snippet shows how to replace matching expressions in a text that contains dates with different formats:
t:="date1=20/11/2010,date2=1-Oct-2013,date3=2014-6-30" numr:= regreplace(t, '([[:digit:]]{4})-([01]?[[:digit:]])-([0-3]?[[:digit:]])', '\3/\2/\1', 1, REG_EXTENDED) if numr>0 then writeln(numr, " replacements: ", t) end-if
This is the output produced by the code above:
1 replacements: date1=20/11/2010,date2=1-Oct-2013,date3=30/6/2014
There are alternative ways of stating the same regular expression with BRE or ERE syntax, for example:
numr:= regreplace(t, '\(\d\{4\}\)-\([01]\{0,1\}\d\)-\([0-3]\{0,1\}\d\)', '\3/\2/\1' ) numr:= regreplace(t, '(\d{4})-([01]{0,1}\d)-([0-3]{0,1}\d)', '\3/\2/\1', 1, REG_EXTENDED )
In these replacement statements we have used the following special characters for stating regular expressions: \d or [:digit:] indicates a numerical character, square brackets contain a set of possible character matches, {M,N} means minimum M and maximum N match count and ? stands for 0 times or once.
© 2001-2025 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.