Pyomo中线性规划接口的使用

      之前在 Pyomo介绍-CSDN博客 中以饮食为例介绍过Pyomo的使用,执行以下命令:

pyomo solve --solver=glpk test_pyomo_linear_programming.py ../test_data/diet.dat

      直接执行以上命令,不便之处有以下几点:

      (1).不能直接解析python文件,不便于后期与FastAPI集成;

      (2).不支持传入额外参数;

      (3).不能直接获取需要的结果,需要额外解析results.yml

      针对以上问题,对原始代码进行了重新实现,包括:支持解析dat文件和json文件、支持输入参数、结果可直接通过print打印输出:

      1.解析dat文件的实现如下:

def parse_dat(file):
	# creating a model object
	model = ConcreteModel()

	# load data file
	data = DataPortal()
	data.load(filename=file)

	# define sets and parameters
	model.F = Set(initialize=data['F'])
	model.N = Set(initialize=data['N'])

	model.c = Param(model.F, initialize=data['c'], within=PositiveReals)
	model.a = Param(model.F, model.N, initialize=data['a'], within=NonNegativeReals)
	model.V = Param(model.F, initialize=data['V'], within=PositiveReals)
	model.Nmin = Param(model.N, initialize=data['Nmin'], within=NonNegativeReals, default=0.0)
	model.Nmax = Param(model.N, initialize=data['Nmax'], within=NonNegativeReals, default=float('inf'))
	model.Vmax = Param(initialize=data['Vmax'], within=PositiveReals)

	return model

     diet.dat文件内容如下:

param:  F:                          c     V  :=
  "Cheeseburger"                 1.84   4.0  
  "Ham Sandwich"                 2.19   7.5  
  "Hamburger"                    1.84   3.5  
  "Fish Sandwich"                1.44   5.0  
  "Chicken Sandwich"             2.29   7.3  
  "Fries"                         .77   2.6  
  "Sausage Biscuit"              1.29   4.1  
  "Lowfat Milk"                   .60   8.0 
  "Orange Juice"                  .72  12.0 ;

param Vmax := 75.0;

param:  N:       Nmin   Nmax :=
        Cal      2000      .
        Carbo     350    375
        Protein    55      .
        VitA      100      .
        VitC      100      .
        Calc      100      .
        Iron      100      . ;

param a:
                               Cal  Carbo Protein   VitA   VitC  Calc  Iron :=
  "Cheeseburger"               510     34     28     15      6    30    20
  "Ham Sandwich"               370     35     24     15     10    20    20
  "Hamburger"                  500     42     25      6      2    25    20
  "Fish Sandwich"              370     38     14      2      0    15    10
  "Chicken Sandwich"           400     42     31      8     15    15     8
  "Fries"                      220     26      3      0     15     0     2
  "Sausage Biscuit"            345     27     15      4      0    20    15
  "Lowfat Milk"                110     12      9     10      4    30     0
  "Orange Juice"                80     20      1      2    120     2     2 ;

      2.解析json文件的实现如下:

def parse_json(file):
	model = ConcreteModel()

	data = DataPortal()
	data.load(filename=file)

	model.F = Set(initialize=data['sets']['F'])
	model.N = Set(initialize=data['sets']['N'])

	model.c = Param(model.F, initialize=data['params']['c'], within=PositiveReals)
	def parse_a(model, food, nutr):
		return data['params']['a'][food][nutr]
	model.a = Param(model.F, model.N, initialize=parse_a, within=NonNegativeReals)
	model.V = Param(model.F, initialize=data['params']['V'], within=PositiveReals)
	model.Nmin = Param(model.N, initialize=data['params']['Nmin'], within=NonNegativeReals, default=0.0)
	def parse_Nmax(model, nutr):
		val = data['params']['Nmax'][nutr]
		return val if val != "inf" else math.inf 
	model.Nmax = Param(model.N, initialize=parse_Nmax, within=NonNegativeReals)
	model.Vmax = Param(initialize=data['params']['Vmax'], within=PositiveReals)

	return model

      diet.json文件内容如下:

{
  "sets": {
    "F": [
      "Cheeseburger",
      "Ham Sandwich",
      "Hamburger",
      "Fish Sandwich",
      "Chicken Sandwich",
      "Fries",
      "Sausage Biscuit",
      "Lowfat Milk",
      "Orange Juice"
    ],
    "N": [
      "Cal",
      "Carbo",
      "Protein",
      "VitA",
      "VitC",
      "Calc",
      "Iron"
    ]
  },
  "params": {
    "c": {
      "Cheeseburger": 1.84,
      "Ham Sandwich": 2.19,
      "Hamburger": 1.84,
      "Fish Sandwich": 1.44,
      "Chicken Sandwich": 2.29,
      "Fries": 0.77,
      "Sausage Biscuit": 1.29,
      "Lowfat Milk": 0.6,
      "Orange Juice": 0.72
    },
    "V": {
      "Cheeseburger": 4.0,
      "Ham Sandwich": 7.5,
      "Hamburger": 3.5,
      "Fish Sandwich": 5.0,
      "Chicken Sandwich": 7.3,
      "Fries": 2.6,
      "Sausage Biscuit": 4.1,
      "Lowfat Milk": 8.0,
      "Orange Juice": 12.0
    },
    "Vmax": 75.0,
    "Nmin": {
      "Cal": 2000.0,
      "Carbo": 350.0,
      "Protein": 55.0,
      "VitA": 100.0,
      "VitC": 100.0,
      "Calc": 100.0,
      "Iron": 100.0
    },
    "Nmax": {
      "Cal": "inf",
      "Carbo": 375.0,
      "Protein": "inf",
      "VitA": "inf",
      "VitC": "inf",
      "Calc": "inf",
      "Iron": "inf"
    },
    "a": {
      "Cheeseburger": {
        "Cal": 510.0,
        "Carbo": 34.0,
        "Protein": 28.0,
        "VitA": 15.0,
        "VitC": 6.0,
        "Calc": 30.0,
        "Iron": 20.0
      },
      "Ham Sandwich": {
        "Cal": 370.0,
        "Carbo": 35.0,
        "Protein": 24.0,
        "VitA": 15.0,
        "VitC": 10.0,
        "Calc": 20.0,
        "Iron": 20.0
      },
      "Hamburger": {
        "Cal": 500.0,
        "Carbo": 42.0,
        "Protein": 25.0,
        "VitA": 6.0,
        "VitC": 2.0,
        "Calc": 25.0,
        "Iron": 20.0
      },
      "Fish Sandwich": {
        "Cal": 370.0,
        "Carbo": 38.0,
        "Protein": 14.0,
        "VitA": 2.0,
        "VitC": 0.0,
        "Calc": 15.0,
        "Iron": 10.0
      },
      "Chicken Sandwich": {
        "Cal": 400.0,
        "Carbo": 42.0,
        "Protein": 31.0,
        "VitA": 8.0,
        "VitC": 15.0,
        "Calc": 15.0,
        "Iron": 8.0
      },
      "Fries": {
        "Cal": 220.0,
        "Carbo": 26.0,
        "Protein": 3.0,
        "VitA": 0.0,
        "VitC": 15.0,
        "Calc": 0.0,
        "Iron": 2.0
      },
      "Sausage Biscuit": {
        "Cal": 345.0,
        "Carbo": 27.0,
        "Protein": 15.0,
        "VitA": 4.0,
        "VitC": 0.0,
        "Calc": 20.0,
        "Iron": 15.0
      },
      "Lowfat Milk": {
        "Cal": 110.0,
        "Carbo": 12.0,
        "Protein": 9.0,
        "VitA": 10.0,
        "VitC": 4.0,
        "Calc": 30.0,
        "Iron": 0.0
      },
      "Orange Juice": {
        "Cal": 80.0,
        "Carbo": 20.0,
        "Protein": 1.0,
        "VitA": 2.0,
        "VitC": 120.0,
        "Calc": 2.0,
        "Iron": 2.0
      }
    }
  }
}

      注:pyomo对json二维参数model.a需特殊处理

      3.支持的输入参数如下:

def parse_args():
	parser = argparse.ArgumentParser(description="pyomo glpk diet")
	parser.add_argument("--file", required=True, type=str, help="*.dat file or *.json file")
	parser.add_argument("--number", type=int, default=5, help="must select number of food types from all food types")

	args = parser.parse_args()
	return args

      4.主体实现如下:

def main(file, number):
	if Path(file).suffix.lower() == ".dat":
		model = parse_dat(file)
	elif Path(file).suffix.lower() == ".json":
		model = parse_json(file)
	else:
		raise ValueError(colorama.Fore.RED + f"unsupported file format: {file}")

	# define variables
	model.x = Var(model.F, within=NonNegativeIntegers)
	model.y = Var(model.F, within=Binary)

	# define the cost objective
	model.cost = Objective(expr=sum(model.c[i]*model.x[i] for i in model.F), sense=minimize)

	# define constraint
	def nutrient_rule(model, j):
		value = sum(model.a[i,j]*model.x[i] for i in model.F)
		return inequality(model.Nmin[j], value, model.Nmax[j])
	model.nutrient_limit = Constraint(model.N, rule=nutrient_rule)

	def volume_rule(model):
		return sum(model.V[i]*model.x[i] for i in model.F) <= model.Vmax
	model.volume = Constraint(rule=volume_rule)

	def select_rule(model):
		return sum(model.y[i] for i in model.F) == number
	model.select = Constraint(rule=select_rule)

	def linking_upper_rule(model, f):
		return model.x[f] <= model.y[f] * 1e6
	model.linking_upper = Constraint(model.F, rule=linking_upper_rule)

	def linking_lower_rule(model, f):
		return model.x[f] >= model.y[f]
	model.linking_lower = Constraint(model.F, rule=linking_lower_rule)

	# model.pprint() # print model structure

	# solve the model
	solver = SolverFactory('glpk')
	results = solver.solve(model)
	# print(f"results: {results}")
	if results.solver.termination_condition != TerminationCondition.optimal:
		raise ValueError(colorama.Fore.RED + f"no optimal solution was found")

	# print result
	print(f"total cost: {value(model.cost):.2f}")
	count = 0
	print("selected food:")
	for f in model.F:
		v = int(value(model.x[f]))
		if v != 0:
			print(f"  {f}: {v}")
			count += 1

	if count != number:
		raise ValueError(colorama.Fore.RED + f"solution result is wrong, number of food types does not match: {count}:{number}")

	print("nutrients:")
	for n in model.N:
		actual = sum(value(model.a[f,n] * model.x[f]) for f in model.F)
		print(f"  {n}: actual value: {actual:.2f}; boundary:[{value(model.Nmin[n])},{value(model.Nmax[n])}]")

      模型构建方式:

      (1).AbstractModel:模型定义时不包含具体数据。

      (2).ConcreteModel:模型定义时直接包含具体数据。

      DataPortal类:加载数据到Pyomo模型,支持多种文件格式,如.dat, .json, .csv, Excel等。

      建模组件:Set, Param, Var, Constraint, Objective等。

      SolverFactory类:创建求解器实例,这里使用的是开源求解器glpk。

      5.入口函数实现如下:

if __name__ == "__main__":
	colorama.init(autoreset=True)
	args = parse_args()
	start = time.perf_counter()
	main(args.file, args.number)
	end = time.perf_counter()
	print(f"elapsed time: {end-start:.2f} seconds")

	print(colorama.Fore.GREEN + "====== execution completed ======")

      6.执行结果如下:与之前的结果一致

      GitHubhttps://github.com/fengbingchun/Python_Test

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值