5.4.3. C语言的SDP建模与优化

在本节中,我们将使用 MindOpt C 语言的 API 来建模以及求解 半定规划问题示例 中的问题。

5.4.3.1. SDP示例1

首先,需要引入头文件:

22#include "Mindopt.h"

第一步:创建优化模型:

先建立一空的MindOpt模型。

67    /*------------------------------------------------------------------*/
68    /* Step 1. Create a model and change the parameters.                */
69    /*------------------------------------------------------------------*/
70    /* Create an empty model. */
71    MDO_CHECK_CALL(Mdo_createMdl(&model));

第二步:SDP模型输入

接着,我们将目标函数改为maximization,并新增 \(\mathbf{X}\) 对称矩阵变量。

73    /*------------------------------------------------------------------*/
74    /* Step 2. Input model.                                             */
75    /*------------------------------------------------------------------*/
76    /* Change to maximization problem. */
77    MDO_CHECK_CALL(Mdo_setIntAttr(model, MDO_INT_ATTR_MIN_SENSE, MDO_NO));
78 
79    /* Add matrix variable. */
80    MDO_CHECK_CALL(Mdo_addSymMat(model, dim_mat, mat_name));

我们利用 Mdo_replaceSymMatObjs() 来输入目标函数中的 \(\mathbf{C}\) 的非零元素。 其中,第一个参数为 模型, 第二个参数为 矩阵的索引值 (此处为0),第三个元素代表着 矩阵非零的元素的个数 第四个和第五个参数分别代表 矩阵中非零元素其对应的行和列之索引 , 而最后一个参数则为 非零元的数值的索引

82    /* Input objective coefficients. */
83    MDO_CHECK_CALL(Mdo_replaceSymMatObjs(
84        model, 0, C_size, C_row_indices, C_col_indices, C_values));

接着,我们输入第一个约束。我们首先输一空约束;接着再利用 Mdo_replaceSymMatElements() 输入约束中的 \(\mathbf{A}\) 的非零元素。其中,第二和第三个参数分别为: 约束的索引值 (此处为0)以及 矩阵的索引值 (此处为0),第四个参数代表 矩阵的非零元素的个数,第五个和第六个参数代表 矩阵中非零元素其对应的行和列之索引,而最后一个参数则为 非零元的数值的索引

86    /* Input first constraint. */
87    MDO_CHECK_CALL(Mdo_addRow(model, 1.0, 1.0, 0, NULL, NULL, "c0"));
88    MDO_CHECK_CALL(Mdo_replaceSymMatElements(model, 0, 0, A_size,
89        A_row_indices_in_cone, A_col_indices_in_cone, A_values_in_cone));

第三步:求解SDP模型 模型输入后,我们接着用 Mdo_solveProb() 来求解问题,并用 Mdo_displayResults() 呈现求解结果。

94    /* Solve the problem. */
95    MDO_CHECK_CALL(Mdo_solveProb(model));
96    Mdo_displayResults(model);

第四步: 取得SDP模型的解 我们利用泛用函数 Mdo_getRealAttr() 来取得最优的的目标函数值

104        MDO_CHECK_CALL(Mdo_getRealAttr(model, MDO_REAL_ATTR_PRIMAL_OBJ_VAL, &val));

最后,我们利用 Mdo_getRealAttrSymMat() 来取得 \(\mathbf{X}\) 的变量值。其中, 第二个参数定义 属性值 (附注: Mdo_getRealAttrSymMat() 为泛用函数,因此必须配合 属性值来定义函数的使用目的; MDO_REAL_ATTR_SYM_MAT_PRIMAL_SOLN定义目的为 获取(对称矩阵)变量的数值),第三个参数为: 矩阵的索引值 (此处为0),第四个参数为 矩阵的尺寸 返回值soln则为矩阵中对应元素的索引。

107        MDO_CHECK_CALL(Mdo_getRealAttrSymMat(
108            model, MDO_REAL_ATTR_SYM_MAT_PRIMAL_SOLN, 0, dim_mat * dim_mat, NULL, NULL, soln));

