线性规划饮食问题求解:FastAPI作为服务端+libhv作为客户端实现

之前在 Pyomo介绍-CSDN博客 中介绍过通过Pyomo求解线性规划问题,这里使用FastAPI作为服务端,开源网络库libhv作为客户端,求解饮食成本最小化问题。

服务端测试代码test_fastapi_pyomo_server.py如下:

python 复制代码
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)

执行以下命令启动服务:

bash 复制代码
fastapi run test_fastapi_pyomo_server.py

执行结果如下图所示:

客户端测试代码如下所示:

cpp 复制代码
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创建

bash 复制代码
{
  "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

相关推荐
万粉变现经纪人1 小时前
如何解决pip安装报错ModuleNotFoundError: No module named ‘websockets’问题
ide·pycharm·beautifulsoup·pandas·fastapi·pip·httpx
檀越剑指大厂1 天前
【Python系列】Flask 和 FastAPI对比
python·flask·fastapi
larance3 天前
FastAPI + SQLAlchemy 数据库对象转字典
数据库·fastapi
waynaqua5 天前
FastAPI开发AI应用五:模型新增图片理解
python·openai·fastapi
waynaqua5 天前
FastAPI开发AI应用四:新增豆包、kimi模型
python·fastapi·ai编程
nightunderblackcat5 天前
新手向:用FastAPI快速构建高性能Web服务
前端·fastapi
爱吃小白兔的猫6 天前
【框架篇二】FastAPI路由与请求处理
fastapi
蓝倾8 天前
如何将淘宝获取店铺所有商品API接口应用到自己店铺?实战讲解详细步骤
api·fastapi
在星空下8 天前
Fastapi-Vue3-Admin
前端·python·fastapi
onelafite15 天前
一键式商品信息获取:京东API返回值深度挖掘
api·fastapi