Gurobi中一些基本的概念

  1. Parameters:参数用于控制求解器的行为,相关参数参考
  2. Attributes:属性是查询和修改相关对象特征的机制,相关属性参考
  3. Environment:环境是Model的容器,gurobi的python接口会自动创建环境。设置环境的Parameters会让环境中的每个Model都默认这些Parameters,也可以单独为每个模型设置Parameters。

使用Gurobi建模的一般步骤

使用Gurobi求解优化模型比较有条理的步骤如下:

  1. 先建模,建模可按以下步骤一步一步分析,避免脑袋绕晕
    1. 定义模型中要使用的集合
    2. 定义模型中要使用的参数
    3. 定义模型中要使用的决策变量
    4. 定义模型的目标函数
    5. 分析模型应该包含的所有约束条件
  2. 然后使用Gurobi求解,具体又可按以下步骤进行
    1. 使用list或tuplelist对象保存集合(附:集合在模型中一般用作索引)
    2. 使用dict或tupledict对象保存参数
    3. 创建模型对象
      1. 添加变量
      2. 设置目标函数
      3. 添加约束条件
      4. 进行优化

tuplelist和tupledict数据结构与list和dict的区别是什么?

前者是gurobi的内置数据结构,有更加强大的索引功能。

操作速查

获取或修改模型的参数

直接用Model对象的Params属性就可以实现gurobi中相关参数的获取和修改。

  • Termination parameters which set the stopping conditions for algorithms;
  • Tolerance parameters which control solution quality requirements;
  • Logging and I/O parameters which control logging behavior as well as input and output files;
  • Algorithmic control parameters for Presolve, Simplex, Barrier, Scaling, MIP, MIP Cuts, and Numerics;
  • Tuning parameters which control parameter tuning strategies for grbtune and the tune API calls;
  • Solution Pool parameters which control how the solver searches for and stores additional solutions other than the best available one;
  • Multi-Objective parameters which control some aspects when working with multiple objectives;
  • Parameters for configuring aspects of remote optimization and licensing: Parallel and Distributed Computing, Instant Cloud, Compute Server, Cluster Manager, Token Server, Web License Service; and
通过Model对象的Params属性修改参数的示范
model.Params.MIPFocus = 0 # 求解策略参数的设置 model.Params.LogFile = 'modelversion2.log' # 求解日志存放位置 model.Params.MIPGap = 0.7 # 要求找到最优解,*测试的时候直接设置称60%的gap,真正要收敛到0太困难了,收到60%左右基本上就是最优解,利用这个技巧方便后面做灵敏度分析*

获取或修改对象的属性

获取对象属性的话就用Model对象的getAttr方法就好了,设置对象属性则可以使用setAttr方法,当然了也可以使用面向对象的.属性名方式来访问或修改属性,缺点是批量操作得写循环。这两个方法都有attrname和objects参数,当只提供属性名的时候,查询或设置的就是Model对象对应属性的值,如果提供了objects(通常是tuplelist或tupledict)这两个方法就查询和设置的是这些objects的属性值。

gurobi获取或删除对象属性的示范
main_model.setAttr('VType',main_model.getVars(),GRB.INTEGER) # 将main_model.getVars()获取到的所有变量的vtype属性设置为GRB.INTEGER。

使用quicksum函数和tupledict的sum方法完成求和操作

quicksum一般的用法是里面写一个生成器推导式,得到所有要求和的项,与sum方法相比,quicksum函数更加灵活,可以实现变量与系数的加权求和。

quicksum函数和sum方法的示例
for i in I: model.addConstr(gp.quicksum(x[i,j] for j in J)<=5) for i in I: model.addConstr(x.sum(i,"*")<=5)

创建约束条件的时候实际上可以使用一种更加简单的写法,及在addConstrs方法内部写生成器推导式。上述的约束条件也可以写成:

另一种简便写法
model.addConstrs(gp.quicksum(x[i,j] for I in J)<=5 for i in I) model.addConstrs(x.sum(i,"*") for i in I)

利用GRB减少不必要的记忆

