数学建模报告


数学建模报告

前言

2022年五一数模比赛A题解决方案,感谢队友的倾情付出。

图片待更换图床后即可显示。

摘要

根据题目给出的第1-104周血管机器人使用数量表,我们按照题目逐渐增加约束条件,通过Python编程构建回溯算法,配合手动演算验证数据得到最优解。同时在第四问中利用回溯查找的方法,寻找提前购入带来的降价和额外的维护费用之间的最优解。对于第五问我们采用Matlab对自定义函数进行参数整定,取得了优良的拟合效果,通过对这个函数进行采样得到104周后需求数据,代入方案一和方案二分别计算,得到更优的方案是

关键词:回溯算法 Python 血管机器人

背景

随着社会的不断发展和人们生活水平的逐渐提高,心血管病已逐渐成为影响人们健康的一大因素。应对此类疾病,血管介入手术能够有效地对其进行诊断与治疗,已成为主要的治疗方法。传统的介入治疗方案是在医学影像设备的引导下,经皮穿刺,将穿刺针、特制导管、导丝等精密器械引入体内血管,对疾病进行微创诊断和治疗。但是该种方案具有操作复杂,对仪器要求苛刻,患者需接收射线照射等缺点,且医务人员也会受到负面影响。基于以上问题血管介入机器人被研发出来用于解放手术室中的医生,减少医生的负担的同时提升手术的安全性与准确性。

某医院购买了型号为ABLVR型号的血管机器人,其具有两个部分(容器艇,操作手),一个容器艇上拥有四个操作手,其整体一起工作时可在人体中游动。新操作手需在已学习好的操作手的指导下学习一周后投入工作,机器人整体的工作时间是一周,工作一周后操作手需要保养一周,容器艇不必须保养可连续使用。

该医院从第一周开始使用此机器人,初始拥有13个容器艇和50个熟练操作手

问题重述

问题一

在每周开始时,医院可以购买到操作手和容器艇。每个熟练操作手可以作为指导者“指导”10个购买的新操作手进行生物学习。如果仅仅考虑第1-8周,请问每周需要购买多少容器艇和操作手,既满足治疗又能够使运营成本达到最低?

问题二

假设每周有20%的血管机器人损毁(损毁的个数按四舍五入取整),其他条件遵循问题1,通盘考虑第1-104周,请问总共需要购买多少容器艇和操作手,既满足治疗又能够使运营成本达到最低?填表并进行具体分析,与1–8周的结果数据进行对比分析。

问题三

如果每名熟练操作手可以“指导”新操作手的数量调整为不超过20个,假设每周有10%的血管机器人损毁(损毁的个数按四舍五入取整),同问题2,请研究第1-104周里总共需要购买多少容器艇和操作手既满足治疗又能够使运营成本达到最低?

问题四

如果购买操作手和容器艇有优惠政策,即容器艇一次性购买量不超过5个时的单价为200元/个;容器艇一次性购买量超过5个但不超过10个时,超过5个的那部分单价为180元/个;容器艇一次性购买量超过10个时,超过10个的那部分单价为160元/个。同样,操作手一次性购买量不超过20个时的单价为100元/个;操作手一次性购买量超过20个但不超过40个时,超过20个的那部分单价为90元/个;操作手一次性购买量超过40个时,超过40个的那部分单价为80元/个。其他条件遵循问题3,则第1-104周里总共购买的容器艇和操作手将如何调整?

问题五

预测第105-112周的血管机器人的使用需求。为了研究第105-112周的血管机器人的使用成本,在遵循问题4条件的基础上,考虑两种不同的方案。

方案1:在第1-104周最优结果的基础上,医院在第105周开始时有可能需要以每个300元的高价购买能够直接使用的容器艇和每个150元购买熟练操作手,而后续每周均按问题4中的优惠政策购买合适数量的新容器艇和新操作手,满足第105-112周的血管机器人的需求。

方案2:通盘考虑第1-112周的血管机器人的需求。

比较两种方案的第1-112周最低运营成本的差额。

问题分析

一、在第一至三问中,由于采购费用固定,故运营成本主要来自于操作手和容器艇的保养费,为了既满足治疗又能够使运营成本达到最低,只要使容器艇和操作手的数量恰好满足治疗业务需求,即可保证剩余不进行工作的容器艇和操作手数量最小,即保养费最少。

二、操作手训练时需要有老操作手教学,在计算不足的操作手时需额外计算此部分操作手

