之前在 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.执行结果如下:与之前的结果一致
GitHub:https://github.com/fengbingchun/Python_Test