MMC-C/problem1.ipynb

703 lines
22 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "e19ec4ae5347c678",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"pd.options.display.max_columns = 100"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "initial_id",
"metadata": {},
"outputs": [],
"source": [
"\n",
"df_crop_details = pd.read_excel('./data/2.xlsx', sheet_name=1)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "1d2a51b3414be94",
"metadata": {},
"outputs": [],
"source": [
"# 去除空格\n",
"df_crop_details['cropName'] = df_crop_details['cropName'].apply(lambda x: x.strip())\n",
"# CropType = [x.strip() for x in CropType]\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "514cd9136d9ca341",
"metadata": {},
"outputs": [],
"source": [
"df_crop_planting = pd.read_excel('./data/2.xlsx', sheet_name=0)\n",
"# 照例去除一下空格\n",
"df_crop_planting['cropName'] = df_crop_planting['cropName'].apply(lambda x: x.strip())\n",
"# ffill\n",
"df_crop_planting['landName'] = df_crop_planting['landName'].ffill()\n",
"# df_crop_planting"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "1503f8b642c842db",
"metadata": {},
"outputs": [],
"source": [
"df_land = pd.read_excel('./data/1.xlsx', sheet_name=0)\n",
"# 去除landType和landName的空格\n",
"df_land['landType'] = df_land['landType'].apply(lambda x: x.strip())\n",
"df_land['landName'] = df_land['landName'].apply(lambda x: x.strip())\n",
"# df_land"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "3cdf51a9a9d4d30f",
"metadata": {},
"outputs": [],
"source": [
"\n",
"unit_profit_lsc = []\n",
"for line in df_crop_details.values:\n",
" s = str(line[7]).split('-')\n",
" unit_profit_lsc.append((float(s[0]) + float(s[1])) / 2 * line[5] - line[6])\n",
"df_crop_details['unitProfit'] = unit_profit_lsc\n",
"# df_crop_details"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "569016a9b90f841b",
"metadata": {},
"outputs": [],
"source": [
"df_crop_type_land = pd.read_excel('./data/1.xlsx', sheet_name=1)\n",
"# 老规矩去掉cropName和cropType的空格\n",
"df_crop_type_land['cropType'] = df_crop_type_land['cropType'].apply(lambda x: x.strip())\n",
"df_crop_type_land['cropName'] = df_crop_type_land['cropName'].apply(lambda x: x.strip())\n",
"# df_crop_type_land"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "a7661d84217b578c",
"metadata": {},
"outputs": [],
"source": [
"# 搞一下季节和年份的集合\n",
"SeasonType = [\"单季\", \"第一季\", \"第二季\"]\n",
"SeasonDict = {\"单季\": 1, \"第一季\": 1, \"第二季\": 2}\n",
"SeasonNum = [1, 2]\n",
"years = [2024, 2025, 2026, 2027, 2028, 2029, 2030]"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "4a4e06a1f2d4d8ab",
"metadata": {},
"outputs": [],
"source": [
"# 枚举地块类型\n",
"LandType = {\"A\": \"平旱地\", \"B\": \"梯田\", \"C\": \"山坡地\", \"D\": \"水浇地\", \"E\": \"普通大棚\", \"F\": \"智慧大棚\"}\n",
"# LandType"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "cc938565a63a8129",
"metadata": {},
"outputs": [],
"source": [
"# 枚举地块\n",
"LandName = df_crop_planting['landName'].unique()\n",
"# LandName"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "dae53a6215cea525",
"metadata": {},
"outputs": [],
"source": [
"# 枚举地块面积取df_land的landName为keylandArea为value\n",
"LandArea = {x: df_land[df_land['landName'] == x]['landArea'].values[0] for x in LandName}\n",
"# LandArea"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "7a0e6cc209d93b56",
"metadata": {},
"outputs": [],
"source": [
"# 读入作物名称\n",
"CropName = df_crop_details['cropName'].unique()\n",
"# CropName"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "8a280f918bb3139a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'粮食(豆类)': array(['黄豆', '黑豆', '红豆', '绿豆', '爬豆'], dtype=object),\n",
" '粮食': array(['小麦', '玉米', '谷子', '高粱', '黍子', '荞麦', '南瓜', '红薯', '莜麦', '大麦', '水稻'],\n",
" dtype=object),\n",
" '蔬菜(豆类)': array(['豇豆', '刀豆', '芸豆'], dtype=object),\n",
" '蔬菜': array(['土豆', '西红柿', '茄子', '菠菜', '青椒', '菜花', '包菜', '油麦菜', '小青菜', '黄瓜',\n",
" '生菜', '辣椒', '空心菜', '黄心菜', '芹菜', '大白菜', '白萝卜', '红萝卜'], dtype=object),\n",
" '食用菌': array(['榆黄菇', '香菇', '白灵菇', '羊肚菌'], dtype=object),\n",
" '粮食(除了水稻)': array(['小麦', '玉米', '谷子', '高粱', '黍子', '荞麦', '南瓜', '红薯', '莜麦', '大麦'],\n",
" dtype=object),\n",
" '豆类': array(['黄豆', '黑豆', '红豆', '绿豆', '爬豆', '豇豆', '刀豆', '芸豆'], dtype=object)}"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# 作物分类\n",
"CropType = {x: df_crop_type_land[df_crop_type_land['cropType'] == x]['cropName'].values for x in\n",
" df_crop_type_land['cropType'].values}\n",
"CropType['粮食(除了水稻)'] = CropType['粮食'][:-1] # 这样不太好,但能用\n",
"CropType['豆类'] = np.array(list(CropType['粮食(豆类)']) + list(CropType['蔬菜(豆类)']), dtype=object)\n",
"CropType"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "bf6d61d66f06bdeb",
"metadata": {},
"outputs": [],
"source": [
"# any([x[-1] == ' ' for x in CropName])\n",
"# 居然有空格\n",
"# 现在去掉了"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "22f9730e3d9c1016",
"metadata": {},
"outputs": [],
"source": [
"# 亩产量\n",
"unit_yield_lsc = {\n",
" l: {\n",
" s: {\n",
" c: df_crop_details[\n",
" (df_crop_details['cropName'] == c) &\n",
" (df_crop_details['cropLandType'] == l) &\n",
" (df_crop_details['season'] == s)\n",
" ]['unitYield'].values[0] if df_crop_details[\n",
" (df_crop_details['cropName'] == c) &\n",
" (df_crop_details['cropLandType'] == l) &\n",
" (df_crop_details['season'] == s)\n",
" ]['unitYield'].values.size > 0 else 0\n",
" for c in CropName\n",
" } for s in SeasonType\n",
" }\n",
" for l in LandType.values()\n",
"}\n",
"\n",
"# unit_yield"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "99dbe4ca6c54db0b",
"metadata": {},
"outputs": [],
"source": [
"# 亩利润\n",
"unit_profit_lsc = {\n",
" l: {\n",
" s: {\n",
" c: df_crop_details[\n",
" (df_crop_details['cropName'] == c) &\n",
" (df_crop_details['cropLandType'] == l) &\n",
" (df_crop_details['season'] == s)\n",
" ]['unitProfit'].values[0]\n",
" if df_crop_details[\n",
" (df_crop_details['cropName'] == c) &\n",
" (df_crop_details['cropLandType'] == l) &\n",
" (df_crop_details['season'] == s)\n",
" ]['unitYield'].values.size > 0 else 0\n",
" for c in CropName\n",
" } for s in SeasonType\n",
" }\n",
" for l in LandType.values()\n",
"}\n",
"\n",
"# unit_profit"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "13f2ccc7aa215d4c",
"metadata": {},
"outputs": [],
"source": [
"# 每种作物的总需求\n",
"crop_demand = {\n",
" c: 0\n",
" for c in CropName\n",
"}\n",
"\n",
"# 这里需要另一张表\n",
"# 代码独立出来移到上面去了\n",
"\n",
"for line in df_crop_planting.values:\n",
" # 面积*该土地类型的亩产量 面积 地块类型字典 地块类型 季节 作物名称\n",
" crop_demand[line[2]] += line[4] * unit_yield_lsc[LandType[line[0][0]]][line[5]][line[2]]\n",
"\n",
"# crop_demand"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "5d09872708d40da4",
"metadata": {},
"outputs": [],
"source": [
"# 准备开搞\n",
"from pulp import LpMaximize, LpProblem, LpVariable, lpSum, value, LpInteger, PULP_CBC_CMD, LpContinuous, LpBinary\n",
"import pulp"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "b6eff87c7a762769",
"metadata": {},
"outputs": [],
"source": [
"model = LpProblem(\"Crop_Planting_Optimization_with_Specific_Rules\", LpMaximize)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "7bfdb9425c3e9683",
"metadata": {},
"outputs": [],
"source": [
"# %timeit X = LpVariable.dicts(\"X\", (CropName, LandName, years, seasons), lowBound=0, cat=LpContinuous)\n",
"# %timeit XX = LpVariable.dicts(\"X\", [(c, l, y, s) for c in CropName for l in LandName for y in years for s in seasons],lowBound=0, cat=LpContinuous)"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "4cb129e20385a7ee",
"metadata": {},
"outputs": [],
"source": [
"# 地块l在y年的s季的种植c的量\n",
"# X = LpVariable.dicts(\"crop_plant_area\", (CropName, LandName, years, SeasonType), lowBound=0, cat=LpContinuous)\n",
"X = LpVariable.dicts(\"X\", [(c, l, y, s)\n",
" for c in CropName\n",
" for l in LandName\n",
" for y in years\n",
" for s in SeasonType],\n",
" lowBound=0,\n",
" cat=LpContinuous\n",
" )\n",
"# X"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "9dab983ca7b3b607",
"metadata": {},
"outputs": [],
"source": [
"# 地块l在y年的s季是否种植了c\n",
"# Y = LpVariable.dicts(\"crop_plant_bool\", (CropName, LandName, years, SeasonType), cat=LpBinary)\n",
"Y = LpVariable.dicts(\"Y\", [(c, l, y, s) for c in CropName for l in LandName for y in years for s in SeasonType],\n",
" cat=LpBinary)\n",
"\n",
"# Y"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "603175d36624172f",
"metadata": {},
"outputs": [],
"source": [
"# 确保与X进行01约束\n",
"for c in CropName:\n",
" for l in LandName:\n",
" for y in years:\n",
" for s in SeasonType:\n",
" # 如果种植了作物则种植面积大于0\n",
" model += X[c, l, y, s] <= 10000000 * Y[c, l, y, s], f\"PlantingConstraint1_{c}_{l}_{y}_{s}\"\n",
" # 如果未种植作物则种植面积为0\n",
" model += X[c, l, y, s] >= 0.000001 * Y[c, l, y, s], f\"PlantingConstraint2_{c}_{l}_{y}_{s}\""
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "4505b0f2fe6bf93d",
"metadata": {},
"outputs": [],
"source": [
"# 目标函数:总利润 = sum(x[i]*profit[c,l,s])\n",
"model += lpSum( # 地块类型 季节 作物类型\n",
" X[c, l, y, s] * unit_profit_lsc[LandType[l[0]]][s][c]\n",
" for c in CropName\n",
" for l in LandName\n",
" for y in years\n",
" for s in SeasonType\n",
"), \"total_profit\"\n"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "1f185c0856fbcf27",
"metadata": {},
"outputs": [],
"source": [
"# 约束,启动!\n",
"# 条件1每年的所有季节中每种作物的种植量必须小于需求量\n",
"for c in CropName:\n",
" for y in years:\n",
" model += lpSum(X[c, l, y, s] * unit_yield_lsc[LandType[l[0]]][s][c] for l in LandName for s in SeasonType) <= \\\n",
" crop_demand[c], f\"CropDemandConstraint_{c}_{y}\""
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "7e13de7700d38f19",
"metadata": {},
"outputs": [],
"source": [
"# 条件2 每年每季每块地的种植面积不能超过该地块的总面积\n",
"for l in LandName:\n",
" for y in years:\n",
" for s in SeasonType:\n",
" model += lpSum(X[c, l, y, s] for c in CropName) <= LandArea[l], f\"LandAreaConstraint_{l}_{y}_{s}\""
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "1f6ac4323825f28b",
"metadata": {},
"outputs": [],
"source": [
"# 条件3 平旱地、梯田和山坡地每年适宜单季种植[粮食类作物(水稻除外)]。因为除了[粮食类作物(水稻除外)]以外的作物似乎已经被上面的产量和利润的0给约束了所以大概不需要做限制了吧\n",
"for l in LandName:\n",
" if l[0] not in [\"A\", \"B\", \"C\"]: # [\"平旱地\", \"梯田\", \"山坡地\"]\n",
" continue\n",
" for s in SeasonType:\n",
" if s != \"单季\":\n",
" model += lpSum(Y[c, l, y, s] for c in CropName for y in years) == 0, f\"SingleSeasonConstraint_{l}_{s}\"\n",
" # model += lpSum(X[c, l, y, s] for c in CropName for y in years) == 0"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "c0e9b6b37092f0e7",
"metadata": {},
"outputs": [],
"source": [
"# 条件4 水浇地每年可以单季种植水稻或两季种植蔬菜作物。\n",
"for l in LandName:\n",
" if l[0] != \"D\": # [\"水浇地\"]\n",
" continue\n",
" for y in years:\n",
" model += (\n",
" (lpSum(Y[\"水稻\", l, y, \"单季\"]) != 0) and\n",
" (lpSum(\n",
" Y[c, l, y, s] if c != \"水稻\" else 0\n",
" for c in CropName\n",
" for s in [\"第一季\", \"第二季\"]\n",
" ) == 0)\n",
" ) or (\n",
" (lpSum(Y[\"水稻\", l, y, \"单季\"]) == 0) and\n",
" (lpSum(\n",
" Y[c, l, y, s] if c != \"水稻\" else 0\n",
" for c in CropName\n",
" for s in [\"第一季\", \"第二季\"]\n",
" ) != 0)\n",
" ), f\"irrigatedConstraint_{l}_{y}\"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "8f8da502641f32fa",
"metadata": {},
"outputs": [],
"source": [
"# 条件5 若在某块水浇地种植两季蔬菜,第一季可种植多种蔬菜(大白菜、白萝卜和红萝卜除外);第二季只能种植大白菜、白萝卜和红萝卜中的一种(便于管理)。\n",
"# 感觉没必要因为前面有field和profit为0的惩罚\n"
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "df2d7e8918382a0",
"metadata": {},
"outputs": [],
"source": [
"# 条件6 根据季节性要求,大白菜、白萝卜和红萝卜只能在水浇地的第二季种植。\n",
"# 感觉也没必要,同理\n"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "1d402d3df13f6732",
"metadata": {},
"outputs": [],
"source": [
"# 条件7 普通大棚每年种植两季作物,第一季可种植多种蔬菜(大白菜、白萝卜和红萝卜除外),第二季只能种植食用菌。\n",
"# 同理\n"
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "930ab378623172ec",
"metadata": {},
"outputs": [],
"source": [
"# 条件8 食用菌类只能在普通大棚第二季的时候种植\n",
"# 同理\n"
]
},
{
"cell_type": "code",
"execution_count": 33,
"id": "bda8b4c538a097bc",
"metadata": {},
"outputs": [],
"source": [
"# 条件9 智慧大棚每年都可种植两季蔬菜(大白菜、白萝卜和红萝卜除外)\n",
"# 同理"
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "e8fb2dea1326a05b",
"metadata": {},
"outputs": [],
"source": [
"# 条件10 别种得太碎了,啥意思? 每种作物不超过3块地吧。\n",
"for c in CropName:\n",
" for y in years:\n",
" for s in SeasonType:\n",
" model += lpSum(Y[c, l, y, s] for l in LandName) <= 3, f\"MaxCropTypeConstraint_{c}_{y}_{s}\""
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "49f1490823c6af3c",
"metadata": {},
"outputs": [],
"source": [
"# 条件11 最小种植面积0.2吧\n",
"for l in LandName:\n",
" for y in years:\n",
" for s in SeasonType:\n",
" for c in CropName:\n",
" model += X[c, l, y, s] >= 0.2 * (1 if Y[c, l, y, s] else 0), f\"MinCropAreaConstraint_{c}_{l}_{y}_{s}\""
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "bdf1048b83723f91",
"metadata": {},
"outputs": [],
"source": [
"# 条件12 最重要的,不能重复种植同一块地同一年同一季的同一作物\n",
"# 每种作物在同一地块(含大棚)都不能连续重茬种植\n",
"\n",
"for l in LandName:\n",
" for c in CropName:\n",
" for y in years: # 单季不能连年种植 同一年不能连续种植 跨年也不能连续种植\n",
" if y < years[-1]:\n",
" model += Y[c, l, y, \"单季\"] + Y[c, l, y + 1, \"单季\"] <= 1, f\"SingleSeasonConstraint1_{c}_{l}_{y}\"\n",
" model += Y[c, l, y, \"第一季\"] + Y[c, l, y + 1, \"第二季\"] <= 1, f\"DoubleSeasonConstraint1_{c}_{l}_{y}\"\n",
" model += Y[c, l, y, \"第一季\"] + Y[c, l, y, \"第二季\"] <= 1, f\"DoubleSeasonConstraint2_{c}_{l}_{y}\"\n"
]
},
{
"cell_type": "code",
"execution_count": 37,
"id": "50da0aff9c68ae24",
"metadata": {},
"outputs": [],
"source": [
"# 条件13 最重要的,从 2023 年开始要求每个地块(含大棚) 的所有土地三年内至少种植一次豆类作物\n",
"# CropType['豆类']\n",
"for l in LandName:\n",
" for y in years[:-2]:\n",
" model += lpSum(Y[c,l,y_r,s] for c in CropType['豆类'] for y_r in range(y,y+2) for s in SeasonType)>=1, f\"BeanConstraint_{l}_{y}\"\n",
" # model += lpSum(Y[c, l, y_r, \"单季\"] for c in CropType['豆类'] for y_r in\n",
" # range(y - 2, y + 1)) >= 1, f\"BeanConstraint1_{l}_{y}\"\n",
" # model += lpSum(Y[c, l, y_r, s] for c in CropType['豆类'] for y_r in range(y - 2, y + 1) for s in\n",
" # [\"第一季\", \"第二季\"]) >= 1, f\"BeanConstraint2_{l}_{y}\""
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "d4792a1210afca3b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# model.solve()\n",
"\n",
"# 创建求解器实例\n",
"solver = pulp.PULP_CBC_CMD(\n",
" timeLimit=120,\n",
" threads=4,\n",
")\n",
"solver.solve(model)\n",
"\n",
"\n",
"# print(Y)\n",
"# for c in CropName:\n",
"# for l in LandName:\n",
"# for y in years:\n",
"# for s in SeasonType:\n",
"# print(f\"Y[({c}, {l}, {y}, {s})] = {pulp.value(Y[(c, l, y, s)])}\")\n"
]
},
{
"cell_type": "code",
"execution_count": 40,
"id": "cb3f4e95111e3292",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"结果已保存到文件planting_plan_2024-2030.xlsx\n"
]
}
],
"source": [
"\n",
"# 输出文件名,包含起始年份和结束年份\n",
"output_filename = f\"planting_plan_{years[0]}-{years[-1]}.xlsx\"\n",
"\n",
"# 使用 pd.ExcelWriter 创建 Excel 文件\n",
"with pd.ExcelWriter(output_filename) as writer:\n",
" # 遍历每一年和每个季节\n",
" for y in years:\n",
" for s in SeasonType:\n",
" # 创建一个空的 DataFrame横轴为作物名称纵轴为地块名称\n",
" result_table = pd.DataFrame(columns=CropName, index=LandName)\n",
"\n",
" # 填充表格数据\n",
" for l in LandName:\n",
" for c in CropName:\n",
" # 获取变量 X[c, l, y, s] 的值\n",
" crop_value = value(X[c, l, y, s])\n",
" if crop_value is not None and crop_value > 0:\n",
" result_table.at[l, c] = crop_value # 填入结果\n",
"\n",
" # 用 0 填充空值\n",
" result_table.fillna(0, inplace=True)\n",
"\n",
" # 定义每个 sheet 的名称为 年份_季节\n",
" sheet_name = f\"{y}_Season_{s}\"\n",
"\n",
" # 将每个 result_table 写入到 Excel 的不同 sheet 中\n",
" result_table.to_excel(writer, sheet_name=sheet_name)\n",
"\n",
"print(f\"结果已保存到文件:{output_filename}\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "93b4b3b64fd941aa",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.17"
}
},
"nbformat": 4,
"nbformat_minor": 5
}