三、由于容器艇工作后不必要进行保养,在第一至三问模型中可认为其数量只要满足最大数量需求即可,当需求量增加超过现有容器艇时,在前一周购入即可。

四、该问题较为实际且数据量不大,可以采用手动推演的的方式进行仿真推算数据,同时借助编程工具构建模型相互验证,减小后期计算量

五、观察提供的需求数据,可见其具有一定的变化趋势,可以采用拟合工具寻找规律并预测未来变化

image-20220503232033832

模型的建立与求解

符号约定

符号 含义
对x四舍五入

变量声明

符号 含义
N(t) 第t周时血管机器人需求函数,以天数t为自变量
M(t) 第t周开始时进入保养的操作手
L(t) 第t周开始时剩余可用的操作手
HAdd(t) 第t周开始时需要购买的操作手
CAdd(t) 第t周开始时需要购买的容器艇
Teh 第t周开始时用于培训的操作手
C(t) 第t周开始时容器艇的数量

问题一模型建立求解

问题一时间范围为1–8周,数据量较小,采用手动递推的方式模拟

为了满足每一周的治疗需求,当每一周开始时,其操作手的数量应满足

$M(t-1)+HAdd(t-1)+L(t) \geq 4N(t)$

所对应的容器艇数量

$C(t)+CAdd(t-1) = N(t)$

其中,一个熟练操作手将可以带最多10个新操作手

手动推演得操作手需求情况

t 1 2 3 4 5 6 7 8
M 0 44 20 16 28 64 24 20
L 6 0 28 20 0 4 48 44
HAdd 14 0 0 28 0 0 0 0
CAdd 3 0 0 7 0 0 0 0

所需要的购买的操作手数量为$\sum_{t=1}^{8}{HAdd(t)} = 42$ 个

所需要的购买的容器艇数量为$max(N(t))-C(1) = 3$个

使用Python构建代码模型[见附录 1],利用上述模型进行递推

for pos in range(1, 8):
        maintain_num[pos] = need_arms_num[pos-1]
        remain_num[pos] = add_num[pos - 1] + usable_num[pos - 1] - need_arms_num[pos]
        usable_num[pos] = maintain_num[pos] + remain_num[pos]

当遇到可用操作手小于能够使用的操作手时,回溯一周并在回溯周购入所缺少的操作手

if (need_arms_num[pos + 1] - usable_num[pos] > 0):
            add_num[pos] = need_arms_num[pos + 1] - usable_num[pos]

验证结果一致

问题二模型建立求解

在模型一的基础上,增设条件:每周结束时被使用的血管机器人将有20%损失

故t周开始时进入保养的操作手为

$M(t+1) = 4<0.8N(t)>$

若要满足医疗服务则需满足

$L(t)+M(t-1)+HAdd(t-1)\geq 4N(t)$

同时,为了满足训练新的操作手,需要熟练操作手来培训,每一个熟练操作手最多可以带10个新操作手,为了满足最小的维护费用和购买成本,有

$10HAdd(t-1)\leq Teh$

$Teh\leq L(t)$

依据上述关系进行手动推演,由于题目没有明确描述,故此处认为“保养的容器艇数量”和“保养的操作手数量”均指“在指定的周数内正在进行保养”而不是“在指定的周数后需要保养”

周次 购买的容器艇数量 购买的操作手数量 保养的操作手数量 保养的容器艇数量 参与训练的操作手数量(含“熟练工”和“新手”) 总成本****(单位:元)
第12周 4
第26周 22
第52周 18
第78周 44
第101周 79
第102周 87
第103周 79
第104周 0
1-104周(总计) 3096

使用Python构建代码模型[见附录 1],利用上述模型进行递推,得到相同结果[完整结果见附录 2]

问题三模型建立求解

显然,问题三即是在问题二上对条件进行了更改,修改数据带入Python模型进行计算,得到以下结果[完整结果见附录 2]

周次 购买的容器艇数量 购买的操作手数量 保养的操作手数量 保养的容器艇数量 参与训练的操作手数量(含“熟练工”和“新手”) 总成本****(单位:元)
第12周
第26周
第52周
第78周
第101周
第102周
第103周
第104周
1-104周(总计)

问题四模型建立求解

