7.5 Geometric Programming¶
Geometric programs (GP) are a particular class of optimization problems which can be expressed in special polynomial form as positive sums of generalized monomials. More precisely, a geometric problem in canonical form is
where each \(f_0,\ldots,f_m\) is a posynomial, that is a function of the form
with arbitrary real \(\alpha_{ki}\) and \(c_k>0\). The standard way to formulate GPs in convex form is to introduce a variable substitution
Under this substitution all constraints in a GP can be reduced to the form
involving a log-sum-exp bound. Moreover, constraints involving only a single monomial in \(x\) can be even more simply written as a linear inequality:
We refer to the MOSEK Modeling Cookbook and to [BKVH07] for more details on this reformulation. A geometric problem formulated in convex form can be entered into MOSEK with the help of exponential cones.
7.5.1 Example GP1¶
The following problem comes from [BKVH07]. Consider maximizing the volume of a \(h\times w\times d\) box subject to upper bounds on the area of the floor and of the walls and bounds on the ratios \(h/w\) and \(d/w\):
The decision variables in the problem are \(h,w,d\). We make a substitution
after which (7.10) becomes
Next, we demonstrate how to implement a log-sum-exp constraint (7.9). It can be written as:
This presentation requires one extra variable \(u_k\) for each monomial appearing in the original posynomial constraint.
// Models log(sum(exp(Ax+b))) <= 0.
// Each row of [A b] describes one of the exp-terms
void logsumexp(Model::t M,
std::shared_ptr<ndarray<double, 2>> A,
Variable::t x,
std::shared_ptr<ndarray<double, 1>> b)
{
int k = A->size(0);
auto u = M->variable(k);
M->constraint(Expr::sum(u), Domain::equalsTo(1.0));
M->constraint(Expr::hstack(u,
Expr::constTerm(k, 1.0),
Expr::add(Expr::mul(A, x), b)), Domain::inPExpCone());
}
We can now use this function to assemble all constraints in the model. The linear part of the problem is entered as in Sec. 7.1 (Linear Optimization).
std::shared_ptr<ndarray<double, 1>> max_volume_box(double Aw, double Af,
double alpha, double beta, double gamma, double delta)
{
Model::t M = new Model("max_vol_box"); auto _M = finally([&]() { M->dispose(); });
auto xyz = M->variable(3);
M->objective("Objective", ObjectiveSense::Maximize, Expr::sum(xyz));
logsumexp(M,
new_array_ptr<double,2>({{1,1,0}, {1,0,1}}),
xyz,
new_array_ptr<double,1>({log(2.0/Aw), log(2.0/Aw)}));
M->constraint(Expr::dot(new_array_ptr<double,1>({0,1,1}), xyz), Domain::lessThan(log(Af)));
M->constraint(Expr::dot(new_array_ptr<double,1>({1,-1,0}), xyz), Domain::inRange(log(alpha),log(beta)));
M->constraint(Expr::dot(new_array_ptr<double,1>({0,-1,1}), xyz), Domain::inRange(log(gamma),log(delta)));
M->setLogHandler([](const std::string & msg) { std::cout << msg << std::flush; } );
M->solve();
return std::make_shared<ndarray<double, 1>>(shape(3), [xyz](ptrdiff_t i) { return exp((*(xyz->level()))[i]); });
}
Given sample data we obtain the solution \(h,w,d\) as follows:
int main()
{
double Aw = 200.0;
double Af = 50.0;
double alpha = 2.0;
double beta = 10.0;
double gamma = 2.0;
double delta = 10.0;
auto hwd = max_volume_box(Aw, Af, alpha, beta, gamma, delta);
std::cout << std::setprecision(4);
std::cout << "h=" << (*hwd)[0] << " w=" << (*hwd)[1] << " d=" << (*hwd)[2] << std::endl;
}