之前在 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 json
def 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.inf
model.Nmax = Param(model.N, initialize=parse_Nmax, within=NonNegativeReals)
model.Vmax = Param(initialize=data['params']['Vmax'], within=PositiveReals)
return model
def 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.Vmax
model.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] * 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)
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 = 0
for f in model.F:
v = int(value(model.x[f]))
if v != 0:
results["selected_food"].append({f:v})
count += 1
if 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 val
for 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