GRB类中定义了Gurobi中常用的一些常量,例如我想将求解策略参数设置为均衡模式,我当然可以写model.Params.MIPFocus = 0,但是0这个数字并没什么字面意义,时间一长你可能就忘了到底得把MIPFocus设置成几才是均衡模式了。使用GRB类中定义的常量你可以写成model.Params.MIPFocus=MIPFOCUS_BALANCED。

GRB类中定义的常量
# Status codes LOADED = 1 OPTIMAL = 2 INFEASIBLE = 3 INF_OR_UNBD = 4 UNBOUNDED = 5 CUTOFF = 6 ITERATION_LIMIT = 7 NODE_LIMIT = 8 TIME_LIMIT = 9 SOLUTION_LIMIT = 10 INTERRUPTED = 11 NUMERIC = 12 SUBOPTIMAL = 13 INPROGRESS = 14 USER_OBJ_LIMIT = 15 WORK_LIMIT = 16 MEM_LIMIT = 17 # Batch status codes BATCH_CREATED = 1 BATCH_SUBMITTED = 2 BATCH_ABORTED = 3 BATCH_FAILED = 4 BATCH_COMPLETED = 5 # Constraint senses LESS_EQUAL = '<' GREATER_EQUAL = '>' EQUAL = '=' # Variable types CONTINUOUS = 'C' BINARY = 'B' INTEGER = 'I' SEMICONT = 'S' SEMIINT = 'N' # Objective sense MINIMIZE = 1 MAXIMIZE = -1 # SOS types SOS_TYPE1 = 1 SOS_TYPE2 = 2 # General constraint types GENCONSTR_MAX = 0 GENCONSTR_MIN = 1 GENCONSTR_ABS = 2 GENCONSTR_AND = 3 GENCONSTR_OR = 4 GENCONSTR_NORM = 5 GENCONSTR_NL = 6 GENCONSTR_INDICATOR = 7 GENCONSTR_PWL = 8 GENCONSTR_POLY = 9 GENCONSTR_EXP = 10 GENCONSTR_EXPA = 11 GENCONSTR_LOG = 12 GENCONSTR_LOGA = 13 GENCONSTR_POW = 14 GENCONSTR_SIN = 15 GENCONSTR_COS = 16 GENCONSTR_TAN = 17 GENCONSTR_LOGISTIC = 18 # Operation codes OPCODE_CONSTANT = 0 OPCODE_VARIABLE = 1 OPCODE_PLUS = 2 OPCODE_MINUS = 3 OPCODE_MULTIPLY = 4 OPCODE_DIVIDE = 5 OPCODE_UMINUS = 6 OPCODE_SQUARE = 7 OPCODE_SQRT = 8 OPCODE_SIN = 9 OPCODE_COS = 10 OPCODE_TAN = 11 OPCODE_POW = 12 OPCODE_EXP = 13 OPCODE_LOG = 14 OPCODE_LOG2 = 15 OPCODE_LOG10 = 16 OPCODE_LOGISTIC = 17 # Basis status BASIC = 0 NONBASIC_LOWER = -1 NONBASIC_UPPER = -2 SUPERBASIC = -3 # Numeric constants INFINITY = 1e100 UNDEFINED = 1e101 MAXINT = 2000000000 # Limits MAX_NAMELEN = 255 MAX_STRLEN = 512 MAX_TAGLEN = 10240 MAX_CONCURRENT = 64 # Other constants DEFAULT_CS_PORT = 61000 # Version number VERSION_MAJOR = 12 VERSION_MINOR = 0 VERSION_TECHNICAL = 0 # Errors ERROR_OUT_OF_MEMORY = 10001 ERROR_NULL_ARGUMENT = 10002 ERROR_INVALID_ARGUMENT = 10003 ERROR_UNKNOWN_ATTRIBUTE = 10004 ERROR_DATA_NOT_AVAILABLE = 10005 ERROR_INDEX_OUT_OF_RANGE = 10006 ERROR_UNKNOWN_PARAMETER = 10007 ERROR_VALUE_OUT_OF_RANGE = 10008 ERROR_NO_LICENSE = 10009 ERROR_SIZE_LIMIT_EXCEEDED = 10010 ERROR_CALLBACK = 10011 ERROR_FILE_READ = 10012 ERROR_FILE_WRITE = 10013 ERROR_NUMERIC = 10014 ERROR_IIS_NOT_INFEASIBLE = 10015 ERROR_NOT_FOR_MIP = 10016 ERROR_OPTIMIZATION_IN_PROGRESS = 10017 ERROR_DUPLICATES = 10018 ERROR_NODEFILE = 10019 ERROR_Q_NOT_PSD = 10020 ERROR_QCP_EQUALITY_CONSTRAINT = 10021 ERROR_NETWORK = 10022 ERROR_JOB_REJECTED = 10023 ERROR_NOT_SUPPORTED = 10024 ERROR_EXCEED_2B_NONZEROS = 10025 ERROR_INVALID_PIECEWISE_OBJ = 10026 ERROR_UPDATEMODE_CHANGE = 10027 ERROR_CLOUD = 10028 ERROR_MODEL_MODIFICATION = 10029 ERROR_CSWORKER = 10030 ERROR_TUNE_MODEL_TYPES = 10031 ERROR_SECURITY = 10032 ERROR_NOT_IN_MODEL = 20001 ERROR_FAILED_TO_CREATE_MODEL = 20002 ERROR_INTERNAL = 20003 # Cuts parameter values CUTS_AUTO = -1 CUTS_OFF = 0 CUTS_CONSERVATIVE = 1 CUTS_AGGRESSIVE = 2 CUTS_VERYAGGRESSIVE = 3 # Presolve parameter values PRESOLVE_AUTO = -1 PRESOLVE_OFF = 0 PRESOLVE_CONSERVATIVE = 1 PRESOLVE_AGGRESSIVE = 2 # Method parameter values METHOD_NONE = -1 METHOD_AUTO = -1 METHOD_PRIMAL = 0 METHOD_DUAL = 1 METHOD_BARRIER = 2 METHOD_CONCURRENT = 3 METHOD_DETERMINISTIC_CONCURRENT = 4 METHOD_DETERMINISTIC_CONCURRENT_SIMPLEX = 5 # BarHomogeneous parameter values BARHOMOGENEOUS_AUTO = -1 BARHOMOGENEOUS_OFF = 0 BARHOMOGENEOUS_ON = 1 # BarOrder parameter values BARORDER_AUTOMATIC = -1 BARORDER_AMD = 0 BARORDER_NESTEDDISSECTION = 1 # MIPFocus parameter values MIPFOCUS_BALANCED = 0 MIPFOCUS_FEASIBILITY = 1 MIPFOCUS_OPTIMALITY = 2 MIPFOCUS_BESTBOUND = 3 # SimplexPricing parameter values SIMPLEXPRICING_AUTO = -1 SIMPLEXPRICING_PARTIAL = 0 SIMPLEXPRICING_STEEPEST_EDGE = 1 SIMPLEXPRICING_DEVEX = 2 SIMPLEXPRICING_STEEPEST_QUICK = 3 # VarBranch parameter values VARBRANCH_AUTO = -1 VARBRANCH_PSEUDO_REDUCED = 0 VARBRANCH_PSEUDO_SHADOW = 1 VARBRANCH_MAX_INFEAS = 2 VARBRANCH_STRONG = 3 # Partition parameter values PARTITION_EARLY = 16 PARTITION_ROOTSTART = 8 PARTITION_ROOTEND = 4 PARTITION_NODES = 2 PARTITION_CLEANUP = 1 # Callback phase values PHASE_MIP_NOREL = 0 PHASE_MIP_SEARCH = 1 PHASE_MIP_IMPROVE = 2 # FeasRelax method parameter values FEASRELAX_LINEAR = 0 FEASRELAX_QUADRATIC = 1 FEASRELAX_CARDINALITY = 2

