Fusion is an object oriented API specifically designed to build conic optimizaition models in a simple and expressive mannar, using mainstream programming languages: Python
|| C++
|| Java
|| .NET
.
With focus on usability and compactness, it helps the user focus on modeling instead of coding.
Fusion API for C++ requires a C++11 compilant compiler.
Fusion is a result of many years of experience in conic optimization.
It is a dedicated API for users who want to enjoy a simpler experience interfacing with the solver.
Fusion
is designed for fast and clean prototyping of conic problems without suffering excessive performance degradation.
Fusion
is an object-oriented framework for conic-optimization but it is not a general modeling language. The main design principles of Fusion are:
Expressiveness: we try to make it nice! Despite not being a modeling language, Fusion yields readable, easy to maintain code that closely resembles the mathematical formulation of the problem.
Seamlessly multi-language: Fusion code can be ported across C++, Python, Java, .NET and with only minimal adaptations to the syntax of each language.
auto x= M->variable("x", 3, Domain::greaterThan(0.0)); // C++
x = M.variable('x', 3, Domain.greaterThan(0.0)) # Python
Variable x = M.variable("x", 3, Domain.greaterThan(0.0)) // Java
Variable x = M.Variable("x", 3, Domain.GreaterThan(0.0)) // C#
What you write is what MOSEK gets: A Fusion model is fed into the solver with (almost) no additional transformations.
A model built using Fusion is always a conic optimization problem and it is convex by definition.
The main thing about a FUSION MODEL is that it can be specified in a convenient way without explicitly constructing the representation of a conic optimization problem. Instead the user has access to variables
which are used to construct linear operators that apprear in constraints
. The cone types described above are the domains
of those constraints. A fusion model can potentially contain many different building blocks of that kind. To facilitate manipulations with a large number of variables Fusion defines various logical views of parts of the model.
A Fusion
model is represented by the class Model
and created by a simple construction
Model::t M = new Model();
auto _M = finally([&]() {M->dispose();});
The model object is the user’s interface to the optimization problem, used in particular for
variables
, constraints
and objective
,parameters
, registering
for callbacks, performing I/O, obtaining detailed information from the optimizer etc.Almost all elements of the model: variables
, constraints
and the model itself
can be constructed with or without names. If used, the names for each type of object must be unique. Choosing a good naming convention can make the problem more readable when dumped to a file.
Continuous variables
can be scalars
, vectors
or higher-dimensional arrays
.
They are added to the model with the method Model.variable
which returns a representing object of type Variable
. The shape of a variable (number of dimensions and the length in each dimension) has to be specified at creation. Optionally a variable may be created in a restricted domain
(by default variables are unbounded, that is in R
). For instance, to declare a variable x ∈ R + n x \in R^n_+ x∈R+n we could write
auto x = M->variable("x", n, Domain::greaterThan(0.));
A multi-dimentional variable is declared by specifying an array with all dimension sizes. Here is an n ∗ n n*n n∗n variables:
auto x = M->variable(new_array_ptr({n,n}), Domain::unbounded());
The specification of dimensions can also be part of the domain, as in this declaration of a symmetric positive semidefinite variable of dimension n n n:
auto v = M->variable(Domain::inPSDCone(n));
Integer variables are specified with an additional domain modifier. To add an integer variable z ∈ [ 1 , 10 ] z \in [1,10] z∈[1,10] we write:
auto z = M->variable("z", Domain::integral(Domain::inRange(1.,10.)));
The function Domain.binary
is a shorthand for binary variables often appearing in combinatorial problems:
auto y = M->variable("y", Domain::binary)
Integrality requirement can be switched an and off using the methods Variable.makeInteger
and Variable.makeContinuous
.
A domain usually allows to specify the number of objects to be created. For example here is a definition of m m m symmetric positive semidefinite variables of dimention n n n each. The actual variable x
will be of shape m ∗ n ∗ n m*n*n m∗n∗n where each slice with fixed first coordinate is an n ∗ n n*n n∗n PSD:
auto x = M->variable(Domain::inPSDCone(n,m));
The Variable
object provides the primal (Variable.level
) and dual(Variable.dual
) solution values of the variable after optimization, and it enters in the construction of linear expressions involving the variable.
Linear expressions are constructed combining variables
and matrices
by linear operators. The result is an object that represents the linear expression itself. Fustion
only allows for those combinations of operators and arguments that yield linear functions of the variables. Expression have shapes and dimensions in the same fashion as variables. For instance, if x ∈ R n x\in R^n x∈Rn and A ∈ R m ∗ n A\in R^{m*n} A∈Rm∗n, then A x Ax Ax is a vector expression of length m m m. Note, however, that the internal size of A x Ax Ax is m n mn mn, because each entry is a linear combination for which m m m coefficients have to be stored.
Expressions are concrete implementations of the virtual interface Expression
. In typical situations, however, all operations on expressions can be performed using the static methods and factory methods of the class Expr
.
Method | Description |
---|---|
Expr.add | Element-wise addition of two matrices |
Expr.sub | Element-wise subtraction of two matrices |
Expr.mul | Matrix or matrix-scalar multiplication |
Expr.neg | Sign inversion |
Expr.outer | Vector outer-product |
Expr.dot | Dot product |
Expr.sum | Sum over a given dimension |
Expr.mulElm | Element-wise multiplication |
Expr.mulDiag | Sum over the diagonal of a matrix which is the result of a matrix multiplication |
Expr.constTerm | Return a constant term |
Operations on expressions must adhere to the rules of matrix algebra regarding dimension; otherwise a DimensionError exception will be thrown.
Expression can be composed, nested and used as building blocks in new expressions. For instance A x + B y Ax+By Ax+By can be implemented as :
Expr::add(Expr::mul(A,x), Expr::mul(B,y));
For operations involving multiple variables and expressions the users should consider list-based methods. For instance, a clean way to write x + y + z + w x+y+z+w x+y+z+w would be:
Expr::add(new_array_ptr({x, y, z, w}));
A single variable (object of class Variable
) can also be used as an expression. Once constructed, expressions are immutable.
Constraints are declared within an optimization model using the method Model.constraint
. Every constraint in Fusion has the form:
Expression belongs to a Domain.
Objects of type Domain
correspond roughly to the types of convex cones K K K mentioned at the beginning of this section. For instance, the following set of linear constraints :
x 1 + 2 x 2 = 0 + x 2 + x 3 = 0 x 1 = 0 x_1 + 2x_2 = 0 \\ +x_2+x_3=0 \\x_1=0 x1+2x2=0+x2+x3=0x1=0
could be declared as
auto A = new_array_ptr({
{1.0, 2.0, 0.0},
{0.0, 1.0, 1.0},
{1.0, 0.0, 0.0}
});
auto x = M->variable("x", 3, Domain::unbounded());
auto c = M->constraint(Expr::mul(A,x), Domain::equalsTo(0.0));
The scalar domain Domain.equalsTo
consisting of a single point 0 scales up to the dimension of the expression and applies to all its elements. This allows many constraints to be comfortably expressed in a vectorized form.
The Constraint
object provides the dual Constrain.dual
value of the constraint after optimization and the primal value of the constraint expression (Constraint.level
).
The typical domains used to specify constraints are listed below.
Type | Domain |
---|---|
equality | Domain.equalsTo |
i n e q u a l i t y ≤ inequality \leq inequality≤ | Domain.lessThan |
i n e q u a l i t y ≥ inequality \geq inequality≥ | Domain.greaterThan |
t w o s i d e d b o u n d two \; sided \; bound twosidedbound | Domain.inRange |
q u a d r a t i c c o n e quadratic \; cone quadraticcone | Domain.inQCone |
r o t a t e d q u a d r a t i c c o n e rotated \; quadratic \;cone rotatedquadraticcone | Domain.inRotatedQCone |
e x p o n e n t i a l c o n e exponential \; cone exponentialcone | Domain.inPExpCone |
p o w e r c o n e power \; cone powercone | D o m a i n . i n P P o w e r C o n e ( α ) Domain.inPPowerCone(\alpha) Domain.inPPowerCone(α) |
P S D m a t r i x PSD \; matrix PSDmatrix | Domain.inPSDCone |
I n t e g e r s i n d o m a i n D Integers \; in \; domain \; D IntegersindomainD | Domain.integral(D) |
{ 0 , 1 } \{0,1\} {0,1} | Domain.binary |
Having discussed variables
and constraints
we can finish by defining the optimization objective with Model.objective
. The objective function is a scalar expression and the objective sense is specified by the enumeration ObjectiveSense
as either minimize
or maximize
. The typical linear objective function c T x c^Tx cTx can be declared as
M->objective(ObjectiveSense::Minimize, Expr::dot(c,x));
At some point it becomes necessary to specify linear expressions such as A x Ax Ax where A A A is a (large) constant data matrix. Such coefficient matrices can be represented in dense or sparse format. Dense matrices can always be represented using the standard data structures for arrays and two-dimensional arrays built into the language. Alternatively, or when sparsity can be exploited, matrices can be constructed as objects of the class Matrix. This can have some advantages: a more generic code that can be ported across platforms and can be used with both dense and sparse matrices without modifications.
Dense matices are constructed with a variant of the static factory method Matrix.dense
. The values of all entries must be specified all at once and resluting matrix is immutable. For example the matrix
A = [ 1 2 3 4 5 6 7 8 ] A=\left[ \begin{array}{cc} 1&2&3&4 \\ 5&6&7&8 \end{array} \right] A=[15263748]
can be defined with :
auto A = new_array_ptr({
{1,2,3,4},
{5,6,7,8}
});
auto Ad = Matrix::dense(A);
or from a flattened representation:
auto Af = new_array_ptr({1,2,3,4,5,6,7,8});
auto Aff = Matrix::dense(2,4,Af);
Sparse matrices are constructed with a variant of the static factory method Matrix.sparse
. This is both speed -and memory- efficient when the matrix has few nonzero entries. A matrix A A A in sparse format is given by a list of triples ( i , j , v ) (i,j,v) (i,j,v), each defining one entry: A i , j = v A_{i,j}=v Ai,j=v. The order does not matter. The entries not in the list are assumed to be 0 0 0. For example, take the matrix
$$
A=\left[
\begin{matrix}
1.0&0.0&0.0&2.0 \ 0.0&3.0&0.0&4.0
\end{matrix}
\right]
KaTeX parse error: Can't use function '$' in math mode at position 43: …d columns from $̲0$, the corresp…
A={(0,0,1.0),(0,3,2.0),(1,1,3.0),(1,3,4.0)}
$$
The Fusion definition would be :
auto rows = new_array_ptr({0,0,1,1});
auto cols = new_array_ptr({0,3,1,3});
auto values = new_array_ptr({1.0,2.0,3.0,4.0});
auto ms = Matrix::sparse(row->size(), cols->size(), rows, cols, values);
The Matrix class provides more standard constructions such as the identity matrix, a cosntant value matrix, block diagonal matrices etc.
A parameter (Parameter
) is a placeholder for a constant whose value should be specified before the model is optimized. Parameter can have arbitrary shapes, just like variables, and can be used in any place where using a constant, array or matrix of the same shape would be suitable. That means parameters behave like expressions under additive operations and stacking, and can additionally be used in some multiplicative operations where the result is a affine in the optimization variables.
Variable::t x = M->variable("x", 4); // Variable
Parameter::t p = M->parameter("p", 4); // Parameter of shape [4]
Parameter::t q = M->parameter(); // Scalar parameter
M->constraint(Expr::add(Expr::dot(p,x), q), Domain::lessThan(0.0));
// initialize the parameters
p->setValue(new_array_ptr({1.0, 2.0, 3.0, 4.0}));
q->setValue(5.0);
Fusion provides a way to construct logical views of parts of existing expressions or combinations of existing expressions. They are still represented by objects of type Variable or Expression that refer to the original ones. This can be useful in some scenarios:
Exprssion.pick
picks a subset of entries from a variable or expression. Special cases of picking are Exprssion.index
, which picks just one scalar entry and Expression.slice
which picks a slice, that is restricts each dimension to a subinterval. Slicing is a frequently used operation.
Expressions can be reshaped
creating a view with the same number of coordinates arranged in a different way. A particular example of this operation if flattening, which converts any multi-dimensional expression into a one-dimensional vector.
Stacking
refers to the concatenation of expressions to form a new larger one.
Using Fusion one can compatly express sequences of similar constraints.
Between optimizations the user can modify the model in a few ways:
Limitations, memory management, garbage collection, names, multithreading, efficiency, license system.