CENSO Manual
CENSO Manual
June 3, 2015
Contents
1 Introduction 3
1.1 Intent and reader prerequisites . . . . . . . . . . . . . . . . . 3
1.2 Types of problems solved . . . . . . . . . . . . . . . . . . . . 3
1.3 Supported constraint classes . . . . . . . . . . . . . . . . . . . 4
1.4 Mathematical background . . . . . . . . . . . . . . . . . . . . 6
1.4.1 The epigraph form . . . . . . . . . . . . . . . . . . . . 6
1.4.2 Convexity and convex relaxations . . . . . . . . . . . . 7
1.4.3 The B-spline . . . . . . . . . . . . . . . . . . . . . . . 8
1.4.4 Global optimization and the Branch-and-Bound algo-
rithm . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.5 Availability . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.6 Prerequisites . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.6.1 Ipopt . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.6.2 Eigen . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2 Installation 10
2.1 Getting system packages . . . . . . . . . . . . . . . . . . . . . 10
2.2 Getting the code . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3 External code . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.4 Compiling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
5 Solvers 18
5.1 Ipopt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
5.2 CENSO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
5.2.1 Basic algorithm parameters . . . . . . . . . . . . . . . 18
5.2.2 Branch-and-Bound specic parameters . . . . . . . . . 19
5.3 Bonmin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1
6 Example problems 22
6.1 Testing framework and the TestProblem class . . . . . . . . . 22
6.2 Farming problem . . . . . . . . . . . . . . . . . . . . . . . . . 23
6.2.1 Linear programming case . . . . . . . . . . . . . . . . 23
6.2.2 Quadratic programming case . . . . . . . . . . . . . . 26
6.2.3 Integer variables . . . . . . . . . . . . . . . . . . . . . 30
6.3 Network ow problem . . . . . . . . . . . . . . . . . . . . . . 33
6.3.1 Simple maximum ow case . . . . . . . . . . . . . . . 33
6.3.2 Maximum ow with routing . . . . . . . . . . . . . . . 37
6.3.3 Pressure driven ow case - Linear ow model (INCOM-
PLETE) . . . . . . . . . . . . . . . . . . . . . . . . . . 42
6.4 The Six-Hump Camelback function . . . . . . . . . . . . . . . 44
6.4.1 Formulating the optimization problem . . . . . . . . . 45
6.4.2 Domain bounds, starting point, objective function and
constraint set. . . . . . . . . . . . . . . . . . . . . . . . 45
6.4.3 Local optimization using the ConstraintPolynomial
class . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
6.4.4 Local optimization using a custom constraint class . . 49
6.4.5 Global optimization using Alpha-Branch-and-Bound . 55
6.4.6 Global optimization using a B-spline approximation . 69
2
1 Introduction
1.1 Intent and reader prerequisites
This manual intends to give the reader a quick introduction to the CENSO
framework, so (s)he will be able set up and solve optimization problems.
It will focus on the practical aspects rather than the theory behind the
concepts used in the solver algorithm. Therefore, the reader should at least
be acquainted with the following topics within the eld of optimization:
• Mathematical formulation of mixed-integer nonlinear programming (MINLP)
optimization problems.
3
• Equation (1b) shows the constraints of the MINLP. Each ci : Rnc ×
Zni 7→ R is a constraint function, cL
i ∈ (R ∪ {−∞}) is the lower bound
for the constraint function and ci ∈ (R ∪ {∞}) is the upper bound
U
assumptions are made about the constraint functions; they can be lin-
ear or nonlinear, convex or nonconvex. However, to ensure predictable
solver behaviour, they should be twice continuously dierentiable.
cL 2 U
i ≤ ax0 + bx0 + c − x1 ≤ ci , (4)
where a, b and c are constants, and x0 and x1 are any two optimization
variables.
4
• Bilinear constraints on the form
cL U
i ≤ ax0 x1 − x2 ≤ ci , (5)
cL
i ≤ ae
bx0
− x1 ≤ cU
i , (6)
where a and b are constants, and x0 and x1 are any two optimization
variables.
• Polynomial constraints on the form
cL U
i ≤ p(x̃) ≤ ci . (7)
cL U
i ≤ a sin(bx0 ) − x1 ≤ ci , (9)
where a and b are constants, and x0 and x1 are any two optimization
variables.
Constraints which do not t into any of the classes above, can be imple-
mented in one of two ways:
• Creating a new constraint class which matches the constraint exactly.
• Sampling the constraint in a grid and create a B-spline approximation
of the constraint (see section 1.4.3 for details).
5
1.4 Mathematical background
This section discusses a few of the mathematical concepts needed to better
understand how CENSO works. The epigraph form is a transformation of
an optimization problem to a form with a linear objective, which we need
in CENSO. Convexity and convex relaxations are important topics in op-
timization theory in general, and in Branch-and-Bound algorithms such as
CENSO, good convex relaxations are essential for a global solution to be
found. The B-spline is a powerful approximation and interpolation tech-
nique that can be used in CENSO to approximate nonlinear functions with
piecewise polynomials. Also, we can easily nd good convex relaxations of
B-splines, which is a very useful property. We also introduce global opti-
mization and the Branch-and-Bound algorithm.
minimize t, (10a)
subject to f (x) − t ≤ 0, (10b)
cL
i ≤ ci (x) ≤ cU
i , i = {1, . . . , m} (10c)
L
x ≤x≤x , U
(10d)
That is, we want to nd the smallest t that lies on or above the graph space of
f (x). This is illustrated in Figure 1. We now have a new objective function
which is linear (and convex). This conversion is always possible, meaning we
can assume that we only have to deal with nonconvexity and nonlinearity in
the constraints. From Figure 1 we see that (x∗ , t∗ ) is optimal for (10) if and
only if x∗ is optimal for (1) and t = f (x).
6
(a) Standard form (b) Epigraph form. Note that (10b) con-
strains feasible points (x, t) to lie on or
above the graph space of f (x), which we
denote epi f .
• The objective function f (x) must be convex. This is always the case
in (10), since the objective function (10a) is linear.
7
(a) A non-convex function. The line (b) A convex function. No matter
joining(x1 , f (x1 )) and (x2 , f (x2 )) lies where we choose to place x1 , x2 and
below the graph of f , violating the x3 , the lines joining the points lie
convexity condition (11). This is in- above f .
dicated in red. The green line joining
(x2 , f (x2 )) and (x3 , f (x3 )) lies above
f , meaning f is convex in this inter-
val.
Linear functions are convex, because all points on a line joining two
points on the function would coincide with the function itself. Functions with
positive curvature in the entire function domain (positive denite Hessian)
are convex.
Convex sets.
Convex hulls.
Convex relaxations.
8
1.6.1 Ipopt
Ipopt is the default solver used in CENSO for solving optimization problems
locally. Ipopt uses an interior point algorithm to nd local solutions of (1).
For more information on Ipopt and installation instructions, the reader is
referred to the Ipopt home page. Details about the algorithm itself can be
found in [6].
1.6.2 Eigen
Eigen is a C++ template library for linear algebra. It includes functionality
for Matlab-like matrix and vector operations, solvers and other related algo-
rithms such as matrix decompositions. For more information on Eigen and
installation instructions, the reader is referred to the Eigen web site [4].
9
2 Installation
2.1 Getting system packages
2.2 Getting the code
2.3 External code
2.4 Compiling
10
3 Using CENSO through C++/Qt
CENSO is written in C++ 11, using the Qt IDE (Qt Creator), which is a
powerful and widely used IDE. Although the Qt framework oers a vast suite
of functions, these are not used in CENSO for the sake of easy portability.
11
In addition to the folders described above, some additional les deserve
some explanation:
12
4 Formulation of an optimization problem
This section will introduce the C++ classes of the optimization framework.
An optimization problem is made up of one objective object, one constraint
object, three double vectors (variable lower bound, upper bound and solver
starting point). Branch-and-Bound solvers will in addition to these need two
int vectors, one for variable types (continuous, binary or integer) and one for
the indices of variables that can be divided in the branching procedure.
// Eigen matrices
typedef Eigen :: MatrixXd DenseMatrix ;
typedef Eigen :: SparseMatrix < double > SparseMatrix ; // declares a
column - major sparse matrix type of double
0 ≤ x0 ≤ 1
0 ≤ x1 ≤ 3
−∞ ≤ x2 ≤ ∞
13
4.4 Variable types
Variable types are represented as stl vectors of type int. The types are de-
clared as an enum. The available types are BINARY, INTEGER and CONTINUOUS.
The code snippet below shows how to create a vector of variable types.
std :: vector < int > variable_types { BINARY , INTEGER , CONTINUOUS
};
evaluates the objective Hessian at x and stores the value in ddx. The Hessian
is stored in the format indicated by
void structureHessian ( std :: vector < int >& iRow , std :: vector <
int >& jCol )
where iRow and jCol indicates the positions in the Hessian matrix.
void augmentDomain ( int dim )
14
will increase the dimension of the domain without altering the objective
function itself (this is only to allow solver to introduce additional variables
for instance when creating convex relaxations).
As an example; to create the linear objective function f (x) = cT x with
c = [0, 0, 1] we must rst create the vector cT . It is represented by a Eigen
T
Currently only linear objectives are avaiable. The global branch and
bound solver assumes a convex objective.
evaluates the constraint Jacobian at x and stores the value in dx using the
structure given by
void structureJacobian ( std :: vector < int >& iRow , std :: vector <
int >& jCol ) .
evaluates the constraint Hessian at x and stores the value in ddx using the
structure given by
void structureHessian ( std :: vector < int >& eqnr , std :: vector <
int >& iRow , std :: vector < int >& jCol )
void setDomainBounds ( std :: vector < double > lb , std :: vector <
double > ub )
chages the domain bounds. The new bounds must be a subset of the current
constraint domain.
15
The constraint composite is a special constraint object that contains a
list of other constraint objects. It is based on the composite pattern. The
composite has a add function that accepts a constraint object along with
a vector of indexes that indicates which variables that are related to the
constraint.
the following code snippet illustrates how to make a cubic B-spline con-
straint from a data table.
InterpolationTable data = ...
int splineDegree = 3;
int equality = true ;
ConstraintPtr cbspline ( new ConstraintBspline ( data , 3 , equality )
);
16
z0 , variable_types , branching_variables ) ;
int returnStatus = ip . optimize () ;
17
5 Solvers
5.1 Ipopt
Ipopt solves convex NLPs and LPs. Refer to the Ipopt documentation.
5.2 CENSO
CENSO solves convex MINLPs and non-convex MINLPs with convex relax-
ations available.
18
5.2.2 Branch-and-Bound specic parameters
This section describes a few more specialized parameters which can be used
to adjust the behaviour of the Branch-and-Bound algorithm. A summary of
these parameters and their corresponding variables is found in table 7. These
variables are all members in the BranchAndBound class. If adjustments are
to be made, they must be made in the instance of BranchAndBound used for
optimization.
• Node processing policy. This parameter decides the policy for node
processing. The available options are shown in table 3.
19
• Branching rules for integer variables. This parameter decides
the rules for branching on integer varianles. The available options are
shown in table 4.
20
Variable name: branchingRuleContinuous
BINARY_INTEGER_CONTINUOUS Starts with branching on all binary variables. When
nished with the binary variables, the algorithm
branches on the integer variables. Finally, contin-
uous variables are branched on.
RANDOM_MIX Selects a random mix of binary, integer and contin-
uous variables for branching.
5.3 Bonmin
Bonmin solves convex MINLPs. Refer to the Bonmin documentation.
21
6 Example problems
6.1 Testing framework and the TestProblem class
The TestProblem class is a framework for creating test problems. The code
that implements the test problem itself is in the solveProblem() function
of each class. The test problems described in the following section are sum-
marized in table 8. Each of these classes inherit from TestProblem.
22
6.2 Farming problem
In this section we will look at a simple optimization problem taken from the
lecture notes of the NTNU course TTK4135 Optimization and Control. We
will look at three cases:
Objective function. The farmer wants to maximize his prots, that is,
he wants to
maximize 7000x0 + 6000x1 .
We want a minimization problem, so we negate the objective function and
write it on standard LP form:
x0
minimize −7000 −6000 .
| {z } x1
v>
| {z }
x
23
We want a constraint on the form Ax ≤ b, so we write
4000 3000 x0 100000
≤
60 80 x1 2000
| {z } | {z } | {z }
A x b
25 Feasible area
20
x1 (Bananas [tonnes])
*
15 x =(14.2857,14.2857)
10
z0
0
0 5 10 15 20 25
x0 (Apples [tonnes])
24
Setting up and solving the problem. We set up a new TestProblem
class called FarmingLP. The code below is implemented in the solveProblem()
function to set up and solve the farming LP.
1 void FarmingLP :: solveProblem ()
2 {
3 // Variable bounds
4 std :: vector < double > lb {0 , 0};
5 std :: vector < double > ub { INF , INF };
6 // Starting point
7 std :: vector < double > z0 {0 , 0};
8 // Objective function
9 DenseMatrix v (1 , 2) ;
10 v << -7000 , -6000;
11 ObjectivePtr objective ( new ObjectiveLinear ( v ) ) ;
12 // Constraints
13 DenseMatrix A (2 , 2) ;
14 A << 4000 , 3000 ,
15 60 , 80;
16 DenseVector b (2) ;
17 b << 100000 ,
18 2000;
19 ConstraintPtr cLinear ( new ConstraintLinear (A , b , false ) ) ;
20 // Constraint composite
21 ConstraintCompositePtr constraints ( new ConstraintComposite
(2 , lb , ub ) ) ;
22 constraints - > add ( cLinear );
23 // Optimize
24 OptimizerIpopt optIpopt ( objective , constraints , z0 ) ;
25 int status = optIpopt . optimize () ;
26 fopt_found = optIpopt . getObjectiveValue () ;
27 zopt_found = optIpopt . getOptimalSolution () ;
28 cout << " Optimal solution : f *= " << fopt_found << endl ;
29 cout << " Optimal point : x *=( " << zopt_found . at (0) << " ," <<
zopt_found . at (1) << " ) " << endl ;
30 }
Lines 4-7 declare and ll STL vectors with the lower variable bounds, upper
variable bounds and starting point. Lines 8-11 dene the objective function.
Lines 12-19 dene the linear constraints. Lines 20-22 create a constraint
composite and inserts the linear constraint. Lines 23-25 creates an Ipopt
optimizer object and solves the problem. Lines 26-29 extracts and prints
information about the solution. When used in the TestProblem framework,
this gives us the printout
Running Farming LP problem...
******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
Ipopt is released as open source code under the Eclipse Public License (EPL).
For more information visit http://projects.coin-or.org/Ipopt
******************************************************************************
25
Optimal solution: f*=-185714
Optimal point: x*=(14.2857,14.2857)
Farming LP problem successfully solved in 0 (ms)
Press <RETURN> to close this window...
We see that the correct optimal solution is found, as expected. The maxi-
mum prot is 185714.
26
which we can write as
200 0 0
0 ≤ x> 0 140 0 x + −7000 −4000 1 x ≤ 0,
0 0 0 | {z }
| {z } q>
P
that is, we can use the ConstraintQuadratic class to dene this constraint.
0 ≤ x0 ≤ ∞
0 ≤ x1 ≤ ∞
−∞ ≤ x2 ≤ ∞
27
25 Feasible area
20
x1 (Bananas [tonnes])
15
x*=(15.7178,12.3762,88676)
10
z
0
0
0 5 10 15 20 25
x0 (Apples [tonnes])
28
20 // Linear constraint variable mapping
21 std :: vector < int > varMapLinear {0 , 1};
22 // Quadratic constraint
23 DenseMatrix P (3 , 3) ;
24 P << 200 , 0, 0,
25 0 , 140 , 0 ,
26 0, 0 , 0;
27 DenseMatrix q (3 , 1) ;
28 q << -7000 ,
29 -4000 ,
30 1;
31 ConstraintPtr cQuadratic ( new ConstraintQuadratic (P , q , 0 ,
0 , 0) ) ;
32 // Constraint composite
33 ConstraintCompositePtr constraints ( new ConstraintComposite
(3 , lb , ub ) ) ;
34 constraints - > add ( cLinear , varMapLinear ) ;
35 constraints - > add ( cQuadratic ) ;
36 // Optimize
37 OptimizerIpopt optIpopt ( objective , constraints , z0 ) ;
38 int status = optIpopt . optimize () ;
39 fopt_found = optIpopt . getObjectiveValue () ;
40 zopt_found = optIpopt . getOptimalSolution () ;
41 cout << " Optimal solution : f *= " << fopt_found << endl ;
42 cout << " Optimal point : x *=( " << zopt_found . at (0) << " ," <<
zopt_found . at (1) << " ," << zopt_found . at (2) << " ) " <<
endl ;
43 }
Lines 3-7 declare and ll STL vectors with the lower variable bounds, upper
variable bounds and starting point. Lines 8-11 dene the objective func-
tion. Lines 12-19 dene the linear constraint. Line 21 denes an STL vector
of integers which denes the mapping between the variables in the linear
constraint (which has two variables) and the variables in the constraint com-
posite (which has three variables). Lines 22-31 dene the quadratic con-
straint. Lines 32-25 create a constraint composite and inserts the linear and
quadratic constraints. Lines 36-38 creates an Ipopt optimizer object and
solves the problem. Lines 39-42 extracts and prints information about the
solution. When used in the TestProblem framework, this gives us the print-
out
Running Farming QP problem...
******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
Ipopt is released as open source code under the Eclipse Public License (EPL).
For more information visit http://projects.coin-or.org/Ipopt
******************************************************************************
29
Optimal point: x*=(15.7178,12.3762,88675.7)
Farming QP problem successfully solved in 10 (ms)
Press <RETURN> to close this window...
We see that the correct optimal solution is found, as expected. The maxi-
mum prot is 88675.7.
25 Feasible points
20
x1 (Bananas [tonnes])
15
x*=(16,12,88640)
10
z0
0
0 5 10 15 20 25
x (Apples [tonnes])
0
30
4 std :: vector < double > lb {0 , 0 , - INF };
5 std :: vector < double > ub {30 , 30 , INF };
6 // Starting point
7 std :: vector < double > z0 {0 , 0 , 0};
8 // Objective function
9 DenseMatrix v (1 , 3) ;
10 v << 0, 0 , -1;
11 ObjectivePtr objective ( new ObjectiveLinear ( v ) ) ;
12 // Linear constraint
13 DenseMatrix A (2 , 2) ;
14 A << 4000 , 3000 ,
15 60 , 80;
16 DenseVector b (2) ;
17 b << 100000 ,
18 2000;
19 ConstraintPtr cLinear ( new ConstraintLinear (A , b , false ) ) ;
20 // Linear constraint variable mapping
21 std :: vector < int > varMapLinear ;
22 varMapLinear . push_back (0) ;
23 varMapLinear . push_back (1) ;
24 // Quadratic constraint
25 DenseMatrix P (3 , 3) ;
26 P << 200 , 0, 0,
27 0 , 140 , 0 ,
28 0, 0 , 0;
29 DenseMatrix q (3 , 1) ;
30 q << -7000 ,
31 -4000 ,
32 1;
33 ConstraintPtr cQuadratic ( new ConstraintQuadratic (P , q , 0 ,
0 , 0) ) ;
34 // Constraint composite
35 ConstraintCompositePtr constraints ( new ConstraintComposite
(3 , lb , ub ) ) ;
36 constraints - > add ( cLinear , varMapLinear ) ;
37 constraints - > add ( cQuadratic ) ;
38 // Variable types
39 std :: vector < int > variable_types ;
40 variable_types . push_back ( INTEGER ) ; // x0
41 variable_types . push_back ( INTEGER ) ; // x1
42 variable_types . push_back ( CONTINUOUS ) ; // x2
43 // Branching variables
44 std :: vector < int > branching_variables ;
45 branching_variables . push_back (0) ; // x0
46 branching_variables . push_back (1) ; // x1
47 // Optimize
48 BranchAndBound bnb ( objective , constraints , z0 ,
variable_types , branching_variables ) ;
49 int status = bnb . optimize () ;
50 fopt_found = bnb . getObjectiveValue () ;
51 zopt_found = bnb . getOptimalSolution () ;
52 }
31
• In line 5, the upper bounds of x0 and x1 are set to 30 instead of ∞.
This is because we are branching on these variables, so we must dene
an upper bound so that the Branch-and-Bound algorithm is able to
calculate where to split the variable when branching. 30 is a reasonable
choice as an upper bound, because there is not room to grow 30 tons
of either fruit.
• Lines 38-42 dene the variable types. Here, we have dened that x0
and x1 are integer variables, and x2 (the prot function) is continuous.
When used in the TestProblem framework, this gives us the following out-
put:
Branch and Bound tree search finished after 19 iterations, using 0 sec.
Global solution upper bound = -88640
Global solution lower bound = -88640
Optimality gap = 0 <= 0.001 (epsilon)
Optimal point x* = ( 16, 12, 8.864e+04)
32
6.3 Network ow problem
Now we will look at some cases of network ow. Throughout this section we
will look at a simple ow network with one source vertex, one sink vertex,
four internal vertices and ten edges. The ow network is illustrated in g. 7.
We denote the vertices vi , i = {0 . . . 5} and the edges ej , j = {0 . . . 9}. For
context, we could say the ow network represents a routing network where
the edges represent pipelines, and the vertices represent connection points
between the pipelines.
Figure 7: Flow network. v0 is the source node and v5 is the sink node.
33
Incidence matrix. To formulate the optimization problem, we will make
use of an incidence matrix A = {ai,j }. The rows of the incidence matrix
represent vertices, and the columns represent edges. If edge j leaves vertex
i, then ai,j = −1. If edge j enters vertex i, then ai,j = 1. The incidence
matrix for our ow network is shown below.
e0 e1 e2 e3 e4 e5 e6 e7 e8 e9
−1 −1 0
v0 0 0 0 0 0 0 0
v1
1 0 −1 −1 −1 0 0 0 0 0
v2
0 1 1 0 0 −1 −1 0 0 0 =A
v3
0 0 0 1 0 1 0 −1 −1 0
v4 0 0 0 0 1 0 1 1 0 −1
v5 0 0 0 0 0 0 0 0 1 1
Note that this incidence matrix could be slightly dierent since our ow net-
work is undirected (liquid can ow both ways through a pipeline). However,
here we have assumed that the default ow direction is right → left and up
→ down.
x0
x1
x2
x3
x4
minimize 0 0 0 0 0 0 0 0 −1 −1 .
}x5
| {z
x6
v >
x
7
x
8
x9
| {z }
x
Note that we could just as well have maximized the ow out of the source
vertex, or the ow across any cut in the network.
34
that for each vertex (except the source and the sink), the sum of inows
must equal the sum of outows:
For vertex v1 : x0 − x2 − x3 − x4 = 0,
For vertex v2 : x1 + x2 − x5 − x6 = 0,
For vertex v3 : x3 + x5 − x7 − x8 = 0,
For vertex v4 : x4 + x6 + x7 − x9 = 0.
To write this compactly, we dene the matrix Aint , which is the incidence
matrix A with the top and bottom rows removed (so that it represents only
the internal nodes):
1 0 −1 −1 −1 0 0 0 0 0
0 1 1 0 0 −1 −1 0 0 0
Aint = .
0 0 0 1 0 1 0 −1 −1 0
0 0 0 0 1 0 1 1 0 −1
Now we can state the mass balance constraint as a linear system of equations
Aint x = 0. The source and sink vertices are assumed to have innite capacity,
so these are not represented in any constraints.
Variable bounds. Since the optimization variables are the ows through
the edges, we can represent the ow capacity constraints as bounds on the
optimization variables:
x ≤ x ≤ x.
35
12 A << -1 , -1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
13 1 , 0 , -1 , -1 , -1 , 0 , 0 , 0 , 0 , 0 ,
14 0 , 1 , 1 , 0 , 0 , -1 , -1 , 0 , 0 , 0 ,
15 0 , 0 , 0 , 1 , 0 , 1 , 0 , -1 , -1 , 0 ,
16 0 , 0 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , -1 ,
17 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1;
18 // Incidence matrix for internal nodes
19 DenseMatrix A_int = A . block (1 ,0 ,4 ,10) ;
20 // Objective function
21 DenseMatrix v (1 , 10) ;
22 v << 0, 0 , 0 , 0 , 0 , 0 , 0 , 0 , -1 , -1;
23 ObjectivePtr objective ( new ObjectiveLinear ( v ) ) ;
24 // Mass balance constraint
25 DenseVector zeros ; zeros . setZero (4 ,1) ;
26 ConstraintPtr cMassBalance ( new ConstraintLinear ( A_int ,
zeros , true ) ) ;
27 // Constraint composite ( with flow capacity as bounds )
28 ConstraintCompositePtr constraints ( new ConstraintComposite
(10 , lb , ub ) ) ;
29 constraints - > add ( cMassBalance ) ;
30 // Optimize
31 OptimizerIpopt optIpopt ( objective , constraints , z0 ) ;
32 int status = optIpopt . optimize () ;
33 fopt_found = optIpopt . getObjectiveValue () ;
34 zopt_found = optIpopt . getOptimalSolution () ;
35 cout << " Optimal solution : f *= " << fopt_found << endl ;
36 cout << " Optimal flows : " << endl ;
37 for ( int i = 0; i < 10; i ++) cout << " Edge " << i << " : "
<< zopt_found . at ( i ) << endl ;
38 }
Line 4 denes the starting point, which we have chosen to be zero ow. Lines
6-9 dene the variable bounds, which are also the edge capacities. Lines 10-
19 dene the incidence matrix and the internal node incidence matrix. Lines
20-23 dene the objective function. Lines 24-26 dene the mass balance con-
straint, and lines 27-29 dene the constraint composite and adds the mass
balance constraint. Lines 30-37 solves the problem and prints information
about the solution. When used in the TestProblem framework, this gives us
the following output:
Running Maximum Flow problem...
******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
Ipopt is released as open source code under the Eclipse Public License (EPL).
For more information visit http://projects.coin-or.org/Ipopt
******************************************************************************
36
Edge 1: 5.63704
Edge 2: 2.36296
Edge 3: 5
Edge 4: 3
Edge 5: 2
Edge 6: 6
Edge 7: 0.196329
Edge 8: 6.80367
Edge 9: 9.19633
Maximum Flow problem successfully solved in 0 (ms)
Press <RETURN> to close this window...
We see that the four edges across the middle of the network are at full capac-
ity, and it is not possible to increase the ow across this cut further, meaning
this solution must be optimal (however, it is not the only optimal solution).
We can accomplish this by collapsing the variable bounds based on the value
of the binary variables. Consider the following:
bj xj ≤ xj ≤ bj xj .
37
bounds. We can split this into two equations and write it on matrix form:
bj xj − xj ≤ 0 −1 xj xj
⇒ ≤ 0. (15)
xj − bj xj ≤ 0 1 −xj bj
b3 + b4 + b5 + b6 = 2. (16)
Applying (15) to all four routing options and combining this with (16), we
get the linear inequality constraint
−1 0 0 0 x3 0 0 0 0
0 −1 0
0 0 x4 0 0 x3
0
0
0 −1 0 0 0 x 5 0 4 0
x
0
0 0 −1 0 0 0 x6 x5
0
1 0 0 0 −x3 0 0 0 x6 0
≤
0
1 0 0 0 −x4 0 0 b3 0
0
0 1 0 0 0 −x5 0 b4 0
0
0 0 1 0 0 0 −x6 b5
0
0 0 0 0 1 1 1 1 b6 2
0 0 0 0 −1 −1 −1 −1 | {z } −2
| {z } x̃ | {z }
AR bR
38
9 DenseMatrix A (6 , 10) ;
10 A << -1 , -1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
11 1 , 0 , -1 , -1 , -1 , 0 , 0 , 0 , 0 , 0 ,
12 0 , 1 , 1 , 0 , 0 , -1 , -1 , 0 , 0 , 0 ,
13 0 , 0 , 0 , 1 , 0 , 1 , 0 , -1 , -1 , 0 ,
14 0 , 0 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , -1 ,
15 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1;
16 // Incidence matrix for internal nodes
17 DenseMatrix A_int = A . block (1 ,0 ,4 ,10) ;
18 // Objective function
19 DenseMatrix v (1 , 14) ;
20 v << 0, 0 , 0 , 0 , 0 , 0 , 0 , 0 , -1 , -1 , 0 , 0 , 0 , 0;
21 ObjectivePtr objective ( new ObjectiveLinear ( v ) ) ;
22 // Mass balance constraints
23 std :: vector < int > massBalanceVars {0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9};
24 VecD zeros4 ; zeros4 . setZero (4 , 1) ;
25 ConstraintPtr cMassBalance ( new ConstraintLinear ( A_int ,
zeros4 , true ) ) ;
26 // Routing constraints
27 DenseMatrix AR (10 , 8) ;
28 AR << -1 , 0 , 0 , 0 , lb . at (3) , 0, 0, 0,
29 0 , -1 , 0 , 0 , 0 , lb . at (4) , 0, 0,
30 0 , 0 , -1 , 0 , 0, 0 , lb . at (5) , 0,
31 0 , 0 , 0 , -1 , 0, 0, 0 , lb . at (6) ,
32 1 , 0 , 0, 0 , - ub . at (3) , 0, 0, 0,
33 0 , 1 , 0, 0 , 0 , - ub . at (4) , 0, 0,
34 0 , 0 , 1, 0 , 0, 0 , - ub . at (5) , 0,
35 0 , 0 , 0, 1 , 0, 0, 0 , - ub . at (6) ,
36 0 , 0 , 0, 0 , 1, 1, 1, 1,
37 0 , 0 , 0, 0 , -1 , -1 , -1 , -1;
38 VecD bR ; bR . setZero (10 , 1) ;
39 bR (8) = 2;
40 bR (9) = -2;
41 // Variables x3 - x6 and b3 - b6
42 std :: vector < int > routingVars {3 ,4 ,5 ,6 ,10 ,11 ,12 ,13};
43 ConstraintPtr cRouting ( new ConstraintLinear ( AR , bR , false ) ) ;
44 // Constraint composite ( with flow capacity as bounds )
45 ConstraintCompositePtr constraints ( new ConstraintComposite
(14 , lb , ub ) ) ;
46 constraints - > add ( cMassBalance , massBalanceVars );
47 constraints - > add ( cRouting , routingVars ) ;
48 // Variable types
49 std :: vector < int > variable_types ;
50 for ( int i = 0; i < 10; i ++) variable_types . push_back (
CONTINUOUS ) ;
51 for ( int i = 0; i < 4; i ++) variable_types . push_back (
BINARY ) ;
52 // Branching variables ( routing options )
53 std :: vector < int > branching_variables {10 , 11 , 12 , 13};
54 // Optimize
55 BranchAndBound bnb ( objective , constraints , z0 ,
variable_types , branching_variables ) ;
56 int status = bnb . optimize () ;
57 fopt_found = bnb . getObjectiveValue () ;
39
58 zopt_found = bnb . getOptimalSolution () ;
59 cout << " Optimal solution : f *= " << fopt_found << endl ;
60 cout << " Optimal flows : " << endl ;
61 for ( int i = 0; i < 10; i ++) cout << " Edge " << i << " : "
<< zopt_found . at ( i ) << endl ;
62 cout << " Routing decision : " << endl ;
63 for ( int i = 10; i < 14; i ++) cout << " Edge " << i -7 << " :
" << zopt_found . at ( i ) << endl ;
64 }
The dierences between this and the previous example is the following:
• Lines 3-7: Starting point and bound vectors are augmented with four
elements to facilitate the new routing variables.
• Line 23: We have dened variable mapping vector for the mass balance
constraints, since this constraint no longer includes all the variables.
• Lines 27-42 is the implementation of equation (17c); that is, the routing
constraints.
• Lines 47-50 dene the variable types; variables 0 to 9 (x0 -x9 ) are con-
tinuous, while variables 10 to 13 (b3 -b6 ) are binary.
• Line 52 dene the branching variables; these are the binary variables
b3 -b6 .
40
Edge 4: 0
Edge 5: 0
Edge 6: 1
Maximum Flow with Routing problem successfully solved in 370 (ms)
Press <RETURN> to close this window...
This solution is shown graphically in g. 10. As expected, the two edges
with the largest capacity are selected and the maximum ow is 11.
Figure 10: Optimal solution of maximum ow problem with routing con-
straints. Red edges indicate that the edge is at its maximum capacity and
dotted lines indicate that they have been swithced o.
41
Figure 11: Optimal solution of maximum ow problem with routing con-
straints and integer variables.
In this section, we will assume a simple model where the ow is a linear
function of the pressure drop, that is,
f (pA , pB ) = k(pA − pB ).
42
Constraints. We keep the mass balance Aint x = 0, and add the ow
equation constraints
x0 = k0 (p0 − p1 )
x1 = k1 (p0 − p2 )
x2 = k2 (p1 − p2 )
x3 = k3 (p1 − p3 )
x4 = k4 (p1 − p4 )
x5 = k5 (p2 − p3 )
x6 = k6 (p2 − p4 )
x7 = k7 (p3 − p4 )
x8 = k8 (p3 − p5 )
x9 = k9 (p4 − p5 ).
We can make use of our incidence matrix and collect the kj 's in a diagonal
matrix K = diag {k0 , . . . , k9 }, and write this as −KA> p = x (we leave it to
the reader to verify this), which gives us the constraint
>
x
−I −KA = 0.
p
43
6.4 The Six-Hump Camelback function
In this section we will minimize the Six-Hump Camelback function locally
and globally, using various methods. The Six-Hump Camelback function is a
classical test function for optimization algorithms, and is found many places
in the literature. The function has two variables, and is given by
x40
2
fSH (x0 , x1 ) = 4 − 2.1x0 + x20 + x0 x1 + (−4 + 4x21 )x21 . (19)
3
(a) −3 ≤ x0 ≤ 3 and −2 ≤ x1 ≤ 2
44
6.4.1 Formulating the optimization problem
We want to nd the global minimum of the Six-Hump Camelback function,
that is, we want to solve the unconstrained optimization problem
x40
minimize x2 = 4 − 2.1x0 + 2
x20 + x0 x1 + (−4 + 4x21 )x21 (20)
3
Note that we have introduced the new variable x2 = fSH (x0 , x1 ). The reason
for this is that we want to state (20) in its epigraph form (see section 1.4.1).
We dene a vector of optimization variables x = [x0 , x1 , x2 ]> . Since we want
to minimize x2 , this gives the linear objective function
x0
f (x) = 0 0 1 x1 = v > x. (21)
x2
Note that setting the lower bound equal to zero would give the same solution;
this would restrict x2 to lie on the graph of fSH as opposed to on or above
the graph of fSH .
45
ub . push_back (3) ; // Upper bound for x0
ub . push_back (2) ; // Upper bound for x1
ub . push_back ( IPOPT_UNBOUNDED ) ; // Upper bound for x2
Dene the starting point. The starting point for the optimization is
also dened using a vector of doubles. Since we are solving the problem
locally, the starting point will aect which solution we end up with. When
following this example, the reader should experiment with several dierent
starting points to see how the solver behaves.
std :: vector < double > z0 ; // Starting point
x60
fSH (x0 , x1 ) = − 2.1x40 + 4x20 + x0 x1 + 4x41 − 4x21 .
3
This means we can write (22b) on the form (8) and implement the Six-Hump
Camelback function as a polynomial constraint. First, we need to nd the
46
vector c and the matrix E used in (8). We have that
x60
− 2.1x40 + 4x20 + x0 x1 + 4x41 − 4x21 − x2
3
1
= x60 x01 x02 − 2.1x40 x01 x02 + 4x20 x01 x02
3
+ x10 x11 x02 + 4x00 x41 x02 − 4x00 x21 x02 − x00 x01 x12 ,
so we get 1
3 6 0 0
−2.1 4 0 0
4 2 0 0
(23)
1
c= 1
and E = 1 0.
4 0 4 0
−4 0 2 0
−1 0 0 1
We are now ready to solve the optimization problem using CENSO. To do
this, we complete the following steps:
• Add the constraint header le
• Dene the Six-Hump Camelback constraint
• Add the constraint to the constraint composite
• Solve the optimization problem with a local solver
Add the constraint header le. The rst thing we must do is to include
the ConstraintPolynomial header le, since we will be using this class to
dene our constraint.
# include " OptimizationProblem / constraintpolynomial . h "
47
Now we can create the ConstraintPolynomial object. The third and fourth
parameters passed to the constructor are the constraint's lower and upper
bound from (22b), which are −∞ and 0, respectively.
ConstraintPtr cSixHump ( new ConstraintPolynomial (c , E , -
IPOPT_UNBOUNDED , 0) ) ;
With the starting point z0 = (0, 0, 0), we should get the following output:
Solved (locally) using Ipopt!
Status: 1
48
f*: -9.99e-09
x*: ( 0, 0, 0)
This is not the global optimum; Ipopt is stuck in the saddle point located at
the origin. However, if we try dierent starting points, we will obtain better
solutions. For instance, if we give Ipopt the starting point z0 = (1, 0, 0),
it nds the global minimum at x∗ = (0.08984, −0.7127, −1.032), and the
starting point z0 = (−1, 0, 0) gives the other globally optimal point at x∗ =
(−0.08984, 0.7127, −1.032).
We will now complete the following steps to implement this constraint class
and solve the optimization problem:
• Implement the constructor
49
• Run the optimization problem
50
Implementing the clone function. When a constraint is added to a
constraint composite, what actually happens is that a copy of the constraint
is created by calling the desired constraint class clone() function. Therefore,
we must implement the clone() function here. This function simply returns
a pointer to a clone of the constraint which is created by the copy constructor,
which is a built-in function that creates a new object and copies all the values
of each data member from the original to the copy. We implement it directly
in the header le:
virtual ConstraintSixHumpCamel * clone () const { return new
ConstraintSixHumpCamel (* this ) ;}
y (0) = t1 * pow ( x (0) ,2) + x (0) * x (1) + t2 * pow ( x (1) ,2) -x (2) ;
}
Here, the doubles t1 and t2 are only used as temporary storage to make the
expression for y(0) a little bit tidier.
51
When implementing the Jacobian evaluation function, the value of nnzGradient
denes the number of elements we must calculate. This value was set to
three because there are three nonzero elements in the Jacobian. We add the
declaration as a public function in the header le:
public :
virtual void evalGradF ( DenseVector & x , DenseVector & dx ) ;
• dx, which is also an Eigen vector of doubles where we write the value
of the Jacobian elements evaluated at x.
An important remark here is that dx(0) does not necessarily mean the rst
element of the Jacobian; we will later implement the function structureGradF,
which maps each element of dx to the correct element in the Jacobian.
This Hessian has three nonzero elements, which is the reason we set nnzHessian
to 3 in the constructor. Although there are technically four nonzero elements,
we do not count the symmetric elements of the Hessian, since these are re-
dundant. We add the declaration as a public function in the header le:
public :
virtual void evalHessianF ( DenseVector & x , DenseVector & ddx )
;
• ddx, which is also an Eigen vector of doubles where we write the value
of the Hessian elements evaluated at x.
52
void ConstraintSixHumpCamel :: evalHessianF ( DenseVector & x ,
DenseVector & ddx )
{
ddx (0) = 8 -25.2* pow ( x (0) ,2) +10* pow ( x (0) ,4) ;
ddx (1) = 1;
ddx (2) = 48* pow ( x (1) ,2) -8;
}
As with the Jacobian, the elements of ddx contain the values of the nonzero
elements, and we will dene the position of these elements later in the func-
tion structureHessianF.
• iRow, which is an STL vector of integers with the row indices of the
elements of dx.
In our case, the calculated values assume the following positions in the Ja-
cobian:
Col 0 Col 1 Col 2
Row 0 dx(0) dx(1) dx(2)
That is, the value calculated for dx(0) should be placed in the (0,0) element
of the Jacobian, dx(1) should be placed in the (0,1) element and dx(2)
should be placed in the (0,2) element. This gives the following implementa-
tion in the .cpp le:
void ConstraintSixHumpCamel :: structureGradF ( std :: vector < int >
& iRow , std :: vector < int > & jCol )
{
// Position of dx (0) is (0 ,0)
iRow . push_back (0) ; jCol . push_back (0) ;
// Position of dx (1) is (0 ,1)
iRow . push_back (0) ; jCol . push_back (1) ;
// Position of dx (2) is (0 ,2)
iRow . push_back (0) ; jCol . push_back (2) ;
}
53
Dening the structure of the Hessian. The function structureHessianF
denes the position of the nonzero elements in the Hessian. We add the dec-
laration as a public function in the header le:
public :
virtual void structureHessianF ( std :: vector < int >& eqnr , std
:: vector < int >& iRow , std :: vector < int >& jCol ) ;
• iRow, which is an STL vector of integers with the row indices of the
elements of ddx.
In our case, the calculated values assume the following positions in the Hes-
sian:
Col 0 Col 1 Col 2
Row 0 ddx(0) ddx(1) −
Row 1 ddx(1) ddx(2) −
Row 2 − − −
The implementation becomes
void ConstraintSixHumpCamel :: structureHessianF ( std :: vector < int
> & eqnr , std :: vector < int > & iRow , std :: vector < int > &
jCol )
{
// Position of ddx (0) is (0 ,0)
eqnr . push_back (0) ; iRow . push_back (0) ; jCol . push_back (0) ;
// Position of ddx (1) is (0 ,1) ( and (1 ,0) )
eqnr . push_back (0) ; iRow . push_back (0) ; jCol . push_back (1) ;
// Position of ddx (2) is (1 ,1)
eqnr . push_back (0) ; iRow . push_back (1) ; jCol . push_back (1) ;
}
54
Add the constraint to the ConstraintComposite object. Now that we
have dened a constraint object, we need to add this to the ConstraintComposite
object that we created earlier. This is done the same way as for the polyno-
mial constraint:
constraints - > add ( cSixHump , variableMapping ) ;
Of course we can display solution information here the same way as we could
when we used the polynomial constraint class.
where the λi (x)'s are the eigenvalues of Hf (x), the Hessian of f (x). To
nd the required value of α for our convex relaxation, we need to know the
minimum eigenvalue of the Hessian of f (x). This is not trivial to solve.
However, Thm. 3.2 in [1] gives us a neat trick that we can use to nd a
55
lower bound on the minimum eigenvalue of a matrix A = {aij }. It is given
by
X
λmin ≥ min aii − max(|aij |, |aij |) (27)
i
j6=i
In other words, if we can nd upper and lower bounds on each element of
the Hessian, we can also get a lower bound on the minimum eigenvalue. The
Hessian of the Six-Hump Camelback function was given in (24). Note that
all the elements are constant except for the (0,0) element, which is a function
of x0 only, and the (1,1) element, which is a function of x1 only. We can
exploit this to nd upper and lower bounds. In g. 14 we see the (0,0)
element of the Hessian as a function of x0 . We know that both the minimum
and maximum of this function must be located either in the end points (xL 0
and xU 0 ) or in one of the stationary points, which are located at (0, 8) and
(±1.225, −7.876).
800 10
700
9.5
600
500
9
H(0,0)
H(0,0)
400
8.5
300
200
8
100
0 7.5
−3 −2 −1 0 1 2 3 −0.8 −0.6 −0.4 −0.2 0 0.2 0.4 0.6 0.8
x0 x0
The same goes for the (1,1) element, except that this is even easier since
it is only a second degree polynomial, and it is convex (see g. 15). Here,
the minimum and and maximum must be located either in xL 1 , x1 , or the
U
200
150
100
H(1,1)
50
−50
−2 −1.5 −1 −0.5 0 0.5 1 1.5 2
x1
56
We create a new constraint class which we name ConstraintSixHumpCamelWithRelaxation,
which starts out the same as the ConstraintSixHumpCamel class. That is,
our starting point is the following header le:
# ifndef CONSTRAINTSIXHUMPCAMELWITHRELAXATION_H
# define CONSTRAINTSIXHUMPCAMELWITHRELAXATION_H
# include " constraint . h "
virtual void structureGradF ( std :: vector < int >& iRow , std ::
vector < int >& jCol ) ;
virtual void structureHessianF ( std :: vector < int >& eqnr , std
:: vector < int >& iRow , std :: vector < int >& jCol ) ;
};
# endif // CONSTRAINTSIXHUMPCAMELWITHRELAXATION_H
We now go through the steps required to modify this class to allow global
optimization. These steps are:
57
Add a pointer to a relaxed constraint as a private data member.
We add the following in the header le:
private :
ConstraintPtr relaxedConstraint ;
58
lowerBoundF . push_back ( - IPOPT_UNBOUNDED );
upperBoundF . push_back (0) ;
// Gradient : true , Hessian : true , Linear : false , Convex :
false , Convex relaxation : true
setConstraintProperties ( true , true , false , false , true ) ;
// Number of nonzero elements in the Jacobian and Hessian
nnzGradient = 3;
nnzHessian = 3;
// Compute convex relaxation
computeConvexRelaxation () ;
// Check settings for obvious mistakes
checkConstraintSanity () ;
}
59
To accomplish this, we add the declaration in the header le:
public :
ConstraintSixHumpCamelWithRelaxation (
ConstraintSixHumpCamelWithRelaxation const & copy ) ;
60
Implement the computeConvexRelaxation() and getConvexRelaxation()
functions. To implement these functions, we need equations (26) and (27).
The function computeConvexRelaxation() is declared a private function:
private :
void computeConvexRelaxation () ;
declares two matrices H_lo and H_hi, which represent the lower and upper
bounds on the elements of the Hessian (24). These bounds are calculated in
the second line:
// Compute interval Hessian ( depends on current domain bounds )
intervalHessian ( H_lo , H_hi ) ;
and the implementation (refer to the comments to see exactly what the
function does):
void ConstraintSixHumpCamelWithRelaxation :: intervalHessian (
DenseMatrix & H_lo , DenseMatrix & H_hi )
{
// The Hessian of the six hump camelback function has the
following form :
//
// | 8 - 25.2 x0 ^2 + 10 x0 ^4 1 0 |
// H = | 1 48 x1 ^2 - 8 0 |
// | 0 0 0 |
61
//
// When the bounds on x0 and x1 are given , it is easy to
// find bounds on the elements of the Hessian matrix .
// The only non - constant elements are the (0 ,0) element
// and the (1 ,1) element . The (0 ,0) element has three
// stationary points at x0 = 0, x0 = +/ - 1.2249722
// where the function value is 8 and -7.876 , respectively .
// The (1 ,1) element has one stationary point at x1 = 0 ,
// where the function value is -8.
62
maxMinCandidates . clear () ;
63
return false ;
}
}
Now that we have calculated bounds on each element in the Hessian, we can
calculate a lower bound on the minimum eigenvalue of the Hessian. This is
done in the third line:
// Calculate lower bound on minimum eigenvalue
double minEigenVal = minIntervalEigenValue ( H_lo , H_hi ) ;
// Check that both matrices are square and that they are
// the same size
assert ( H_lo . rows () == H_lo . cols () ) ;
assert ( H_hi . rows () == H_hi . cols () ) ;
assert ( H_lo . rows () == H_hi . rows () ) ;
assert ( H_lo . cols () == H_hi . cols () ) ;
64
We use the obtained bound to calculate the required value of α with (26) in
line four:
// Calculate required value for alpha
double alpha = fmax (0 , -0.5* minEigenVal ) ;
and pass this value to the constructor of the relaxed constraint class in the
fth and nal line:
// Create new relaxed constraint based on alpha
relaxedConstraint = ConstraintPtr ( new
ConstraintSixHumpCamelConvexRelaxation ( domainLowerBound
, domainUpperBound , alpha ) ) ;
The function getConvexRelaxation() simply clones the relaxed constraint
and returns a pointer to the clone. It has the declaration
public :
virtual Constraint * getConvexRelaxation () const ;
and the implementation
Constraint * ConstraintSixHumpCamelWithRelaxation ::
getConvexRelaxation () const
{
assert ( relaxedConstraint != nullptr ) ;
return relaxedConstraint - > clone () ;
}
65
The gradient ∇c1 (x) is
>
8x0 − 8.4x30 + 2x50 + x1 + 2αx0 − α(xL U
0 + x0 )
∇c1 (x) = x0 − 8x1 + 16x31 + 2αx1 − α(xL U
1 + x1 )
.
−1
8 − 25.2x20 + 10x40 + 2α
1 0
∇2 c1 (x) = 1 48x21 − 8 + 2α 0 .
0 0 0
virtual void structureGradF ( std :: vector < int >& iRow , std ::
vector < int >& jCol ) ;
virtual void structureHessianF ( std :: vector < int >& eqnr , std
:: vector < int >& iRow , std :: vector < int >& jCol ) ;
private :
double alpha ;
};
# endif // CONSTRAINTSIXHUMPCAMELCONVEXRELAXATION_H
66
ConstraintSixHumpCamelConvexRelaxation ::
ConstraintSixHumpCamelConvexRelaxation ( std :: vector < double
> lb , std :: vector < double > ub , double alpha )
{
dimensionDomainF = 3;
dimensionCodomainF = 1;
gradientCalculatedF = true ;
hessianCalculatedF = true ;
domainLowerBound = lb ;
domainUpperBound = ub ;
nnzGradient = 3;
nnzHessian = 3;
checkConstraintSanity () ;
}
double t1 = (4.0 -2.1* pow ( x (0) ,2) + pow ( x (0) ,4) /3.0) * pow ( x (0)
,2) ;
double t2 = x (0) * x (1) ;
double t3 = ( -4+4* pow ( x (1) ,2) ) * pow ( x (1) ,2) ;
double t4 = alpha *( x0L * x0U - x (0) *( x0L + x0U ) + pow ( x (0) ,2) ) ;
double t5 = alpha *( x1L * x1U - x (1) *( x1L + x1U ) + pow ( x (1) ,2) ) ;
y (0) = t1 + t2 + t3 + t4 + t5 - x (2) ;
}
67
double x0U = domainUpperBound . at (0) ;
double x1L = domainLowerBound . at (1) ;
double x1U = domainUpperBound . at (1) ;
dx (0) = 8* x (0) -8.4* pow ( x (0) ,3) +2* pow ( x (0) ,5) + x (1) +2* alpha * x
(0) - alpha *( x0L + x0U ) ;
dx (1) = x (0) -8* x (1) +16* pow ( x (1) ,3) +2* alpha * x (1) - alpha *( x1L +
x1U ) ;
dx (2) = -1;
}
68
variable_types . push_back ( CONTINUOUS ) ; // x0
variable_types . push_back ( CONTINUOUS ) ; // x1
variable_types . push_back ( CONTINUOUS ) ; // x2
Now we can create a BranchAndBound object, which takes the objective, con-
straints, starting point, variable types and branching variables as parameters,
and call its optimize() function to run the Branch-and-Bound algorithm:
BranchAndBound bnb ( objective , constraints , z0 , variable_types ,
branching_variables ) ;
bnb . optimize () ;
The optimal point and objective function value are available through the
getOptimalSolution() and getObjectiveValue() functions, respectively:
zopt_found = bnb . getOptimalSolution () ;
fopt_found = bnb . getObjectiveValue () ;
0 0
−5 −5
−10 −10
−15 −15
Obj. fn. value
−20 −20
−25 −25
−30 −30
−35 −35
Upper bound Upper bound
Lower bound Lower bound
−40 −40
0 500 1000 1500 2000 2500 0 500 1000 1500 2000 2500 3000 3500 4000
Iteration no. Iteration no.
69
section 1.4.3, an attractive property of the B-spline is that the control points
used to describe the B-spline can be used to construct a convex relaxation of
the B-spline. This property has been exploited in the ConstraintBspline
class, meaning that we can solve problems dened with B-splines globally.
To solve the Six-Hump Camelback problem with this approach, we have to
complete the following steps:
• Create an InterpolationTable object and ll it with samples
• Create a B-spline constraint using the interpolation table
• Add the constraint to the constraint composite
• Solve the optimization problem in a Branch-and-Bound framework
double dx = 0.05;
for ( double x1 = lb . at (0) ; x1 <= ub . at (0) ; x1 += dx )
{
for ( double x2 = lb . at (1) ; x2 <= ub . at (1) ; x2 += dx )
{
std :: vector < double > x ;
x . push_back ( x1 ) ;
x . push_back ( x2 ) ;
70
Create a B-spline constraint using the interpolation table. We se-
lect a polynomial degree of 3, since this gives us a B-spline which is twice
continuously dierentiable. We set the third parameter to true to indicate
that we want an equality constraint.
ConstraintPtr cbspline ( new ConstraintBspline (* data , 3 , true ) ) ;
This time, the problem is solved in only 49 iterations. The large improvement
in performance is due to the improved quality of the convex relaxations. The
progress of the algorithm is shown in g. 19. Note that the initial lower
bound is very close to the upper bound (< 0.01 as opposed to > 25-30 for
the αBB approach).
71
−1.03
−1.032
−1.034
Obj. fn. value
−1.036
−1.038
−1.04
Upper bound
Lower bound
−1.042
0 5 10 15 20 25 30 35 40
Iteration no.
72
References
[1] C.S. Adjiman, S. Dallwig, C.A. Floudas, and A. Neumaier. A global
optimization method, αbb, for general twice-dierentiable constrained
{NLPs} â i. theoretical advances. Computers & Chemical Engineering,
22(9):1137 1158, 1998.
73