在模型一到三中,我们已经利用Python实现并验证了对操作手和容器艇数量的建模,在模型四中,发生更改的条件为容器艇和操作手购买的优惠政策,因此我们只需在模型三的基础上改变购入机器人的时间,比较提前集体购入所带来的降价和其带来的保养费的多少,寻找最节省成本的方案即可。同时该模型相比于模型三并没有改变操作手训练的时间点,因此无需对代码模型进行过多改动。

我们在模型三得到的购买时间和购买数量推演结果上

问题五模型建立求解

由于题目中没有给出104周以后对血管机器人的需求数量,我们将数据导入Matlab进行观察。通过plot函数,可以清晰的观察到其需求函数大致呈二次函数形上升,同时具有一定周期的波动

image-20220503194821775

我们想到可以构建以下函数模型

$f(x)=ax^m+bsin(nx)+cx+d$

利用Matlab工具箱进行拟合,手动调整参数$m$和参数$n$,得到了以下拟合结果,可以看到其拟合结果良好,$R^2=0.9598$

image-20220503200323478

相比于简单的二次函数拟合,该函数模型更加符合其周期性波动的特性,其后续预测结果变化规律也与1–104周基本一致

image-20220503201911486

对该函数$x\in(105,112)$区间的整数进行采样,结果四舍五入,得到以下结果

周数 105 106 107 108 109 110 111 112
需求预测 102 104 106 109 112 116 120 124

image-20220503231813549

方案一

方案二

参考文献

本文没有参考文献

附录

附录一

第一问代码

""" 周数 """
weeks = list(range(104))
""" 需求机器人数量 x """
need_body_num = [
    11,	 5,	  4,   7,	16,   6,	5,   7,
    13,	 6,	  5,   7,   12,   5,	4,   6,
    9,	 5,	  5,   11,  29,   21,	17,  20,
    27,	 13,  9,   10,  16,   6,	5,   7,
    11,	 5,	  5,   6,   12,   7,	7,   10,
    15,	 10,  9,   11,  15,   10,	10,	 16,
    26,	 21,  23,  36,  50,   45,	45,	 49,
    57,  43,  40,  44,  52,   43,	42,	 45,
    52,	 41,  39,  41,  48,   35,	34,	 35,
    42,	 34,  36,  43,  55,   48,	54,	 65,
    80,	 70,  74,  85,  101,  89,	88,	 90,
    100, 87,  88,  89,	104,  89,	89,	 90,
    106, 96,  94,  99,	109,  99,	96,	 102,]

""" 所需机械臂个数 x """
need_arms_num = []
for i in weeks:
    num = need_body_num[i] * 4
    need_arms_num.append(num)
print(f"x={need_arms_num[:8]}")

""" 保养数量 y """
maintain_num = [0]*104
""" 剩余数量 z"""
remain_num = [0]*104
""" 购买数量 w """
add_num = [0]*104
""" 下次可用数量 u """
usable_num = [0]*104
""" 总数量 t """
total_num = [0]*104

def FunOne():
    maintain_num[0] = 0
    remain_num[0] = 6
    add_num[0] = 14
    usable_num[0] = 6
    total_num[0] = 64

    for pos in range(1, 8):
        maintain_num[pos] = need_arms_num[pos-1]
        remain_num[pos] = add_num[pos - 1] + usable_num[pos - 1] - need_arms_num[pos]
        usable_num[pos] = maintain_num[pos] + remain_num[pos]
        if (need_arms_num[pos + 1] - usable_num[pos] > 0):
            add_num[pos] = need_arms_num[pos + 1] - usable_num[pos]
        else:
            add_num[pos] = 0
        total_num[pos] = need_arms_num[pos] + usable_num[pos]

FunOne()
"""输出结果"""
print(f"y={maintain_num[:8]}\nz={remain_num[:8]}\nw={add_num[:8]}")
print(f"u={usable_num[:8]}\nt={total_num[:8]}")

第二问代码

import math

""" 周数"""
weeks = list(range(104))
""" 需求机器人数量 x """
need_body_num = [
    11,	 5,	  4,   7,	16,   6,	5,   7,
    13,	 6,	  5,   7,   12,   5,	4,   6,
    9,	 5,	  5,   11,  29,   21,	17,  20,
    27,	 13,  9,   10,  16,   6,	5,   7,
    11,	 5,	  5,   6,   12,   7,	7,   10,
    15,	 10,  9,   11,  15,   10,	10,	 16,
    26,	 21,  23,  36,  50,   45,	45,	 49,
    57,  43,  40,  44,  52,   43,	42,	 45,
    52,	 41,  39,  41,  48,   35,	34,	 35,
    42,	 34,  36,  43,  55,   48,	54,	 65,
    80,	 70,  74,  85,  101,  89,	88,	 90,
    100, 87,  88,  89,	104,  89,	89,	 90,
    106, 96,  94,  99,	109,  99,	96,	 102,]