通过回调函数插入惰性约束

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2025-05-04 10:26:02
# @Author  : syuansheng (Dalian Maritime University)
from math import sqrt
from typing import Dict,List
from numpy import ndarray,zeros
from gurobipy import Model,GRB,GurobiError,quicksum

def findAllSubroutes(solution:ndarray)->Dict[int,List[List]]:
	route_dict = {} # subtours of each car
	for k in range(solution.shape[2]):
		subtour_kcars = [] # subtours of car k
		# check subtours of car k 
		for i in range(1,solution.shape[1]-1):
			flag = False 
			subtour_kcars_start_i = [] # subtours of car k that starts from point i 
			for j in range(1,solution.shape[1]-1):
				# car k moved from point i to j
				if solution[i,j,k] > 0.9:
					subtour_kcars_start_i.append(i)
					subtour_kcars_start_i.append(j)
					current_point = j 
					# cheack whether i->j formed a subtour
					while not flag:
						for next_point in range(1,solution.shape[1]):
							if solution[current_point,next_point,k] >  0.9:
								subtour_kcars_start_i.append(next_point)
								current_point = next_point
							# we find a subtour start from ponstallint i
							if subtour_kcars_start_i[0] == subtour_kcars_start_i[-1]:
								flag = True
								subtour_kcars.append(subtour_kcars_start_i)
								break
							# car k finally arrived point (solution.shape[2]-1) means i->j didn't form a subtour
							if current_point == solution.shape[1]-1:
								flag = True
								break
				else:
					continue
		route_dict[k] = subtour_kcars
	return route_dict