文件链接 MdoSdoEx1.c 提供了完整源代码:

  1/**
  2 *  Description
  3 *  -----------
  4 *
  5 *  Semidefinite optimization (row-wise input).
  6 *
  7 *  Formulation
  8 *  -----------
  9 *
 10 *  Maximize 
 11 *  obj: tr(C X) 
 12 * 
 13 *  Subject To
 14 *    c0 : tr(A X) = 1
 15 *  Matrix
 16 *    C = [ -3  0  1 ]  A = [ 3 0 1 ]
 17 *        [  0 -2  0 ]      [ 0 4 0 ]
 18 *        [  1  0 -3 ]      [ 1 0 5 ]
 19 *  End
 20 */
 21#include <stdio.h>
 22#include "Mindopt.h"
 23
 24/* Macro to check the return code. */
 25#define MDO_CHECK_CALL(MDO_CALL)                                    \
 26    code = MDO_CALL;                                                \
 27    if (code != MDO_OKAY)                                           \
 28    {                                                               \
 29        Mdo_explainResult(model, code, str);                        \
 30        Mdo_freeMdl(&model);                                        \
 31        fprintf(stderr, "===================================\n");   \
 32        fprintf(stderr, "Error   : code <%d>\n", code);             \
 33        fprintf(stderr, "Reason  : %s\n", str);                     \
 34        fprintf(stderr, "===================================\n");   \
 35        return (int)code;                                           \
 36    }
 37
 38int main(void)
 39{
 40    /* Variables. */
 41    char str[1024] = { "\0" };
 42    MdoMdl * model = NULL;
 43    MdoResult code = MDO_OKAY;
 44    MdoStatus status = MDO_UNKNOWN;
 45
 46    /* Input data. */
 47    const int    num_mats = 1;
 48    const int    dim_mat =  3 ; /* Dimension of the matrix variables. */
 49    const char * mat_name = "X";
 50
 51    const int    C_size = 4;
 52    const int    C_row_indices[] =  {  0,   0,    1,    2   }; /* Row index of a matrix variable. */
 53    const int    C_col_indices[] =  {  0,   2,    1,    2   }; /* Column index of a matrix variable. */
 54    const double C_values[]      =  { -3.0, 1.0, -2.0, -3.0 }; /* Values of a matrix variable. */
 55
 56    const int    A_size = 4;
 57    const int    A_mat_idx = 1;
 58    const int    A_row_indices_in_cone[] = { 0, 2, 1, 2 }; /* Row index in a matrix variable. */
 59    const int    A_col_indices_in_cone[] = { 0, 0, 1, 2 }; /* Column index in a matrix variable. */
 60    const double A_values_in_cone[]      = { 3.0, 1.0, 4.0, 5.0 }; /* Values of a matrix variable. */
 61
 62    /* Temp. */
 63    double soln[15];
 64    double val;
 65    int i, j, b;
 66
 67    /*------------------------------------------------------------------*/
 68    /* Step 1. Create a model and change the parameters.                */
 69    /*------------------------------------------------------------------*/
 70    /* Create an empty model. */
 71    MDO_CHECK_CALL(Mdo_createMdl(&model));
 72  
 73    /*------------------------------------------------------------------*/
 74    /* Step 2. Input model.                                             */
 75    /*------------------------------------------------------------------*/
 76    /* Change to maximization problem. */
 77    MDO_CHECK_CALL(Mdo_setIntAttr(model, MDO_INT_ATTR_MIN_SENSE, MDO_NO));
 78 
 79    /* Add matrix variable. */
 80    MDO_CHECK_CALL(Mdo_addSymMat(model, dim_mat, mat_name));
 81
 82    /* Input objective coefficients. */
 83    MDO_CHECK_CALL(Mdo_replaceSymMatObjs(
 84        model, 0, C_size, C_row_indices, C_col_indices, C_values));
 85
 86    /* Input first constraint. */
 87    MDO_CHECK_CALL(Mdo_addRow(model, 1.0, 1.0, 0, NULL, NULL, "c0"));
 88    MDO_CHECK_CALL(Mdo_replaceSymMatElements(model, 0, 0, A_size,
 89        A_row_indices_in_cone, A_col_indices_in_cone, A_values_in_cone));
 90
 91    /*------------------------------------------------------------------*/
 92    /* Step 3. Solve the problem and populate the result.               */
 93    /*------------------------------------------------------------------*/
 94    /* Solve the problem. */
 95    MDO_CHECK_CALL(Mdo_solveProb(model));
 96    Mdo_displayResults(model);
 97
 98    switch (Mdo_getStatus(model))
 99    {
100    case MDO_UNKNOWN:
101        printf("Optimizer terminated with an UNKNOWN status.\n");
102        break;
103    case MDO_OPTIMAL:
104        MDO_CHECK_CALL(Mdo_getRealAttr(model, MDO_REAL_ATTR_PRIMAL_OBJ_VAL, &val));
105        printf("Optimizer terminated with an OPTIMAL status.\n");
106        printf(" - Primal objective : %e.\n", val);
107        MDO_CHECK_CALL(Mdo_getRealAttrSymMat(
108            model, MDO_REAL_ATTR_SYM_MAT_PRIMAL_SOLN, 0, dim_mat * dim_mat, NULL, NULL, soln));
109        printf("X = \n");
110        for (i = 0; i < dim_mat; ++i)
111        {
112            printf("  (");
113            for (j = 0; j < dim_mat; ++j)
114            {
115                printf(" %+8.6e", soln[i * dim_mat + j]);
116            }
117            printf(" )\n");
118        }
119        break;
120    case MDO_INFEASIBLE:
121        printf("Optimizer terminated with an INFEASIBLE status.\n");
122        break;
123    case MDO_UNBOUNDED:
124        printf("Optimizer terminated with an UNBOUNDED status.\n");
125        break;
126    case MDO_INF_OR_UBD:
127        printf("Optimizer terminated with an INFEASIBLE or UNBOUNDED status.\n");
128        break;
129    }
130
131    /*------------------------------------------------------------------*/
132    /* Step 4. Free the model.                                          */
133    /*------------------------------------------------------------------*/
134    /* Free the model. */
135    Mdo_freeMdl(&model);
136
137    return (int)code;
138}

