10 Technical guidelines¶
This section contains some more in-depth technical guidelines for Fusion API for Python, not strictly necessary for basic use of MOSEK.
10.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 maximum number of structural nonzeros in any single expression object is \(2^{31}-1\).
The total size of an item (the product of dimensions) is limited to \(2^{63}-1\).
10.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
.
The Model
supports the Context Manager protocol, so it will be destroyed properly when used in the construction:
with Model() as M:
# Work with the model here
pass;
One can also write
try:
M = Model()
# Work with the model here
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(Model):
def __init__(self):
finished = False
try:
Model.__init__(self)
# other initialization
finished = True
finally:
if not finished:
self.dispose()
10.3 Names¶
All elements of an optimization problem in MOSEK (objective, constraints, variables, etc.) can be given names. Assigning meaningful names to variables and constraints makes it much easier to understand and debug optimization problems dumped to a file. On the other hand, note that assigning names can substantially increase setup time, so it should be avoided in time-critical applications.
The Model
object’s, variables’ and constraints’ constructors provide versions with a string name as an optional parameter.
Names introduced in Fusion are transformed into names in the underlying low-level optimization task, which in turn can be saved to a file. In particular:
a scalar variable with name
var
becomes a variable with namevar[]
,a one- or more-dimensional variable with name
var
becomes a sequence of scalar variables with namesvar[0]
,var[1]
, etc. orvar[0,0]
,var[0,1]
, etc., depending on the shape,the same applies to constraints,
a new variable with name
1.0
may be added.
These are the guidelines. No guarantees are made for the exact form of this transformation.
The user can override the default numbering scheme by providing a list of string labels for some or all axes. For example the following code
itemNames = ["ITEM1", "ITEM2", "ITEM3"]
slotNames = ["SLOT1", "SLOT2"]
x = M.variable("price", [3,2], Domain.unbounded()
.withNamesOnAxis(itemNames,0)
.withNamesOnAxis(slotNames,1))
will lead to the individual entries of variable price
being named as price[ITEM1,SLOT1]
, price[ITEM1,SLOT2]
and so on instead of price[0,0]
, price[0,1]
etc.
Note that file formats impose various restrictions on names, so not all resulting names can be written verbatim to each type of file, and when writing to a file further transformations and character substitutions can be applied, resulting in poor readability. This is particularly true for LP files, so saving Fusion problems in LP format is discouraged. The PTF format is recommended instead. See Sec. 15 (Supported File Formats).
10.4 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.
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. Note also that not all parts of the algorithm can be parallelized, so there are times when CPU utilization is only 1 even if more cores are available.
Determinism
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.
Setting the number of threads
The number of threads the optimizer uses can be changed with the parameter numThreads
.
The parameter numThreads
affects only the optimizer. It may be the case that numpy
is consuming more threads. In most cases this can be limited by setting the environment variable MKL_NUM_THREADS
. See the numpy
documentation for more details.
10.5 Efficiency¶
The following guidelines can help keep the code as efficient as possible.
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
could be implemented in a loop as
ee = Expr.constTerm(k, 0.)
for i in range(n):
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:
ee = Expr.add( [ Expr.mul(AA,xx) for (AA,xx) in zip(A,x)] )
Fusion design naturally promotes this sort of vectorized implementations. See Sec. 6.8 (Vectorization) for more examples.
Parametrize relevant parts of the model
If you intend to reoptimize the same model with changing input data, use a parametrized model and modify it between optimizations by resetting parameter values, see Sec. 6.6 (Parameters). This way the model is constructed only once, and only a few coefficients need to be recomputed each time.
Keep a healthy balance and parametrize only the part of the model you in fact intend to change. For example, using parameters in place of all constants appearing in the model would be an overkill with an adverse effect on efficiency since all coefficients in the problem would still have to be recomputed each time.
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 Sec. 6.7 (Stacking and views). 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.
10.6 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, andthe token is returned when the process exits.
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 parameterlicenseWait
will force MOSEK to wait until a license token becomes available instead of throwing an exception.The default path to the license file can be changed with
Model.putlicensepath
.
10.7 Deployment¶
When redistributing a Python application using the MOSEK Fusion API for Python 10.2.3, the following shared libraries from the MOSEK bin
folder are required:
Linux :
libmosek64
,libmosekxx
,libtbb
,Windows :
mosek64
,mosekxx
,tbb
,svml_dispmd
,OSX :
libmosek64
,libmosekxx
,libtbb
.
Furthermore, the folder python/3/mosek
must be in the PYTHONPATH
.
By default the MOSEK Python API will look for the binary libraries in the MOSEK module directory, i.e. the directory containing __init__.py
. Alternatively, if the binary libraries reside in another directory, the application can pre-load the mosekxx
library from another location before mosek
is imported, e.g. like this
import ctypes ; ctypes.CDLL('my/path/to/mosekxx.dll')