GLPK(GNU线性规划工具包)介绍

发布于:2025-05-12 ⋅ 阅读:(11) ⋅ 点赞:(0)

      GLPK全称为GNU Linear Programming Kit(GNU线性规划工具包),可从 https://sourceforge.net/projects/winglpk/ 下载源码及二进制库,最新版本为4.65。也可从 https://ftp.gnu.org/gnu/glpk/ 下载,仅包含源码,最新版本为5.0。

      GLPK是用C实现的,旨在解决线性规划(LP, Linear Programming)、混合整数线性规划(MIP, Mixed Integer linear Programming)和其他相关问题。MIP问题是一种LP问题,其中某些变量需要额外满足整数条件。

      之前在 https://blog.csdn.net/fengbingchun/article/details/146341096 中介绍过通过Pyomo调用glpk解决饮食问题,这里通过调用GLPK API接口实现,测试代码如下:

int test_glpk()
{
	constexpr int total_foods{ 9 };
	const std::vector<std::string> foods{ "food1", "food2", "food3", "food4", "food5", "food6", "food7", "food8", "food9" };
	const std::vector<float> unit_prices{ 0.886, 0.863, 0.855, 0.917, 0.237, 0.856, 0.833, 0.904, 0.876 };
	const std::vector<std::array<float, 2>> bounds{ {1.0, 55.0}, {1.0, 55.0}, {1.0, 55.0}, {1.0, 55.0}, {7.0, 9.0}, {1.0, 55.0}, {1.0, 55.0}, {1.0, 55.0}, {1.0, 55.0} };

	constexpr int num_nutrients{ 4 };
	const std::vector<std::string> nutrients{ "nutrient1", "nutrient2", "nutrient3", "nutrient4" };
	const std::vector<std::array<float, 4>> nutrient_values{ {0.21, 65.10, 0.72, 88.5}, {0.08, 64.58, 0.63, 76.9}, {0.22, 64.81, 0.53, 86.1}, {0.58, 65.84, 1.09, 57.8}, {0.033, 46.07, 14.15, 0},
																{0.059, 65.25, 0.39, 87.2}, {0.14, 64.25, 0.57, 93.7}, {0.033, 63.06, 1.36, 79.0}, {0.076, 65.20, 0.59, 99.0} };
	const std::vector<std::array<float, 2>> nutrient_limit{ {0., 49.}, {6200., 6230.}, {9.9, 782.}, {6500., 7000.} };

	constexpr float total_quantity{ 99. };
	const std::string mandatory_food{ "food5" };
	constexpr int num_select_foods{ 5 };

	if (foods.size() != total_foods) {
		std::cerr << "Error: number of foods mismatch" << std::endl;
		return -1;
	}
	if (unit_prices.size() != total_foods) {
		std::cerr << "Error: number of unit_prices mismatch" << std::endl;
		return -1;
	}
	if (bounds.size() != total_foods) {
		std::cerr << "Error: number of bounds mismatch" << std::endl;
		return -1;
	}
	if (nutrient_values.size() != total_foods) {
		std::cerr << "Error: number of nutrient_values mismatch" << std::endl;
		return -1;
	}

	if (nutrients.size() != num_nutrients) {
		std::cerr << "Error: number of nutrients mismatch" << std::endl;
		return -1;
	}
	if (nutrient_limit.size() != num_nutrients) {
		std::cerr << "Error: number of nutrient_limit mismatch" << std::endl;
		return -1;
	}

	int mandatroy_food_index{ 0 };
	for (auto i = 0; i < total_foods; ++i) {
		if (mandatory_food == foods[i]) {
			mandatroy_food_index = i;
			break;
		}
	}

	auto lp = glp_create_prob(); // create problem object
	glp_set_prob_name(lp, "diet problem");
	glp_set_obj_dir(lp, GLP_MIN); // minimize objective function

	// add col variables
	glp_add_cols(lp, 2 * total_foods); // first total_foods: number of each food; last total_foods: binary variables, whether the food is selected

	for (auto j = 0; j < total_foods; ++j) {
		// set food number variable
		glp_set_col_name(lp, j + 1, foods[j].c_str());
		glp_set_col_kind(lp, j + 1, GLP_CV); // GLP_IV: number of foods can only be an integer, use MIP; default is GLP_CV
		glp_set_col_bnds(lp, j + 1, GLP_LO, 0.0, 0.0);
		glp_set_obj_coef(lp, j + 1, unit_prices[j]); // set cost coefficient

		// set binary select variables
		std::string name = "S_" + foods[j];
		glp_set_col_name(lp, total_foods + j + 1, name.c_str());
		glp_set_col_kind(lp, total_foods + j + 1, GLP_BV);
		glp_set_obj_coef(lp, total_foods + j + 1, 0.);
	}

	// add row constraints
	glp_add_rows(lp, num_nutrients);

	for (auto i = 0; i < num_nutrients; ++i) {
		glp_set_row_name(lp, i + 1, nutrients[i].c_str());
		glp_set_row_bnds(lp, i + 1, GLP_DB, nutrient_limit[i][0], nutrient_limit[i][1]);
	}

	// add select constraints
	glp_add_rows(lp, 2);
	glp_set_row_name(lp, num_nutrients + 1, "select_total");
	glp_set_row_bnds(lp, num_nutrients + 1, GLP_FX, num_select_foods, num_select_foods);

	// add mandatory food constraint
	glp_set_row_name(lp, num_nutrients + 2, "must_have_food");
	glp_set_row_bnds(lp, num_nutrients + 2, GLP_FX, 1., 1.);

	// add total_quantity constraint
	glp_add_rows(lp, 1);
	glp_set_row_name(lp, num_nutrients + 3, "total_quantity");
	glp_set_row_bnds(lp, num_nutrients + 3, GLP_FX, total_quantity, total_quantity);

	// constraint matrix:
	// glp_load_matrix(lp, ne, ia, ja, ar): for k = 1,..., ne, where ia[k] is the row index, ja[k] is the column index, and ar[k] is a numeric value of corresponding constraint coefficient
	// parameter ne specifies the total number of (non-zero) elements in the matrix to be loaded
	int ia[1 + 1000]{}, ja[1 + 1000]{}, k{ 0 };
	double ar[1 + 1000]{};

	// nutrients constraint
	for (auto i = 0; i < num_nutrients; ++i) {
		for (auto j = 0; j < total_foods; ++j) {
			k++;
			ia[k] = i + 1, ja[k] = j + 1;
			ar[k] = nutrient_values[j][i];
		}
	}

	// select total constraint
	for (auto j = 0; j < total_foods; ++j) {
		k++;
		ia[k] = num_nutrients + 1, ja[k] = total_foods + j + 1;
		ar[k] = 1.;
	}

	// mandatory food constraint
	k++;
	ia[k] = num_nutrients + 2, ja[k] = total_foods + mandatroy_food_index + 1;
	ar[k] = 1.;

	// total_quantity constraint
	for (auto j = 0; j < total_foods; ++j) {
		k++;
		ia[k] = num_nutrients + 3, ja[k] = j + 1;
		ar[k] = 1.;
	}

	// add relationship constraint between food quantity and select variable
	for (auto j = 0; j < total_foods; ++j) {
		// lower constraint: quantity_j >= min_j * select_j
		glp_add_rows(lp, 1);
		glp_set_row_name(lp, num_nutrients + 4 + 2 * j, "quantity_min");
		glp_set_row_bnds(lp, num_nutrients + 4 + 2 * j, GLP_LO, 0., 0.);
		
		// quantity_j coefficient: 1.0
		k++;
		ia[k] = num_nutrients + 4 + 2 * j, ja[k] = j + 1;
		ar[k] = 1.;

		// select_j cofficient: -min_j
		k++;
		ia[k] = num_nutrients + 4 + 2 * j, ja[k] = total_foods + j + 1;
		ar[k] = -bounds[j][0];

		// upper constraint: quantity_j <= max_j * select_j
		glp_add_rows(lp, 1);
		glp_set_row_name(lp, num_nutrients + 4 + 2 * j + 1, "quantity_max");
		glp_set_row_bnds(lp, num_nutrients + 4 + 2 * j + 1, GLP_UP, 0., 0.);

		k++;
		ia[k] = num_nutrients + 4 + 2 * j + 1, ja[k] = j + 1;
		ar[k] = 1.;

		k++;
		ia[k] = num_nutrients + 4 + 2 * j + 1, ja[k] = total_foods + j + 1;
		ar[k] = -bounds[j][1];
	}

	glp_load_matrix(lp, k, ia, ja, ar);

	// verify constraint matrix and variables
	std::cout << "number of constraints: " << glp_get_num_rows(lp) << std::endl;
	for (auto i = 1; i <= glp_get_num_rows(lp); ++i)
		std::cout << "i: " << i << "; type: " << glp_get_row_type(lp, i) << "; lower: " << glp_get_row_lb(lp, i) << "; upper: " << glp_get_row_ub(lp, i) << std::endl;

	std::cout << "number of variables: " << glp_get_num_cols(lp) << std::endl;
	for (auto j = 1; j <= glp_get_num_cols(lp); ++j) // Note: the value of glp_get_col_prim is different in different positions
		std::cout << "j: " << j << "; kind: " << glp_get_col_kind(lp, j) << "; primal value: " << glp_get_col_prim(lp, j) << "; lower: " << glp_get_col_lb(lp, j) << "; upper: " << glp_get_col_ub(lp, j) << "; coef: " << glp_get_obj_coef(lp, j) << std::endl;

	auto ret = glp_write_lp(lp, nullptr, "../../../testdata/model.lp");
	if (ret != 0) {
		std::cerr << "Error: failed to write problem data, error code: " << ret << std::endl;
		glp_delete_prob(lp);
		return -1;
	}

	double quantity_sum{ 0. };
	if (0) { // LP(Linear Programming)
		glp_smcp parm{};
		glp_init_smcp(&parm);
		parm.msg_lev = GLP_MSG_ERR; // warning and error messages only
		parm.presolve = GLP_ON;
		ret = glp_simplex(lp, &parm); // solve LP problem with the primal or dual simplex method
		if (ret != 0) {
			std::cerr << "Error: failed to solve: error code: " << ret << std::endl;
			glp_delete_prob(lp);
			return -1;
		}

		ret = glp_get_status(lp);
		if (ret != GLP_OPT) {
			std::cerr << "Error: there is no optimal solution, status: " << ret << std::endl;
			glp_delete_prob(lp);
			return -1;
		}

		std::cout << "minimum cost: " << glp_get_obj_val(lp) << std::endl;
		for (auto j = 0; j < total_foods; ++j) {
			auto qty = glp_get_col_prim(lp, j + 1);
			auto selected = glp_get_col_prim(lp, total_foods + j + 1);
			if (selected > 0.5) {
				std::cout << foods[j] << ": quantity: " << qty << ", limit: [" << bounds[j][0] << "," << bounds[j][1] << "]" << std::endl;
				quantity_sum += qty;
			}
		}
	}
	else { // MIP(Mixed Integer linear Programming)
		glp_iocp parm;
		glp_init_iocp(&parm);
		parm.presolve = GLP_ON;
		parm.msg_lev = GLP_MSG_ERR; // close information output prompt
		ret = glp_intopt(lp, &parm);
		if (ret != 0) {
			std::cerr << "Error: failed to solve: error code: " << ret << std::endl;
			glp_delete_prob(lp);
			return -1;
		}

		ret = glp_mip_status(lp);
		if (ret != GLP_OPT) {
			std::cerr << "Error: there is no optimal solution, status: " << ret << std::endl;
			glp_delete_prob(lp);
			return -1;
		}

		std::cout << "minimum cost: " << glp_mip_obj_val(lp) << std::endl;
		for (auto j = 0; j < total_foods; ++j) {
			auto qty = glp_mip_col_val(lp, j + 1);
			auto selected = glp_mip_col_val(lp, total_foods + j + 1);
			if (selected > 0.5) {
				std::cout << foods[j] << ": quantity: " << qty << ", limit: [" << bounds[j][0] << "," << bounds[j][1] << "]" << std::endl;
				quantity_sum += qty;
			}
		}
	}

	std::cout << "result quantity sum: " << quantity_sum << "; require quantity sum: " << total_quantity << std::endl;

	glp_delete_prob(lp);
	return 0;
}

      测试数据说明:饮食问题,目标是选择以最低成本满足每日营养需求的食物,测试数据无实际意义

      total_foods:食物种类总数。

      foods:每种食物的名称。

      unit_prices:每种食物的价格。

      bounds:每种食物可取的食物份数限制,变量的界限。

      num_nutrients:每种食物含有的营养成分个数,每种食物含有相同的营养成分个数。

      nutrients:每种食物含有的营养成分名称。

      nutrient_values:每种食物含有的营养成分含量。

      nutrient_limit:每种营养成分消耗总量约束。

      total_quantity:被选中食物总份数约束。

      mandatory_food:被选中食物中必选要包含的食物约束。

      num_select_foods:被选中食物种类数约束。

      使用到的GLPK函数:接口详细描述参考源码中的doc/glpk.pdf

      glp_create_prob:创建一个新的问题对象(problem object),该对象最初是"empty",即没有行和列。该函数返回指向所创建对象的指针,该指针应在对该对象的任何后续操作中使用。

      glp_delete_prob:删除问题对象,释放分配给该对象的所有内存。

      glp_set_prob_name:给问题对象分配问题名,长度要求1至255个字符,如果参数名称为nullptr或空字符串,则将删除问题对象的现有问题名。

      glp_set_obj_dir:设置(更改)目标函数优化方向标志,GLP_MIN表示最小化目标函数;GLP_MAX表示最大化目标函数。默认情况,最小化目标函数。

      glp_add_cols:将新列添加到问题对象。新列始终添加到列表的末尾,因此现有列的序数不会改变。每个新添加的列初始值固定为零,并且约束系数列表为空。

      glp_set_col_name:分配(更改)第j列列名,长度要求1至255个字符,如果参数名称为nullptr或空字符串,则将删除第j列的现有名称。

      glp_set_col_kind:按照参数类型指定的方式设置(更改)第j列的类型(kind)。GLP_CV表示连续变量;GLP_IV表示整数变量;GLP_BV表示二元变量。

      glp_get_col_kind:返回第j列的类型(kind)。

      glp_set_col_bnds(lp,j,type,lb,ub):设置(更改)第j列类型和界限。支持的类型如下图所示:如果列没有下限(lower bound),则忽略参数lb。如果列没有上限(upper bound),则忽略参数ub。如果列是固定(fixed)类型,则仅使用参数lb,而忽略参数ub。添加到问题对象后,每列的初始值都固定为零,即其类型为GLP_FX,且两个界限均为0。

      glp_get_num_cols:返回指定问题对象中的列数。

      glp_set_obj_coef(lp,j,coef):设置(更改)第j列目标系数(objective coefficient)。新的目标系数值由参数coef指定。

      glp_get_obj_coef:返回第j列的目标系数。

      glp_get_col_prim:返回与第j列关联变量的原始值(primal value)。注:此函数在调用glp_simplex前后得到值不同

      glp_get_col_lb:返回第j列的下限,即相应变量的下限。如果该列没有下限,则返回-DBL_MAX。

      glp_get_col_ub:返回第j列的上限,即相应变量的上限。如果该列没有上限,则返回+DBL_MAX。

      glp_add_rows:将新行添加到问题对象。新行始终添加到行列表的末尾,因此现有行的序号不会改变。添加的每个新行最初都是free(unbounded),并且约束系数列表为空。

      glp_set_row_name:分配(更改)第i行行名,长度要求1至255个字符,如果参数名称为nullptr或空字符串,则将删除第i行的现有名称。

      glp_set_row_bnds(lp,i,type,lb,ub):设置(更改)第i行类型和界限。支持的类型与glp_set_col_bnds相同。如果行没有下限(lower bound),则忽略参数lb。如果行没有上限(upper bound),则忽略参数ub。如果行是固定(fixed)类型,则仅使用参数lb,而忽略参数ub。添加到问题对象中的每一行最初都是free,即其类型为GLP_FR。

      glp_get_num_rows:返回指定问题对象中的行数。

      glp_get_row_type:返回第i行的类型,支持的类型与glp_set_col_bnds相同。

      glp_get_row_lb:返回第i行的下限,如果该行没有下限,则返回-DBL_MAX。

      glp_get_row_ub:返回第i行的上限,如果该行没有上限,则返回+DBL_MAX。

      glp_load_matrix(lp,ne,ia,ja,ar):加载(替换)整个约束矩阵(constraint matrix),将传入数组ia,ja和ar的约束矩阵加载到指定的问题对象中。加载前,约束矩阵的当前内容将被销毁。

      约束系数(约束矩阵的元素)应指定为三元组(triplets)(ia[k], ja[k], ar[k]),其中k=1, ..., ne,其中ia[k]是行索引,ja[k]是列索引,ar[k]是相应约束系数(constraint coefficient)的数值。参数ne指定要加载的矩阵中(非零)元素的总数。不允许使用索引相同的系数(同一行和列的索引组合只能出现一次)。允许使用零系数,但是,它们不会存储在约束矩阵中。如果参数ne为0,则参数ia、ja和/或ar可以指定为nullptr。

      glp_write_lp:将LP/MIP问题数据以CPLEX LP格式写入文本文件。可用于检查约束是否正确。

      glp_init_smcp:使用默认值初始化simplex求解器(solver)使用的控制参数。

      glp_simplex:使用simplex方法求解线性规划问题,并将计算结果存储回问题对象

      glp_get_status:报告指定问题对象当前基本解的通用状态,返回可能的状态如下图所示:

      glp_get_obj_val:返回目标函数的当前值。求目标函数的最小值或最大值,由glp_set_obj_dir指定。

      glp_init_iocp:使用默认值初始化branch-and-cut求解器使用的控制参数。

      glp_intopt:使用branch-and-cut方法求解混合整数线性规划问题

      glp_mip_status:报告MIP求解器找到的MIP解决方案的状态,返回可能的状态如下图所示:

      glp_mip_obj_val:返回MIP解决方案的目标函数值。求目标函数的最小值或最大值,由glp_set_obj_dir指定。

      glp_mip_col_val:返回与MIP解决方案的第j列关联变量的值。

      使用到的MIP接口包括:glp_set_col_kind/glp_get_col_kind、glp_init_iocp、glp_intopt、glp_mip_status、glp_mip_obj_val、glp_mip_col_val。

      按照以上给的测试数据,共需18个变量(cols);共需25个约束(rows)。执行结果如下图所示:

      注:在GLPK中,C API要求矩阵元素从索引1开始,row[i]对应第i个约束条件,col[j]对应第j个决策变量。通过约束矩阵即ia(行)、ja(列)、ar(系数)关联二者。

      GitHubhttps://github.com/fengbingchun/Messy_Test


网站公告

今日签到

点亮在社区的每一天
去签到