5.3.3. C++ 的QP建模和优化

在本节中,我们将使用 MindOpt C++ API,以按行输入的形式来建模以及求解 二次规划问题示例 中的问题。

首先,引入头文件:

24#include "MindoptCpp.h"

并创建优化模型:

31    /*------------------------------------------------------------------*/
32    /* Step 1. Create environment and model.                            */
33    /*------------------------------------------------------------------*/
34    MDOEnv env = MDOEnv();
35    MDOModel model = MDOModel(env);

接下来,我们通过 MDOModel::set() 将目标函数设置为 最小化,并调用 MDOModel::addVar() 来添加四个优化变量,定义其下界、上界、名称和类型(有关 MDOModel::set()MDOModel::addVar() 的详细使用方式,请参考 C++ API):

42        /* Change to minimization problem. */
43        model.set(MDO_IntAttr_ModelSense, MDO_MINIMIZE);
44
45        /* Add variables. */
46        std::vector<MDOVar> x;
47        x.push_back(model.addVar(0.0, 10.0,         0.0, MDO_CONTINUOUS, "x0"));
48        x.push_back(model.addVar(0.0, MDO_INFINITY, 0.0, MDO_CONTINUOUS, "x1"));
49        x.push_back(model.addVar(0.0, MDO_INFINITY, 0.0, MDO_CONTINUOUS, "x2"));
50        x.push_back(model.addVar(0.0, MDO_INFINITY, 0.0, MDO_CONTINUOUS, "x3"));

接着,我们开始添加线性约束:

52        /* Add constraints. */
53        model.addConstr(1.0 * x[0] + 1.0 * x[1] + 2.0 * x[2] + 3.0 * x[3], MDO_GREATER_EQUAL, 1.0, "c0");
54        model.addConstr(1.0 * x[0] - 1.0 * x[2] + 6.0 * x[3], MDO_EQUAL, 1.0, "c1");

然后,我们创建一个二次表达式 MDOQuadExpr, 再调用 MDOQuadExpr::addTerms 来设置目标函数线性部分。 obj_idx 表示线性部分的索引,obj_val 表示与 obj_idx 中的索引相对应的非零系数值,整数 4 代表线性部分的非零元的个数。

56        /*Create a QuadExpr. */
57        MDOQuadExpr obj = MDOQuadExpr(0.0);
58
59        /* Add objective linear term.*/
60        const MDOVar obj_idx[] = { x[0], x[1], x[2], x[3]};
61        const double obj_val[] = { 1.0, 1.0, 1.0, 1.0};
62        obj.addTerms(obj_val, obj_idx, 4);

然后,调用 MDOQuadExpr::addTerms 来设置目标的二次项系数 \(Q\)。 其中,qo_values 表示要添加的二次项的系数,qo_col1qo_col2 表示与qo_values相对应的二次项的第一个变量和第二个变量,第四个参数表示 \(Q\) 中的下三角部分的非零元个数。

Note

为了确保 \(Q\) 的对称性,用户只需要输入其下三角形部分,并且在求解器内部会乘以 1/2.

 64        /* Add quadratic objective matrix Q.
 65         *
 66         *  Note.
 67         *  1. The objective function is defined as c^Tx + 1/2 x^TQx, where Q is stored with coordinate format.
 68         *  2. Q will be scaled by 1/2 internally.
 69         *  3. To ensure the symmetricity of Q, user needs to input only the lower triangular part.
 70         *
 71         * Q = [ 1.0  0.5  0    0   ]
 72         *     [ 0.5  1.0  0    0   ]
 73         *     [ 0.0  0.0  1.0  0   ]
 74         *     [ 0    0    0    1.0 ]
 75         */
 76
 77        const double qo_values[] =
 78        {
 79            1.0,
 80            0.5, 1.0,
 81                    1.0, 
 82                        1.0
 83        };
 84        const MDOVar qo_col1[] = 
 85        {
 86            x[0], 
 87            x[1],   x[1],
 88                    x[2],
 89                           x[3]  
 90        };
 91        const MDOVar qo_col2[] =
 92        {
 93            x[0],
 94            x[0],   x[1],
 95                      x[2],
 96                           x[3]
 97        };
 98
 99        obj.addTerms(qo_values, qo_col1, qo_col2, 5);
100
101        model.setObjective(obj, MDO_MINIMIZE);

问题输入完成后,再调用 MDOModel::optimize() 求解优化问题:

107        model.optimize();