""" 所需机械臂个数 x """
need_arms_num = []
for i in weeks:
    num = need_body_num[i] * 4
    need_arms_num.append(num)

""" 保养机器人数量 y """
maintain_body_num = [0]*105
""" 保养机械臂数量 y' """
maintain_arms_num = [0]*105
""" 剩余机器人数量 z """
remain_body_num = [0]*105
""" 剩余机械臂数量 z' """
remain_arms_num = [0]*105
""" 购买机械臂数量 w """
add_arms_num = [0]*105
""" 购买机器人数量 m """
add_body_num = [0]*105
""" 下次机器人可用数量 u """
usable_body_num = [0]*105
""" 下次机械臂可用数量 u' """
usable_arms_num = [0]*105
""" 总机器人数量 t """
total_body_num = [0]*105
""" 机械臂总数量 t' """
total_arms_num = [0]*105
""" 计数器 """
times = 0

def Fun():
    maintain_body_num[0] = 0
    maintain_arms_num[0] = 0
    remain_body_num[0] = 2
    remain_arms_num[0] = 6
    add_body_num[0] = 3
    add_arms_num[0] = 14
    usable_body_num[0] = 2
    usable_arms_num[0] = 6
    total_body_num[0] = 16
    total_arms_num[0] = 64
    pos = 1
    global times

    while pos < 103:
        maintain_body_num[pos] = round(need_body_num[pos - 1] * 0.8)
        maintain_arms_num[pos] = maintain_body_num[pos] * 4
        remain_body_num[pos] = add_body_num[pos - 1] + usable_body_num[pos - 1] - need_body_num[pos]
        remain_arms_num[pos] = add_arms_num[pos - 1] + usable_arms_num[pos - 1] - need_arms_num[pos]
        usable_body_num[pos] = maintain_body_num[pos] + remain_body_num[pos]
        usable_arms_num[pos] = maintain_arms_num[pos] + remain_arms_num[pos]
        """ 购买判断 """
        if (need_body_num[pos + 1] - usable_body_num[pos] > 0):
            add_body_num[pos] = need_body_num[pos + 1] - usable_body_num[pos]
            add_arms_num[pos] = add_body_num[pos] * 4

        total_body_num[pos] = need_body_num[pos] + usable_body_num[pos] + add_body_num[pos]
        total_arms_num[pos] = need_arms_num[pos] + usable_arms_num[pos] + add_arms_num[pos]
        """ 熟练手判断 """
        if (10*remain_arms_num[pos] < add_arms_num[pos]):
            temp = pos
            while(temp >= 0 and (10*remain_arms_num[temp] < add_arms_num[temp])):
                counts1 = math.ceil((add_arms_num[temp] - 10*remain_arms_num[temp])/10)
                add_arms_num[temp - 1] += counts1
                remain_arms_num[temp - 1] += counts1
                usable_arms_num[temp - 1] += counts1
                total_arms_num[temp - 1] += counts1
                counts2 = math.ceil(total_arms_num[temp - 1] / 4) - total_body_num[temp - 1]
                add_body_num[temp - 1] += counts2
                remain_body_num[temp - 1] += counts2
                usable_body_num[temp - 1] += counts2
                temp -= 1
                times += 1
        else:
            pos += 1

Fun()
print(f"需要\n x = {need_body_num}\n x' = {need_arms_num}")
print(f"保养\n y = {maintain_body_num}\n y' = {maintain_arms_num}")
print(f"剩余\n z = {remain_body_num}\n z' = {remain_arms_num}")
print(f"购买\n m = {add_body_num}\n w = {add_arms_num}")
print(f"可用\n u = {usable_body_num}\n u' = {usable_arms_num}")
print(f"总数\n t = {total_body_num}\n t' = {total_arms_num}")
print(times)

第三问代码

import math

