Initializing help system before first use

Language extensions

Topics covered in this chapter:

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 format
initializations 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 line
fopen("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 line
XPRMsetdefstream(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 line
fopen("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 statement
fclose(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 line
fopen("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 to
fopen(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 line
    mosel.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 as
    fopen("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 
    xssh
    Secure version of the xsrv driver to connect to the specified host running the Mosel Remote Launcher xprmsrv through a secure SSH tunnel.
  • 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 format
    initializations 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
MoselUG/transpsvg.png

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, "&#163;") ! 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.

MoselUG/transphtml.png

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 MoselUG/butarchive.png to create the app archive or MoselUG/butdeploy.png to publish the app directly to Insight.

MoselUG/transpinsweb.png

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.

MoselUG/transpinswbvdl.png

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
((xi-xj)2 + (yi-yj)2)

For every point i we have the following quadratic inequality.

(xi-CXi)2 + (yi-CYi)2 ≤ Ri

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
MoselUG/airportsvg.png

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

savef=d ⇔ usefd=SIZEf

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:

diskuse = maximum(savef∈DISKS)

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.

∀d∈DISKS: f∈FILES usefd ≤ CAP

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.