class Data:

	def __init__(self):

		self.o = 0  # index of the depot
		self.d = 16 # index of the depot(duplication)
		self.C = [i for i in range(1,16)] # index of each customer
		self.V = [self.o]+self.C+[self.d]
		self.K = [i for i in range(3)] # index of each car
		self.A = [] # all valid arc
		self.Q = 100 # capacity of each car
		self.c = {}  # distance between i to j
		self.q = {} # demand of each customer
		self.loc = {} # cordinate of each point	

	def getDataReady(self):

		lst = []
		with open(r'./r101_CVRP.txt') as f:
			str_lst = f.readlines()
			for line in str_lst:
				lst.append(list(map(int,line.strip().split())))
		for i,item in enumerate(lst):
			self.loc[i] = [item[1],item[2]]
			self.q[i] = item[3]
		for i in self.V:
			for j in self.V:
				# exclude invalid arc
				if i == self.d or j == self.o or i == j or (i == self.o and j == self.d):
					continue
				else:
					self.A.append((i,j))
		for i,j in self.A:
			if j == self.d:
				self.c[i,j] = round(sqrt((self.loc[i][0]-self.loc[self.o][0])**2+(self.loc[i][1]-self.loc[self.o][1])**2),2)
			else:
				self.c[i,j] = round(sqrt((self.loc[i][0]-self.loc[j][0])**2+(self.loc[i][1]-self.loc[j][1])**2),2)

class VRPModel:

	def __init__(self,data):
		self._model = None
		self._data = data
		self.x = {}

	def buildModel(self):
		self._model = Model(name='CVRP1_1')
		self.x = self._model.addVars(self._data.A,self._data.K,vtype=GRB.BINARY,name='x')
		# set objective function
		self._model.setObjective(
			quicksum(self._data.c[i,j]*self.x[i,j,k] for i,j in self._data.A for k in self._data.K),
			sense=GRB.MINIMIZE
			)
		# set constraints
		self._model.addConstrs(
			(self.x.sum(i,'*','*')==1 for i in self._data.C),
			name = 'constraint1'
			)
		self._model.addConstrs(
			(self.x.sum(self._data.o,'*',k)==self.x.sum('*',self._data.d,k) for k in self._data.K),
			name = 'constraint2'
			)
		self._model.addConstrs(
			(self.x.sum('*',self._data.d,k)<=1 for k in self._data.K),
			name = 'constraint3'
			)
		self._model.addConstrs(
			(self.x.sum(i,'*',k)==self.x.sum('*',i,k) for i in self._data.C for k in self._data.K),
			name = 'constraint4'
			)
		self._model.addConstrs(
			(quicksum(self._data.q[i]*self.x[i,j,k] for i,j in self._data.A)<=self._data.Q for k in self._data.K),
			name =  'constraint5'
			)

	def solveModel(self):
		try:
			self._model.Params.MIPGap = 0 
			self._model.Params.lazyConstraints = 1
			self._model.optimize(self.generateDFJConstraint)
		except GurobiError as e:
			print('erro number:{},erro string:{}'.format(e.errno,e.message))

	def generateDFJConstraint(self,model,where):
		# each time when gurobi find a feasible solution,we check whether we should add some DFJ constraints
		if where ==  GRB.Callback.MIPSOL:
			solution = zeros((len(self._data.V),len(self._data.V),len(self._data.K))) # save the solution results in matrix format
			for key,value in self.x.items():
				idx1,idx2,idx3 = key
				solution[idx1,idx2,idx3] = self._model.cbGetSolution(value)
			route_dict = findAllSubroutes(solution)
			for k in self._data.K:
				for subtour_kcars_start_i in route_dict[k]:
					# Now we get the set S(subtour_kcars_start_i[:-1])
					self._model.cbLazy(
						quicksum(self.x[i,j,k] for i,j in self._data.A if (i in subtour_kcars_start_i[:-1] and j in subtour_kcars_start_i[:-1])) 
						<= len(subtour_kcars_start_i[:-1])-1
						)

