线性规划饮食问题求解:FastAPI作为服务端+libhv作为客户端实现
之前在 Pyomo介绍-CSDN博客 中介绍过通过Pyomo求解线性规划问题,这里使用FastAPI作为服务端,开源网络库libhv作为客户端,求解饮食成本最小化问题。
服务端测试代码test_fastapi_pyomo_server.py如下:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from pyomo.environ import *
import math
import jsondef parse_json(data):model = ConcreteModel()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.infmodel.Nmax = Param(model.N, initialize=parse_Nmax, within=NonNegativeReals)model.Vmax = Param(initialize=data['params']['Vmax'], within=PositiveReals)return modeldef linear_programming(data):model = parse_json(data)model.x = Var(model.F, within=NonNegativeIntegers)model.y = Var(model.F, within=Binary)model.cost = Objective(expr=sum(model.c[i]*model.x[i] for i in model.F), sense=minimize)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.Vmaxmodel.volume = Constraint(rule=volume_rule)def select_rule(model):return sum(model.y[i] for i in model.F) == data['number']model.select = Constraint(rule=select_rule)def linking_upper_rule(model, f):return model.x[f] <= model.y[f] * 1e6model.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)solver = SolverFactory('glpk')ret = solver.solve(model)if ret.solver.termination_condition != TerminationCondition.optimal:return JSONResponse(status_code=400, content={"error": "no optimal solution"})results = {"selected_food": [], "nutrients": []}results["cost"] = f"{value(model.cost):.2f}"count = 0for f in model.F:v = int(value(model.x[f]))if v != 0:results["selected_food"].append({f:v})count += 1if count != data['number']:return JSONResponse(status_code=400, content={"error": "unmatched number", "count": count, "number": data['number']})def inf_convert(val):if val == math.inf:return "INF"elif val == -math.inf:return "-INF"return valfor n in model.N:actual = sum(value(model.a[f,n] * model.x[f]) for f in model.F)results["nutrients"].append({n:f"{actual:.4f}","boundary":[inf_convert(value(model.Nmin[n])), inf_convert(value(model.Nmax[n]))]})return JSONResponse(status_code=200, content=results)app = FastAPI()@app.post("/api/optimize")
async def optimize_diet(request: Request):json_bytes = await request.body()json_str = json_bytes.decode('utf-8')data = json.loads(json_str)if not isinstance(data, dict):return JSONResponse(status_code=400, content={"error": "Invalid JSON format"})return linear_programming(data)
执行以下命令启动服务:
fastapi run test_fastapi_pyomo_server.py
执行结果如下图所示:
客户端测试代码如下所示:
int test_libhv_http_client_diet()
{constexpr char file_name[]{ "../../../testdata/diet.json" };std::ifstream in_file(file_name);if (!in_file.is_open()) {std::cerr << "Error: failed to open json file: " << file_name << std::endl;return -1;}auto j = hv::Json::parse(in_file);constexpr int number{ 5 };j["number"] = number;const std::string server_url{ "http://192.168.1.28:8000" };HttpRequest request{};request.method = HTTP_POST;request.url = server_url + "/api/optimize";request.body = j.dump();request.headers["Content-Type"] = "application/json";request.timeout = 2;hv::HttpClient client{};HttpResponse response{};if (auto ret = client.send(&request, &response); ret == 0) {if (response.status_code == HTTP_STATUS_OK) {hv::Json j = hv::Json::parse(response.body);std::cout << "result: " << j.dump() << std::endl;constexpr char result_name[]{ "../../../testdata/result.json" };std::ofstream out_file(result_name);if (!out_file.is_open()) {std::cerr << "Error: faild to open file: " << result_name << std::endl;return -1;}out_file << j.dump(2);} else {std::cerr << "status code: " << response.status_code << ", status message: " << response.status_message() << ", body: " << response.body << std::endl;return -1;}} else {std::cerr << "Error: failed to send, error code: " << ret << std::endl;return -1;}return 0;
}
执行结果如下图所示:与之前结果一致
与服务端在同一局域网内的任何机子都可以作为客户端。
diet.json内容如下:也可以完全通过nlohmann/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}}}
}
GitHub:
服务端:https://github.com/fengbingchun/Python_Test
客户端:https://github.com/fengbingchun/OpenSSL_Test