示例 MdoQoEx1.cpp 提供了完整源代码:

  1/**
  2 *  Description
  3 *  -----------
  4 *
  5 *  Linear optimization (row-wise input).
  6 *
  7 *  Formulation
  8 *  -----------
  9 *
 10 *  Minimize
 11 *    obj: 1 x0 + 1 x1 + 1 x2 + 1 x3 
 12 *         + 1/2 [ x0^2 + x1^2 + x2^2 + x3^2 + x0 x1]
 13 *  Subject To
 14 *   c0 : 1 x0 + 1 x1 + 2 x2 + 3 x3 >= 1
 15 *   c1 : 1 x0 - 1 x2 + 6 x3 = 1
 16 *  Bounds
 17 *    0 <= x0 <= 10
 18 *    0 <= x1
 19 *    0 <= x2
 20 *    0 <= x3
 21 *  End
 22 */
 23#include <iostream>
 24#include "MindoptCpp.h"
 25#include <vector>
 26
 27using namespace std;
 28
 29int main(void)
 30{
 31    /*------------------------------------------------------------------*/
 32    /* Step 1. Create environment and model.                            */
 33    /*------------------------------------------------------------------*/
 34    MDOEnv env = MDOEnv();
 35    MDOModel model = MDOModel(env);
 36    
 37    try 
 38    {
 39        /*------------------------------------------------------------------*/
 40        /* Step 2. Input model.                                             */
 41        /*------------------------------------------------------------------*/
 42        /* Change to minimization problem. */
 43        model.set(MDO_IntAttr_ModelSense, MDO_MINIMIZE);
 44
 45        /* Add variables. */
 46        std::vector<MDOVar> x;
 47        x.push_back(model.addVar(0.0, 10.0,         0.0, MDO_CONTINUOUS, "x0"));
 48        x.push_back(model.addVar(0.0, MDO_INFINITY, 0.0, MDO_CONTINUOUS, "x1"));
 49        x.push_back(model.addVar(0.0, MDO_INFINITY, 0.0, MDO_CONTINUOUS, "x2"));
 50        x.push_back(model.addVar(0.0, MDO_INFINITY, 0.0, MDO_CONTINUOUS, "x3"));
 51
 52        /* Add constraints. */
 53        model.addConstr(1.0 * x[0] + 1.0 * x[1] + 2.0 * x[2] + 3.0 * x[3], MDO_GREATER_EQUAL, 1.0, "c0");
 54        model.addConstr(1.0 * x[0] - 1.0 * x[2] + 6.0 * x[3], MDO_EQUAL, 1.0, "c1");
 55        
 56        /*Create a QuadExpr. */
 57        MDOQuadExpr obj = MDOQuadExpr(0.0);
 58
 59        /* Add objective linear term.*/
 60        const MDOVar obj_idx[] = { x[0], x[1], x[2], x[3]};
 61        const double obj_val[] = { 1.0, 1.0, 1.0, 1.0};
 62        obj.addTerms(obj_val, obj_idx, 4);
 63
 64        /* Add quadratic objective matrix Q.
 65         *
 66         *  Note.
 67         *  1. The objective function is defined as c^Tx + 1/2 x^TQx, where Q is stored with coordinate format.
 68         *  2. Q will be scaled by 1/2 internally.
 69         *  3. To ensure the symmetricity of Q, user needs to input only the lower triangular part.
 70         *
 71         * Q = [ 1.0  0.5  0    0   ]
 72         *     [ 0.5  1.0  0    0   ]
 73         *     [ 0.0  0.0  1.0  0   ]
 74         *     [ 0    0    0    1.0 ]
 75         */
 76
 77        const double qo_values[] =
 78        {
 79            1.0,
 80            0.5, 1.0,
 81                    1.0, 
 82                        1.0
 83        };
 84        const MDOVar qo_col1[] = 
 85        {
 86            x[0], 
 87            x[1],   x[1],
 88                    x[2],
 89                           x[3]  
 90        };
 91        const MDOVar qo_col2[] =
 92        {
 93            x[0],
 94            x[0],   x[1],
 95                      x[2],
 96                           x[3]
 97        };
 98
 99        obj.addTerms(qo_values, qo_col1, qo_col2, 5);
100
101        model.setObjective(obj, MDO_MINIMIZE);
102
103        /*------------------------------------------------------------------*/
104        /* Step 3. Solve the problem and populate optimization result.                */
105        /*------------------------------------------------------------------*/
106        /* Solve the problem. */
107        model.optimize();
108
109        if(model.get(MDO_IntAttr_Status) == MDO_OPTIMAL)
110        {
111            cout << "Optimal objective value is: " << model.get(MDO_DoubleAttr_ObjVal) << endl;
112            cout << "Decision variables:" << endl;
113            int i = 0;
114            for (auto v : x)
115            {
116                cout << "x[" << i++ << "] = " << v.get(MDO_DoubleAttr_X) << endl;
117            }
118        }
119        else
120        {
121            cout<< "No feasible solution." << endl;
122        }
123        
124    }
125    catch (MDOException& e) 
126    { 
127        std::cout << "Error code = " << e.getErrorCode() << std::endl;
128        std::cout << e.getMessage() << std::endl;
129    } 
130    catch (...) 
131    { 
132        std::cout << "Error during optimization." << std::endl;
133    }
134    
135    return static_cast<int>(MDO_OKAY);
136}