5.4.3.2. SDP示例2

首先,需要引入头文件:

29        Mdo_explainResult(model, code, str);                        \

第一步:创建MindOpt模型

首先,我们必须先建立一空的MindOpt模型。

95    /*------------------------------------------------------------------*/
96    /* Step 1. Create a model and change the parameters.                */
97    /*------------------------------------------------------------------*/
98    /* Create an empty model. */
99    MDO_CHECK_CALL(Mdo_createMdl(&model));

第二步:SDP模型输入

接着,我们将目标函数改为maximization,并新增 \(x_0\)\(x_1\) 两个变量,以及 \(\mathbf{X}_0\)\(\mathbf{X}_1\) 两个对称矩阵变量。

101    /*------------------------------------------------------------------*/
102    /* Step 2. Input model.                                             */
103    /*------------------------------------------------------------------*/
104    /* Change to maximization problem. */
105    MDO_CHECK_CALL(Mdo_setIntAttr(model, MDO_INT_ATTR_MIN_SENSE, MDO_NO));
106 
107    /* Add variables. */
108    MDO_CHECK_CALL(Mdo_addCol(model, 0.0, MDO_INFINITY, 0.0, 0, NULL, NULL, "x0", MDO_NO));
109    MDO_CHECK_CALL(Mdo_addCol(model, 0.0, MDO_INFINITY, 0.0, 0, NULL, NULL, "x1", MDO_NO));
110
111    /* Add matrix variables. */
112    MDO_CHECK_CALL(Mdo_addSymMats(model, num_mats, dim_mats, mat_names));

我们利用 Mdo_replaceSymMatObjs() 来输入目标函数中的 \(\mathbf{C}_0\) 的非零元素。 其中,第二个参数为 矩阵的索引值 (此处为 C0_mat_idx),第三个元素代表着 矩阵非零的元素的个数 第四个和第五个参数分别代表 矩阵中非零元素其对应的行和列之索引 , 而最后一个参数则为 非零元的数值的索引

114    /* Input objective coefficients. */
115    MDO_CHECK_CALL(Mdo_replaceSymMatObjs(
116        model, C0_mat_idx, C0_size, C0_row_indices, C0_col_indices, C0_values));

同理,我们再接着输入目标函数中的 \(\mathbf{C}_1\) 的非零元素。

117    MDO_CHECK_CALL(Mdo_replaceSymMatObjs(
118        model, C1_mat_idx, C1_size, C1_row_indices, C1_col_indices, C1_values));

