9 Technical guidelines

This section contains some technical guidelines for Fusion users.

For modelling guidelines check one of the following sections:

  • Section 6 for an overview of how to express optimization problems in Fusion.
  • Section 12 for how to address numerical issues in modelling and how to tune the continuous optimizers.
  • Section 13 for how to tune the mixed-integer optimizer.

9.1 Limitations

Fusion imposes some limitations on certain aspects of a model to ensure easier portability:

  • Constraints and variables belong to a single model, and cannot as such be used (e.g. stacked) with objects from other models.
  • Most objects forming a Fusion model are immutable.

The limits on the model size in Fusion are as follows:

  • The maximum number of variable elements is \(2^{31}-1\).
  • The maximum size of a dimension is \(2^{31}-1\).
  • The total size of an item (the product of dimensions) is limited to \(2^{63}-1\).

9.2 Memory management and garbage collection

Users who experience memory leaks using Fusion, especially:

  • memory usage not decreasing after the solver terminates,
  • memory usage increasing when solving a sequence of problems,

should make sure that the Model objects are properly garbage collected. Since each Model object links to a MOSEK task resource in a linked library, it is sometimes the case that the garbage collector is unable to reclaim it automatically. This means that substantial amounts of memory may be leaked. For this reason it is very important to make sure that the Model object is disposed of manually when it is not used any more. The necessary cleanup is performed by the method Model.dispose.

        Model M = new Model(); 
        try 
        { 
           // do something with M 
        } 
        finally 
        { 
          M.dispose(); 
        } 

This construction assures that the Model.dispose method is called when the object goes out of scope, even if an exception occurred. If this approach cannot be used, e.g. if the Model object is returned by a factory function, one should explicitly call the Model.dispose method when the object is no longer used.

Furthermore, if the Model class is extended, it is necessary to dispose of the superclass if the initialization of the derived subclass fails. One can use a construction such as:

class MyModel extends Model 
{ 
  public MyModel() 
    { 
      super(); 
      boolean finished = false; 
      try 
      { 
        //perform initialization here 
        finished = true; 
      } 
      finally 
      { 
        if (! finished) 
          dispose();
      } 
    } 
} 

9.3 Multithreading

Thread safety

Sharing a Model object between threads is safe, as long as it is not accessed from more than one thread at a time. Multiple Model objects can be used in parallel without any problems.

Parallelization

The interior-point and mixed-integer optimizers in MOSEK are parallelized. By default MOSEK will automatically select the number of threads. However, the maximum number of threads allowed can be changed by setting the parameter numThreads and related parameters. This should never exceed the number of cores. See Sections 12 and 13 for more details for the two optimizer types.

The speed-up obtained when using multiple threads is highly problem and hardware dependent. We recommend experimenting with various thread numbers to determine the optimal settings. For small problems using multiple threads may be counter-productive because of the associated overhead.

By default the optimizer is run-to-run deterministic, which means that it will return the same answer each time it is run on the same machine with the same input, the same parameter settings (including number of threads) and no time limits.

9.4 Efficiency

In some cases Fusion must reformulate the problem by adding auxiliary variables and constraints before it can be represented in the optimizer’s internal format. This can cause a significant overhead. The following guidelines can help speed up the process.

Decide between sparse and dense matrices

Deciding whether a matrix should be stored in dense or sparse format is not always trivial. First, there are storage considerations. An \(n\times m\) matrix with \(l\) non zero entries, requires

  • \(\approx n\cdot m\) storage space in dense format,
  • \(\approx 3\cdot l\) storage space in sparse (triplet) format.

Therefore if \(l \ll n\cdot m\), then the sparse format has smaller memory requirements. Especially for very sparse density matrices it will also yield much faster expression transformations. Also, this is the format used ultimately by the underlying optimizer task. However, there are borderline cases in which these advantages may vanish due to overhead spent creating the triplet representation.

Sparsity is a key feature of many optimization models and often occurs naturally. For instance, linear constraints arising from networks or multi-period planning are typically sparse. Fusion does not detect sparsity but leaves to the user the responsibility of choosing the most appropriate storage format.

Reduce the number of Fusion calls and level of nesting

A possible source of performance degradation is an excessive use of nested expressions resulting in a large number of Fusion calls with small model updates, where instead the model could be updated in larger chunks at once. In general, loop-free code and reduction of expression nesting are likely to be more efficient. For example the expression

\[\begin{split}\sum_{i=1}^n A_i x_i\\ x_i \in \real^k, A_i \in \real^{k\times k},\end{split}\]

could be implemented in a loop as

        Expression ee = Expr.constTerm(k, 0.);
        for(int i=0;i<n;i++) 
          ee = Expr.add( ee, Expr.mul(A[i],x[i]) );

A better way is to store the intermediate expressions for \(A_i x_i\) and sum all of them in one step:

        Expression[] prods = new Expression[n];
        for(int i=0;i<n;i++) prods[i] = Expr.mul(A[i],x[i]);
        Expression ee = Expr.add( prods );

Fusion design naturally promotes this sort of vectorized implementations. See Vectorization for more examples.

Do not fetch the whole solution if not necessary

Fetching a solution from a shaped variable produces a flat array of values. This means that some reshaping has to take place and that the user gets all values even if they are potentially interested only in some of them. In this case, it is better to create a slice variable holding the relevant elements and fetch the solution for this subset. See Section 6.6. Fetching the full solution may cause an exception due to memory exhaustion or platform-dependent constraints on array sizes.

Remove names

Variables, constraints and the objective function can be constructed with user-assigned names. While this feature is very useful for debugging and improves the readability of both the code and of problems dumped to files, it also introduces quite some overhead: Fusion must check and make sure that names are unique. For optimal performance it is therefore recommended to not specify names at all.

9.5 The license system

MOSEK is a commercial product that always needs a valid license to work. MOSEK uses a third party license manager to implement license checking. The number of license tokens provided determines the number of optimizations that can be run simultaneously.

By default a license token remains checked out from the first optimization until the end of the MOSEK session, i.e.

  • a license token is checked out when the method Model.solve is called the first time, and
  • the token is returned when the Model class instance is destroyed.

Starting the optimization when no license tokens are available will result in an error.

Default behaviour of the license system can be changed in several ways:

  • Setting the parameter cacheLicense to "off" will force MOSEK to return the license token immediately after the optimization completed.
  • Setting the license wait flag with Model.putlicensewait or with the parameter licenseWait will force MOSEK to wait until a license token becomes available instead of throwing an exception.
  • Additional license checkouts and checkins can be performed manually through the underlying MOSEK task and environment. See Section 8.8.
  • The default path to the license file can be changed with Model.putlicensepath.

9.6 Deployment

When redistributing a Java application using the MOSEK Fusion API for Java 8.1.0.24, the following libraries must be included:

64-bit Linux 64-bit Windows 32-bit Windows 64-bit Mac OS
libmosek64.so.8.1 mosek64_8_1.dll mosek8_1.dll libmosek64.8.1.dylib
libiomp5.so libomp5md.dll libomp5md.dll  
libcilkrts.so.5 cilkrts20.dll cilkrts20.dll libcilkrts.5.dylib
libmosekjava8_1.so mosekjava8_1.dll mosekjava8_1.dll libmosekjava8_1.jnilib
libmosekxx8_1.so mosekxx8_1.dll mosekxx8_1.dll libmosekxx8_1.dylib

By default the Java interface will look for the binaries in the same directory as the .jar file, so they should be placed in the same directory when redistributing.