""" 周数"""
weeks = list(range(104))
""" 需求机器人数量 x """
need_body_num = [
    11,	 5,	  4,   7,	16,   6,	5,   7,
    13,	 6,	  5,   7,   12,   5,	4,   6,
    9,	 5,	  5,   11,  29,   21,	17,  20,
    27,	 13,  9,   10,  16,   6,	5,   7,
    11,	 5,	  5,   6,   12,   7,	7,   10,
    15,	 10,  9,   11,  15,   10,	10,	 16,
    26,	 21,  23,  36,  50,   45,	45,	 49,
    57,  43,  40,  44,  52,   43,	42,	 45,
    52,	 41,  39,  41,  48,   35,	34,	 35,
    42,	 34,  36,  43,  55,   48,	54,	 65,
    80,	 70,  74,  85,  101,  89,	88,	 90,
    100, 87,  88,  89,	104,  89,	89,	 90,
    106, 96,  94,  99,	109,  99,	96,	 102,]

""" 所需机械臂个数 x """
need_arms_num = []
for i in weeks:
    num = need_body_num[i] * 4
    need_arms_num.append(num)

""" 保养机器人数量 y """
maintain_body_num = [0]*105
""" 保养机械臂数量 y' """
maintain_arms_num = [0]*105
""" 剩余机器人数量 z """
remain_body_num = [0]*105
""" 剩余机械臂数量 z' """
remain_arms_num = [0]*105
""" 购买机械臂数量 w """
add_arms_num = [0]*105
""" 购买机器人数量 m """
add_body_num = [0]*105
""" 下次机器人可用数量 u """
usable_body_num = [0]*105
""" 下次机械臂可用数量 u' """
usable_arms_num = [0]*105
""" 总机器人数量 t """
total_body_num = [0]*105
""" 机械臂总数量 t' """
total_arms_num = [0]*105
""" 计数器 """
times = 0

def Fun():
    maintain_body_num[0] = 0
    maintain_arms_num[0] = 0
    remain_body_num[0] = 2
    remain_arms_num[0] = 6
    add_body_num[0] = 3
    add_arms_num[0] = 14
    usable_body_num[0] = 2
    usable_arms_num[0] = 6
    total_body_num[0] = 16
    total_arms_num[0] = 64
    pos = 1

    while pos < 103:
        maintain_body_num[pos] = round(need_body_num[pos - 1] * 0.9)
        maintain_arms_num[pos] = maintain_body_num[pos] * 4
        remain_body_num[pos] = add_body_num[pos - 1] + usable_body_num[pos - 1] - need_body_num[pos]
        remain_arms_num[pos] = add_arms_num[pos - 1] + usable_arms_num[pos - 1] - need_arms_num[pos]
        usable_body_num[pos] = maintain_body_num[pos] + remain_body_num[pos]
        usable_arms_num[pos] = maintain_arms_num[pos] + remain_arms_num[pos]
        """ 购买判断 """
        if (need_body_num[pos + 1] - usable_body_num[pos] > 0):
            add_body_num[pos] = need_body_num[pos + 1] - usable_body_num[pos]
            add_arms_num[pos] = add_body_num[pos] * 4

        total_body_num[pos] = need_body_num[pos] + usable_body_num[pos] + add_body_num[pos]
        total_arms_num[pos] = need_arms_num[pos] + usable_arms_num[pos] + add_arms_num[pos]
        """ 熟练手判断 """
        if (20*remain_arms_num[pos] < add_arms_num[pos]):
            temp = pos
            while(temp >= 0 and (20*remain_arms_num[temp] < add_arms_num[temp])):
                counts1 = math.ceil((add_arms_num[temp] - 20*remain_arms_num[temp])/20)
                add_arms_num[temp - 1] += counts1
                remain_arms_num[temp - 1] += counts1
                usable_arms_num[temp - 1] += counts1
                total_arms_num[temp - 1] += counts1
                counts2 = math.ceil(total_arms_num[temp - 1] / 4) - total_body_num[temp - 1]
                add_body_num[temp - 1] += counts2
                remain_body_num[temp - 1] += counts2
                usable_body_num[temp - 1] += counts2
                temp -= 1
        else:
            pos += 1

Fun()
print(f"需要\n x = {need_body_num}\n x' = {need_arms_num}")
print(f"保养\n y = {maintain_body_num}\n y' = {maintain_arms_num}")
print(f"剩余\n z = {remain_body_num}\n z' = {remain_arms_num}")
print(f"购买\n m = {add_body_num}\n w = {add_arms_num}")
print(f"可用\n u = {usable_body_num}\n u' = {usable_arms_num}")
print(f"总数\n t = {total_body_num}\n t' = {total_arms_num}")

  目录