接着,我们输入第一个约束。我们首先输入空约束,接着再利用 Mdo_replaceSymMatElements() 输入约束中的 \(\mathbf{A}_{00}\) 的非零元素。其中,第二个和第三个参数分别为: 约束的索引值 (此处为0)以及 矩阵的索引值 (此处为A00_mat_idx),第四个参数为 矩阵的非零元素的个数,第四个和第五个参数 矩阵中非零元素其对应的行和列之索引 ,而最后一个参数则为 非零元的数值的索引

120    /* Input first constraint. */
121    MDO_CHECK_CALL(Mdo_addRow(model, 1.0, 1.0, row0_size, row0_idx, row0_val, "c0"));
122    MDO_CHECK_CALL(Mdo_replaceSymMatElements(model, 0, A00_mat_idx, A00_size,
123        A00_row_indices_in_cone, A00_col_indices_in_cone, A00_values_in_cone));

同理,我们再接着输入第二个约束。

125    /* Input second constraint. */
126    MDO_CHECK_CALL(Mdo_addRow(model, 2.0, 2.0, row1_size, row1_idx, row1_val, "c1"));
127    MDO_CHECK_CALL(Mdo_replaceSymMatElements(model, 1, A11_mat_idx, A11_size,
128        A11_row_indices_in_cone, A11_col_indices_in_cone, A11_values_in_cone));

第三步:求解SDP模型

模型输入后,我们接着用 Mdo_solveProb() 来求解问题,并用 Mdo_displayResults() 呈现求解结果。

130    /*------------------------------------------------------------------*/
131    /* Step 3. Solve the problem and populate the result.               */
132    /*------------------------------------------------------------------*/
133    /* Solve the problem. */
134    MDO_CHECK_CALL(Mdo_solveProb(model));
135    Mdo_displayResults(model);

第四步: 取得SDP模型的解

我们利用泛用函数 Mdo_getRealAttr() 来取得最优的的目标函数值,并用泛用函数 Mdo_getRealAttrArray() 来取得 \(x_0\)\(x_1\) 两个变量值;此处第二个参数定义了 属性值 (附注: Mdo_getRealAttrArray() 为泛用函数, 因此必须配合属性值来定义函数的使用目的;MDO_REAL_ATTR_PRIMAL_SOLN 定义目的为获取(线性)变量的数值), 第三和第四个参数分别代表第一个索引和最后一个索引 加上1, 也就是 \(x_j\), 其中 \(0 \leq j < 2\)

143        MDO_CHECK_CALL(Mdo_getRealAttr(model, MDO_REAL_ATTR_PRIMAL_OBJ_VAL, &val));
144        printf("Optimizer terminated with an OPTIMAL status.\n");
145        printf(" - Primal objective : %e.\n", val);
146        MDO_CHECK_CALL(Mdo_getRealAttrArray(model, MDO_REAL_ATTR_PRIMAL_SOLN, 0, 2, soln));

最后,我们利用 Mdo_getRealAttrSymMat() 来取得 \(\mathbf{X}_0\)\(\mathbf{X}_1\) 的变量值。其中, 第二个参数定义 属性值 (附注: Mdo_getRealAttrSymMat() 为泛用函数, 因此必须配合属性值来定义函数的使用目的;MDO_REAL_ATTR_SYM_MAT_PRIMAL_SOLN 定义目的为获取(对称矩阵)变量的数值),第三个参数为: 矩阵的序列数 (此处为b), 第四个参数为 矩阵的尺寸 , 返回值soln则为矩阵中对应的元素的索引。

153            MDO_CHECK_CALL(Mdo_getRealAttrSymMat(
154                model, MDO_REAL_ATTR_SYM_MAT_PRIMAL_SOLN, b, dim_mats[b] * dim_mats[b], NULL, NULL, soln));

