2024 年高教社杯数模竞赛
C题参考思路
C题是一道最优策略决策分析类问题,其目的是根据题目所给的农作物种植地块类型、农作物的类型,考虑农作物产量、种植成本、销售价格等因素变化情况,建立最优种植方案模型以获取最高种植利润。整个问题的解决流程主要可以分为三大块:在变量相对稳定的情况下,考虑总产量超过相应的预期销售量,建立未来16年农作物最优种植方案,相对应的,也需要参赛团队有对应方面的一些知识积累和技能掌握:第一,逻辑分析能力,主要是理清楚众多自变量与因变量之间的关系,确定具体的自变量标记、分类以及与因变量的逻辑关系;第二,动态种植方案优化模型,由于变量为一个动态区间,因此需要进行动态计算分析,因此需要参赛者对线性规划、智能优化算法(遗传算法、模拟退火算法等)等模型有所掌握;第三,要评估分析影响最优种植方案重要因素有哪些,对原有众多影响因素进行降维处理,找出与最优方案关联性最大的变量组合,确定未来各年份种植方案,这部分主要需要用到一些统计分析的方法,如主成分分析、因子分析等。
针对问题一,以农作物种植利润为总目标,综合考虑不同地块可种植农作物类型以及各类农作物投入产出比、当季价格以及销售期望等约束条件,针对某种作物每季的总产量超过相应的预期销售量不能正常销售的两种情况进行建模分析:
-
超过部分滞销,造成浪费
Max 利润=(总产量-超过部分产量)*i *售价-成本
其中,总产量是指不同地块农作物产量,正常值参考2023年种植情况,超出部分可以设定以某个适当的特定值为变化单位,当然也要限制最大超出值,这两个数据通过查找相关资料进行确定;i为销售量与总产量比; 滞销产量是指由于某种作物每季的总产量超过相应的预期销售量,这部分由于滞销,无法产生收益;售价参考2023年各农作物价格情况。成本包括种植成本
2. 超过部分按2023年销售价格的 50%降价出售。
Max 利润=未超出部分产量*i*售价+超过部分产量*j*0.5*售价-成本
其中,总产量是指不同地块农作物产量,正常值参考2023年种植情况,超出部分可以设定以某个适当的特定值为变化单位,当然也要限制最大超出值,这两个数据通过查找相关资料进行确定;i为未超出部分销售量与产量比;j为超过部分销售量与产量比;滞销产量是指由于某种作物每季的总产量超过相应的预期销售量,这部分由于滞销,无法产生收益;售价参考2023年各农作物价格情况。成本包括种植成本。
针对问题二:相对于第一问,各类农作物的预期销售量、亩产量、种植成本以及销售价格从原来的单一数值,变化为区间值,同样以种植利润最高为优化目标,将变量(各类农作物的预期销售量、亩产量、种植成本以及销售价格)转换为区间范围,进行模型建立
Max 利润f(ax1,bx2,cx3,dx4)
X1: 预期销售量, a 为(5%~10%)或(-5%~5%);
X2: 预期亩产量, b为(5%~10%)或(-10%~10%);
X3: 种植成本,c为5%
X4: 销售价格,d为5%
针对问题三:
利用主成分分析方法,确定各种农作物之间和预期销售量与销售价格、种植成本等变量之间的关联度,筛选出关联度较高的种植方案以及变量组合,基于问题二模型,求解除该乡村 2024~2030 年农作物的最优种植策略。
【参考代码】
(1)主成分分析Python代码示例
import matplotlib.pyplot as plt #加载matplotlib用于数据的可视化
from sklearn.decomposition import PCA #加载PCA算法包
from sklearn.datasets import load_iris
data=load_iris()
y=data.target
x=data.data
pca=PCA(n_components=2) #加载PCA算法,设置降维后主成分数目为2
reduced_x=pca.fit_transform(x)#对样本进行降维
red_x,red_y=[],[]
blue_x,blue_y=[],[]
green_x,green_y=[],[]
for i in range(len(reduced_x)):
if y[i] ==0:
red_x.append(reduced_x[i][0])
red_y.append(reduced_x[i][1])
elif y[i]==1:
blue_x.append(reduced_x[i][0])
blue_y.append(reduced_x[i][1])
else:
green_x.append(reduced_x[i][0])
green_y.append(reduced_x[i][1])
#可视化
plt.scatter(red_x,red_y,c='r',marker='x')
plt.scatter(blue_x,blue_y,c='b',marker='D')
plt.scatter(green_x,green_y,c='g',marker='.')
plt.show()
(2)遗传算法示例
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
DNA_SIZE = 24
POP_SIZE = 200
CROSSOVER_RATE = 0.8
MUTATION_RATE = 0.005
N_GENERATIONS = 50
X_BOUND = [-3, 3]
Y_BOUND = [-3, 3]
def F(x, y):
return 3*(1-x)**2*np.exp(-(x**2)-(y+1)**2)- 10*(x/5 - x**3 - y**5)*np.exp(-x**2-y**2)- 1/3**np.exp(-(x+1)**2 - y**2)
def plot_3d(ax):
X = np.linspace(*X_BOUND, 100)
Y = np.linspace(*Y_BOUND, 100)
X,Y = np.meshgrid(X, Y)
Z = F(X, Y)
ax.plot_surface(X,Y,Z,rstride=1,cstride=1,cmap=cm.coolwarm)
ax.set_zlim(-10,10)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.pause(3)
plt.show()
def get_fitness(pop):
x,y = translateDNA(pop)
pred = F(x, y)
return (pred - np.min(pred)) + 1e-3 #减去最小的适应度是为了防止适应度出现负数,通过这一步fitness的范围为[0, np.max(pred)-np.min(pred)],最后在加上一个很小的数防止出现为0的适应度
def translateDNA(pop): #pop表示种群矩阵,一行表示一个二进制编码表示的DNA,矩阵的行数为种群数目
x_pop = pop[:,1::2]#奇数列表示X
y_pop = pop[:,::2] #偶数列表示y
#pop:(POP_SIZE,DNA_SIZE)*(DNA_SIZE,1) --> (POP_SIZE,1)
x = x_pop.dot(2**np.arange(DNA_SIZE)[::-1])/float(2**DNA_SIZE-1)*(X_BOUND[1]-X_BOUND[0])+X_BOUND[0]
y = y_pop.dot(2**np.arange(DNA_SIZE)[::-1])/float(2**DNA_SIZE-1)*(Y_BOUND[1]-Y_BOUND[0])+Y_BOUND[0]
return x,y
def crossover_and_mutation(pop, CROSSOVER_RATE = 0.8):
new_pop = []
for father in pop: #遍历种群中的每一个个体,将该个体作为父亲
child = father #孩子先得到父亲的全部基因(这里我把一串二进制串的那些0,1称为基因)
if np.random.rand() < CROSSOVER_RATE: #产生子代时不是必然发生交叉,而是以一定的概率发生交叉
mother = pop[np.random.randint(POP_SIZE)] #再种群中选择另一个个体,并将该个体作为母亲
cross_points = np.random.randint(low=0, high=DNA_SIZE*2) #随机产生交叉的点
child[cross_points:] = mother[cross_points:] #孩子得到位于交叉点后的母亲的基因
mutation(child) #每个后代有一定的机率发生变异
new_pop.append(child)
return new_pop
def mutation(child, MUTATION_RATE=0.003):
if np.random.rand() < MUTATION_RATE: #以MUTATION_RATE的概率进行变异
mutate_point = np.random.randint(0, DNA_SIZE) #随机产生一个实数,代表要变异基因的位置
child[mutate_point] = child[mutate_point]^1 #将变异点的二进制为反转
def select(pop, fitness): # nature selection wrt pop's fitness
idx = np.random.choice(np.arange(POP_SIZE), size=POP_SIZE, replace=True,
p=(fitness)/(fitness.sum()) )
return pop[idx]
def print_info(pop):
fitness = get_fitness(pop)
max_fitness_index = np.argmax(fitness)
print("max_fitness:", fitness[max_fitness_index])
x,y = translateDNA(pop)
print("最优的基因型:", pop[max_fitness_index])
print("(x, y):", (x[max_fitness_index], y[max_fitness_index]))
if __name__ == "__main__":
fig = plt.figure()
ax = Axes3D(fig)
plt.ion()#将画图模式改为交互模式,程序遇到plt.show不会暂停,而是继续执行
plot_3d(ax)
pop = np.random.randint(2, size=(POP_SIZE, DNA_SIZE*2)) #matrix (POP_SIZE, DNA_SIZE)
for _ in range(N_GENERATIONS):#迭代N代
x,y = translateDNA(pop)
if 'sca' in locals():
sca.remove()
sca = ax.scatter(x, y, F(x,y), c='black', marker='o');plt.show();plt.pause(0.1)
pop = np.array(crossover_and_mutation(pop, CROSSOVER_RATE))
#F_values = F(translateDNA(pop)[0], translateDNA(pop)[1])#x, y --> Z matrix
fitness = get_fitness(pop)
pop = select(pop, fitness) #选择生成新的种群
print_info(pop)
plt.ioff()
plot_3d(ax)
(3)模拟退火算法示例
#本功能实现最小值的求解#
from matplotlib import pyplot as plt
import numpy as np
import random
import math
plt.ion()#这里需要把matplotlib改为交互状态
#初始值设定
hi=3
lo=-3
alf=0.95
T=100
#目标函数
def f(x):
return 11*np.sin(x)+7*np.cos(5*x)##注意这里要是np.sin
#可视化函数(开始清楚一次然后重复的画)
def visual(x):
plt.cla()
plt.axis([lo-1,hi+1,-20,20])
m=np.arange(lo,hi,0.0001)
plt.plot(m,f(m))
plt.plot(x,f(x),marker='o',color='black',markersize='4')
plt.title('temperature={}'.format(T))
plt.pause(0.1)#如果不停啥都看不见
#随机产生初始值
def init():
return random.uniform(lo,hi)
#新解的随机产生
def new(x):
x1=x+T*random.uniform(-1,1)
if (x1<=hi)&(x1>=lo):
return x1
elif x1<lo:
rand=random.uniform(-1,1)
return rand*lo+(1-rand)*x
else:
rand=random.uniform(-1,1)
return rand*hi+(1-rand)*x
#p函数
def p(x,x1):
return math.exp(-abs(f(x)-f(x1))/T)
def main():
global x
global T
x=init()
while T>0.0001:
visual(x)
for i in range(500):
x1=new(x)
if f(x1)<=f(x):
x=x1
else:
if random.random()<=p(x,x1):
x=x1
else:
continue
T=T*alf
print('最小值为:{}'.format(f(x)))
main()