if __name__ == '__main__':
	data = Data()
	data.getDataReady()
	model = VRPModel(data)
	model.buildModel()
	model.solveModel()

Gurobi实现列生成算法

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2025-07-25 14:34:23
# @Author  : syuansheng (Dalian Maritime University)

from gurobipy import *

class Data:
	def __init__(self):
		self.l = [4,5,7] # 需求类型
		self.L = [9,14,16] # 可用原木的长度
		self.c = [5,9,10] # 各种长度原木的价格

class CGModel:
	def __init__(self):
		self.data = Data()
		# 初始化主模型
		y = self.initiate_main_model()
		# 初始化所有子模型
		self.initiate_sub_model(y)

	def initiate_main_model(self):
		self.main_model = Model()
		self.x = self.main_model.addVars(3,vtype=GRB.CONTINUOUS,name='x')
		self.main_model.setObjective(
			5*self.x[0]+5*self.x[1]+5*self.x[2],
			sense = GRB.MINIMIZE
			)
		self.main_model.addConstr(2*self.x[0]>=30)
		self.main_model.addConstr(self.x[1]>=20)
		self.main_model.addConstr(self.x[2]>=40)
		self.main_model.optimize()
		# 获取对偶变量的值
		y = self.main_model.getAttr('Pi',self.main_model.getConstrs())
		return y

	def initiate_sub_model(self,y):
		self.submodels = []
		for k in range(len(self.data.L)):
			# k子模型
			submodel = Model()
			p = submodel.addVars(3,vtype=GRB.INTEGER,name='p')
			submodel.setObjective(
				self.data.c[k]-quicksum(y[i]*p[i] for i in range(3)),
				sense=GRB.MINIMIZE
				)
			submodel.addConstr(quicksum(p[i]*self.data.l[i] for i in range(3))<=self.data.L[k])
			self.submodels.append(submodel)

	def update(self):
		"""
		不断更新主模型和子模型并求解,直到得到原模型的最优解
		"""
		while True:
			best_ObjVal,best_P,submodel_idx = 0,None,0
			# 求解子模型
			for k,submodel in enumerate(self.submodels):
				submodel.optimize()
				if submodel.ObjVal < best_ObjVal:
					# 说明存在列加入主模型后使得主模型更好
					best_ObjVal = submodel.ObjVal # 子模型最优解目标函数值
					best_P = submodel.X # 子模型最优解
					submodel_idx = k
			# 退出
			if best_ObjVal >= 0 :
				break
			# 将新的列添加到主模型中,求解主模型并更新子模型
			else:
				col = Column(best_P,self.main_model.getConstrs()) # 创建要加入到主模型的列
				self.main_model.addVar(obj=self.data.c[submodel_idx],column=col,vtype=GRB.CONTINUOUS,name='new_x') # 将新的列加入主模型
				self.main_model.optimize() # 求解主模型
				self.main_model.write('ttest.lp')
				y = self.main_model.getAttr('Pi',self.main_model.getConstrs()) # 新的对偶变量值
				print('----------------------------',y,best_P,submodel_idx)
				self.initiate_sub_model(y) # 更新子模型
		# 将模型转换为对应的IP模型继续求解
		self.main_model.setAttr('VType',self.main_model.getVars(),GRB.INTEGER)
		self.main_model.optimize()


if __name__ == '__main__':
	model = CGModel()
	model.update()
	print(model.main_model.ObjVal)
	print(model.main_model.X)

参考资料