文件链接 MdoSdoEx2.c 提供了完整源代码:

  1/**
  2 *  Description
  3 *  -----------
  4 *
  5 *  Semidefinite optimization (row-wise input).
  6 *
  7 *  Formulation
  8 *  -----------
  9 *
 10 *  Maximize
 11 *  obj: tr(C0 X0)   + tr(C1 X1)    + 0 x0 + 0 x1
 12 *
 13 *  Subject To
 14 *   c0 : tr(A00 X0)                + 1 x0        = 1
 15 *   c1 :              tr(A11 X1)          + 1 x1 = 2
 16 *  Bounds
 17 *    0 <= x0
 18 *    0 <= x1
 19 *  Matrix
 20 *    C0 =  [ 2 1 ]   A00 = [ 3 1 ]
 21 *          [ 1 2 ]         [ 1 3 ]
 22 *
 23 *    C1 = [ 3 0 1 ]  A11 = [ 3 0 1 ]
 24 *         [ 0 2 0 ]        [ 0 4 0 ]
 25 *         [ 1 0 3 ]        [ 1 0 5 ]
 26 *  End
 27 */
 28#include <stdio.h>
 29#include "Mindopt.h"
 30
 31/* Macro to check the return code. */
 32#define MDO_CHECK_CALL(MDO_CALL)                                    \
 33    code = MDO_CALL;                                                \
 34    if (code != MDO_OKAY)                                           \
 35    {                                                               \
 36        Mdo_explainResult(model, code, str);                        \
 37        Mdo_freeMdl(&model);                                        \
 38        fprintf(stderr, "===================================\n");   \
 39        fprintf(stderr, "Error   : code <%d>\n", code);             \
 40        fprintf(stderr, "Reason  : %s\n", str);                     \
 41        fprintf(stderr, "===================================\n");   \
 42        return (int)code;                                           \
 43    }
 44
 45int main(void)
 46{
 47    /* Variables. */
 48    char str[1024] = { "\0" };
 49    MdoMdl * model = NULL;
 50    MdoResult code = MDO_OKAY;
 51    MdoStatus status = MDO_UNKNOWN;
 52
 53    /* Input data. */
 54    const int    num_cols = 2;
 55    const int    num_mats = 2;
 56    const int    dim_mats[] = { 2, 3 }; /* Dimension of the matrix variables. */
 57    const char * mat_names[] = { "X0", "X1" };
 58
 59    const int    row0_size = 1;
 60    const int    row0_idx[] = { 0 };
 61    const double row0_val[] = { 1.0 };
 62    const int    row1_size = 1;
 63    const int    row1_idx[] = { 1 };
 64    const double row1_val[] = { 1.0 };
 65
 66    const int    C0_size = 3;
 67    const int    C0_mat_idx = 0;
 68    const int    C0_row_indices[] =  { 0,   1,   1   }; /* Row index of a matrix variable. */
 69    const int    C0_col_indices[] =  { 0,   0,   1   }; /* Column index of a matrix variable. */
 70    const double C0_values[]      =  { 2.0, 1.0, 2.0 }; /* Values of a matrix variable. */
 71
 72    const int    C1_size = 4;
 73    const int    C1_mat_idx = 1;
 74    const int    C1_row_indices[] =  { 0,   0,   1,   2   }; /* Row index of a matrix variable. */
 75    const int    C1_col_indices[] =  { 0,   2,   1,   2   }; /* Column index of a matrix variable. */
 76    const double C1_values[]      =  { 3.0, 1.0, 2.0, 3.0 }; /* Values of a matrix variable. */
 77
 78    const int    A00_size = 3;
 79    const int    A00_mat_idx = 0;
 80    const int    A00_row_indices_in_cone[] = { 0, 1, 1 }; /* Row index in a matrix variable. */
 81    const int    A00_col_indices_in_cone[] = { 0, 0, 1 }; /* Column index in a matrix variable. */
 82    const double A00_values_in_cone[]      = { 3.0, 1.0, 3.0 }; /* Values of a matrix variable. */
 83
 84    const int    A11_size = 4;
 85    const int    A11_mat_idx = 1;
 86    const int    A11_row_indices_in_cone[] = { 0, 2, 1, 2 }; /* Row index in a matrix variable. */
 87    const int    A11_col_indices_in_cone[] = { 0, 0, 1, 2 }; /* Column index in a matrix variable. */
 88    const double A11_values_in_cone[]      = { 3.0, 1.0, 4.0, 5.0 }; /* Values of a matrix variable. */
 89     
 90    /* Temp. */
 91    double soln[15];
 92    double val;
 93    int i, j, b;
 94
 95    /*------------------------------------------------------------------*/
 96    /* Step 1. Create a model and change the parameters.                */
 97    /*------------------------------------------------------------------*/
 98    /* Create an empty model. */
 99    MDO_CHECK_CALL(Mdo_createMdl(&model));
100 
101    /*------------------------------------------------------------------*/
102    /* Step 2. Input model.                                             */
103    /*------------------------------------------------------------------*/
104    /* Change to maximization problem. */
105    MDO_CHECK_CALL(Mdo_setIntAttr(model, MDO_INT_ATTR_MIN_SENSE, MDO_NO));
106 
107    /* Add variables. */
108    MDO_CHECK_CALL(Mdo_addCol(model, 0.0, MDO_INFINITY, 0.0, 0, NULL, NULL, "x0", MDO_NO));
109    MDO_CHECK_CALL(Mdo_addCol(model, 0.0, MDO_INFINITY, 0.0, 0, NULL, NULL, "x1", MDO_NO));
110
111    /* Add matrix variables. */
112    MDO_CHECK_CALL(Mdo_addSymMats(model, num_mats, dim_mats, mat_names));
113
114    /* Input objective coefficients. */
115    MDO_CHECK_CALL(Mdo_replaceSymMatObjs(
116        model, C0_mat_idx, C0_size, C0_row_indices, C0_col_indices, C0_values));
117    MDO_CHECK_CALL(Mdo_replaceSymMatObjs(
118        model, C1_mat_idx, C1_size, C1_row_indices, C1_col_indices, C1_values));
119
120    /* Input first constraint. */
121    MDO_CHECK_CALL(Mdo_addRow(model, 1.0, 1.0, row0_size, row0_idx, row0_val, "c0"));
122    MDO_CHECK_CALL(Mdo_replaceSymMatElements(model, 0, A00_mat_idx, A00_size,
123        A00_row_indices_in_cone, A00_col_indices_in_cone, A00_values_in_cone));
124
125    /* Input second constraint. */
126    MDO_CHECK_CALL(Mdo_addRow(model, 2.0, 2.0, row1_size, row1_idx, row1_val, "c1"));
127    MDO_CHECK_CALL(Mdo_replaceSymMatElements(model, 1, A11_mat_idx, A11_size,
128        A11_row_indices_in_cone, A11_col_indices_in_cone, A11_values_in_cone));
129
130    /*------------------------------------------------------------------*/
131    /* Step 3. Solve the problem and populate the result.               */
132    /*------------------------------------------------------------------*/
133    /* Solve the problem. */
134    MDO_CHECK_CALL(Mdo_solveProb(model));
135    Mdo_displayResults(model);
136
137    switch (Mdo_getStatus(model))
138    {
139    case MDO_UNKNOWN:
140        printf("Optimizer terminated with an UNKNOWN status.\n");
141        break;
142    case MDO_OPTIMAL:
143        MDO_CHECK_CALL(Mdo_getRealAttr(model, MDO_REAL_ATTR_PRIMAL_OBJ_VAL, &val));
144        printf("Optimizer terminated with an OPTIMAL status.\n");
145        printf(" - Primal objective : %e.\n", val);
146        MDO_CHECK_CALL(Mdo_getRealAttrArray(model, MDO_REAL_ATTR_PRIMAL_SOLN, 0, 2, soln));
147        for (j = 0; j < 2; ++j)
148        {
149            printf("x[%d] = %+8.6e\n", j, soln[j]);
150        }
151        for (b = 0; b < num_mats; ++b)
152        {
153            MDO_CHECK_CALL(Mdo_getRealAttrSymMat(
154                model, MDO_REAL_ATTR_SYM_MAT_PRIMAL_SOLN, b, dim_mats[b] * dim_mats[b], NULL, NULL, soln));
155            printf("X[%d] = \n", b);
156            for (i = 0; i < dim_mats[b]; ++i)
157            {
158                printf("  (");
159                for (j = 0; j < dim_mats[b]; ++j)
160                {
161                    printf(" %+8.6e", soln[i * dim_mats[b] + j]);
162                }
163                printf(" )\n");
164            }
165        }
166        break;
167    case MDO_INFEASIBLE:
168        printf("Optimizer terminated with an INFEASIBLE status.\n");
169        break;
170    case MDO_UNBOUNDED:
171        printf("Optimizer terminated with an UNBOUNDED status.\n");
172        break;
173    case MDO_INF_OR_UBD:
174        printf("Optimizer terminated with an INFEASIBLE or UNBOUNDED status.\n");
175        break;
176    }
177
178    /*------------------------------------------------------------------*/
179    /* Step 4. Free the model.                                          */
180    /*------------------------------------------------------------------*/
181    /* Free the model. */
182    Mdo_freeMdl(&model);
183
184    return (int)code;
185}