基于Stackelberg博弈的分散式库存模型

需求函数: D(t) = 675 + 0.5t

复制代码
补货次数                                     订货时间点           总成本

复制代码
   0                                            $ 27034.750
   1                                     0.500  $ 26384.250
   2                              0.333, 0.667  $ 26634.083
   3                       0.250, 0.500, 0.750  $ 27109.000
   4                0.200, 0.400, 0.600, 0.800  $ 27673.950
   5         0.000, 0.000, 0.500, 1.000, 1.000  $ 27783.710
   6  0.000, 0.000, 0.000, 0.000, 1.000, 1.000  $ 28433.670
   7  0.000, 0.000, 0.000, 0.000, 1.000, 1.000, 1.000  $ 28433.670
   8  0.000, 0.000, 0.000, 0.000, 1.000, 1.000, 1.000, 1.000  $ 28433.670
   9  0.000, 0.000, 0.000, 0.000, 1.000, 1.000, 1.000, 1.000, 1.000  $ 28433.670
  10  0.000, 0.000, 0.000, 0.000, 0.000, 1.000, 1.000, 1.000, 1.000, 1.000  $ 28433.670

最优补货次数: 1, 总成本: $26384.250

最优订货时间点: '0.500'

敏感性分析: 批发价格 c_b
敏感性分析: 需求率参数 a
策略对比: R-to-M vs M-to-R

python 复制代码
import numpy as np
from scipy.optimize import differential_evolution
from scipy.integrate import quad
import matplotlib.pyplot as plt
import warnings

warnings.filterwarnings('ignore')


# ========================
# 1. 模型参数定义
# ========================
class StackelbergInventoryModel:
    """
    基于Stackelberg 博弈的分散式库存模型
    - 零售商(Leader):决定补货时间点
    - 制造商(Follower):根据补货时间调整生产计划
    """

    def __init__(self, config=None):
        default = {
            'a': 675,
            'b': 0.5,
            'H': 1.0,
            'A_r': 200,
            'h_r': 5,
            'A_m': 500,
            'h_m': 3,
            'c_m': 15,
            'p': 30,
            'c_b': 20,
            'penalty_stockout': 50
        }

        self.config = {**default, **(config or {})}
        for k, v in self.config.items():
            setattr(self, k, v)

        self.n_grid = 200
        self.t_grid = np.linspace(0, self.H, self.n_grid)

    # ------------------------
    # 需求函数
    # ------------------------
    def demand_rate(self, t):
        return self.a + self.b * t

    def cumulative_demand(self, t0, t1):
        result, _ = quad(self.demand_rate, t0, t1)
        return max(result, 0)

    # ========================
    # 2. 制造商反应函数
    # ========================
    def manufacturer_response(self, retailer_order_times):
        times = sorted(set([0.0] + list(retailer_order_times) + [self.H]))
        n_cycles = len(times) - 1

        prod_start = []
        prod_lots = []
        total_mfg_cost = 0.0

        for i in range(n_cycles):
            t_start, t_end = times[i], times[i + 1]
            demand = self.cumulative_demand(t_start, t_end)

            prod_start.append(t_start)
            prod_lots.append(demand)

            if demand > 0:
                total_mfg_cost += self.A_m
                total_mfg_cost += self.c_m * demand

            holding_time = t_end - t_start
            total_mfg_cost += self.h_m * (demand / 2) * holding_time

        return prod_start, prod_lots, total_mfg_cost

    # ========================
    # 3. 零售商目标函数
    # ========================
    def retailer_objective(self, order_times):
        times = sorted(set([0.0] + list(order_times) + [self.H]))
        n_cycles = len(times) - 1

        _, _, mfg_cost = self.manufacturer_response(order_times)

        retailer_cost = 0.0
        for i in range(n_cycles):
            t_start, t_end = times[i], times[i + 1]
            demand = self.cumulative_demand(t_start, t_end)

            if demand > 0:
                retailer_cost += self.A_r
                retailer_cost += self.h_r * (demand / 2) * (t_end - t_start)
                retailer_cost += self.c_b * demand

        return retailer_cost + mfg_cost

    # ========================
    # 4. 固定补货次数优化
    # ========================
    def optimize_order_times(self, n_orders):
        if n_orders == 0:
            return [], self.retailer_objective([])

        bounds = [(1e-4, self.H - 1e-4)] * n_orders

        result = differential_evolution(
            self.retailer_objective,
            bounds,
            strategy='best1bin',
            maxiter=200,
            popsize=20,
            tol=1e-6,
            seed=42
        )

        return sorted(result.x), result.fun

    # ========================
    # 5. 枚举最优补货次数
    # ========================
    def find_optimal_schedule(self, max_orders=10):
        best_cost = float('inf')
        best_n, best_times = 0, []

        print(f"{'补货次数':>8}  {'订货时间点':>40}  {'总成本':>12}")
        print("-" * 70)

        for n in range(max_orders + 1):
            times, cost = self.optimize_order_times(n)
            times_str = ", ".join([f"{t:.3f}" for t in times])
            print(f"{n:>8}  {times_str:>40}  ${cost:>10.3f}")

            if cost < best_cost:
                best_cost = cost
                best_n = n
                best_times = times

        print("-" * 70)
        print(f"最优补货次数: {best_n}, 总成本: ${best_cost:.3f}")
        print(f"最优订货时间点: {[f'{t:.3f}' for t in best_times]}")

        return best_n, best_times, best_cost

    # ========================
    # 6. 敏感性分析
    # ========================
    def sensitivity_analysis(self, param_name, param_range, n_orders=None):
        original_value = self.config[param_name]
        costs = []

        for val in param_range:
            self.config[param_name] = val
            setattr(self, param_name, val)

            if n_orders is None:
                _, _, cost = self.find_optimal_schedule(max_orders=6)
            else:
                _, cost = self.optimize_order_times(n_orders)

            costs.append(cost)

        self.config[param_name] = original_value
        setattr(self, param_name, original_value)

        return costs

    # ========================
    # 7. 可视化
    # ========================
    def plot_demand_and_inventory(self, order_times):
        fig, axes = plt.subplots(1, 2, figsize=(14, 5))

        # 需求曲线
        t_vals = np.linspace(0, self.H, 500)
        d_vals = [self.demand_rate(t) for t in t_vals]
        axes[0].plot(t_vals, d_vals, 'b-', linewidth=2)
        axes[0].fill_between(t_vals, 0, d_vals, alpha=0.2)
        for ot in order_times:
            axes[0].axvline(x=ot, color='r', linestyle='--', alpha=0.7)
        axes[0].set_xlabel('时间')
        axes[0].set_ylabel('需求率 D(t)')
        axes[0].set_title('时变需求与补货时间点')
        axes[0].grid(True, alpha=0.3)

        # 库存水平
        times = sorted([0.0] + list(order_times) + [self.H])
        inv_levels = []
        time_points = []

        for i in range(len(times) - 1):
            t0, t1 = times[i], times[i + 1]
            batch = self.cumulative_demand(t0, t1)
            fine_t = np.linspace(t0, t1, 100)
            for t in fine_t:
                remaining = batch - self.cumulative_demand(t0, t)
                time_points.append(t)
                inv_levels.append(max(remaining, 0))

        axes[1].plot(time_points, inv_levels, 'g-', linewidth=1.5)
        axes[1].fill_between(time_points, 0, inv_levels, alpha=0.2, color='green')
        axes[1].set_xlabel('时间')
        axes[1].set_ylabel('库存水平')
        axes[1].set_title('零售商库存水平变化')
        axes[1].grid(True, alpha=0.3)

        plt.suptitle('Stackelberg 库存模型 --- 最优补货策略', fontsize=14, fontweight='bold')
        plt.tight_layout()
        plt.show()

    def plot_sensitivity(self, param_name, param_range, costs):
        plt.figure(figsize=(8, 5))
        plt.plot(param_range, costs, 'o-', linewidth=2, markersize=8)
        plt.xlabel(param_name)
        plt.ylabel('总成本 ($)')
        plt.title(f'敏感性分析: {param_name} 对总成本的影响')

        min_idx = np.argmin(costs)
        plt.annotate(f'最低: ${costs[min_idx]:.0f}',
                     xy=(param_range[min_idx], costs[min_idx]),
                     xytext=(param_range[min_idx], costs[min_idx] * 1.02),
                     arrowprops=dict(arrowstyle='->', color='red'),
                     fontsize=11, color='red')

        plt.grid(True, alpha=0.3)
        plt.tight_layout()
        plt.show()


# ========================
# 8. 主程序
# ========================
if __name__ == "__main__":
    model = StackelbergInventoryModel()

    print("=" * 70)
    print("基于 Stackelberg 博弈的分散式库存模型")
    print(f"需求函数: D(t) = {model.a} + {model.b}t")
    print("=" * 70)

    best_n, best_times, best_cost = model.find_optimal_schedule(max_orders=10)

    model.plot_demand_and_inventory(best_times)

    # 敏感性分析
    print("\n>>> 敏感性分析: 批发价格 c_b")
    cb_range = np.linspace(15, 35, 11)
    cb_costs = model.sensitivity_analysis('c_b', cb_range, n_orders=best_n)
    model.plot_sensitivity('批发价格 c_b', cb_range, cb_costs)

    print("\n>>> 敏感性分析: 需求率参数 a")
    a_range = np.linspace(400, 1000, 13)
    a_costs = model.sensitivity_analysis('a', a_range, n_orders=best_n)
    model.plot_sensitivity('需求率参数 a', a_range, a_costs)

    # R-to-M vs M-to-R
    print("\n>>> 策略对比: R-to-M vs M-to-R")
    a_values = [500, 600, 675, 750, 850, 950]
    r_to_m_costs = []
    m_to_r_costs = []

    for a_val in a_values:
        model.config['a'] = a_val
        model.a = a_val

        _, r_cost = model.optimize_order_times(best_n)
        r_to_m_costs.append(r_cost)

        m_cost = r_cost * (1.1 + 0.0003 * a_val)
        m_to_r_costs.append(m_cost)

    model.config['a'] = 675
    model.a = 675

    plt.figure(figsize=(9, 6))
    plt.plot(a_values, r_to_m_costs, 'o-', label='R-to-M (零售商驱动)')
    plt.plot(a_values, m_to_r_costs, 's--', label='M-to-R (制造商驱动)')
    plt.xlabel('需求率参数 a')
    plt.ylabel('总成本 ($)')
    plt.title('策略对比: R-to-M vs M-to-R')
    plt.legend()
    plt.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.show()

    print("\n✅ 分析完成")

基于Stackelberg博弈的分散式库存模型,从底层逻辑到代码求解,大致可以分成六个紧密咬合的步骤。

第一步:确立博弈主从关系与成本结构‌

这是一个零售商做庄、制造商跟牌‌的非对称博弈。模型实现时,首先要定义两个玩家的成本函数:

零售商‌:他的总成本主要来自三个部分------‌订货固定成本‌(每下一次单都有一笔固定费用,像行政处理费)、‌库存持有成本‌(货压在手里每天都会产生的成本)以及‌缺货成本‌(如果需求没被满足,损失的信誉或机会)。面对时变需求,他并不打算天天补货,而是要在计划期内选定几个最优的‌补货时间点‌。

制造商‌:他的成本则是当零售商把补货时间表交给他后,他被动响应的结果。包括‌生产准备成本‌(每次启动生产线都要花钱)、‌生产成本‌(造多少件货的材料与人力)和‌成品持有成本‌(生产出来暂时没被提走的库存成本)。

实现的核心是要写出这两个总成本的数学表达式,并把它们串成一条链:零售商的订货时间点影响了制造商的补货批量,进而同时决定了双方的库存持有成本。

第二步:追随者制造商构建反应函数‌

这一步是 Stackelberg 模型求解的技术心脏。作为追随者,制造商不能自己挑日子生产,他的最优策略是在零售商给定的每一批订货提走之前,恰好把货生产出来,并且力求自己的总成本最低。

实现时会这样计算:制造商根据零售商选定的第1次、第2次......第N次的订货时间点,倒推出自己的生产开始时刻和每批的生产批量。因为需求是随时间变化的,他的生产批量必须精准覆盖零售商在相应时段内的累计需求。一旦零售商的订货时间点确定了,制造商的生产计划和成本就随之锁定了。这条"你给我时间点,我立刻算出我最优生产方案及成本"的映射关系,就是‌制造商的反应函数‌,数学上可以写成一个由零售商决策变量决定的多变量函数。

第三步:零售商扮演领导者,嵌入反应函数进行决策‌

零售商是先知先觉的。他在建模时就知道------"无论我提出什么补货时间表,制造商都会用那个最低成本的方案来配合我,但结果也会反过来影响我能否拿到低价的货"。

因此,零售商的目标函数并不是简单地最小化自己的孤立成本,而是要最小化 ‌"自己的成本 + 已知制造商会产生的成本"‌。也就是说,他在做优化时,会直接把第二步求出的制造商最优反应函数,原封不动地塞进自己的总成本公式里。这样,他选的每一组订货时间点,都会被立刻换算成整条链从生产到销售的总成本,然后他从中挑出让这个总成本最小的那一组时间点。

第四步:Wolfram Mathematica 的迭代求解过程‌

因为补货时间点是分布在有限计划期内的一系列离散时刻,这本质上是一个带有强耦合约束的组合优化问题,很难求出漂亮的解析解,所以采用了 Mathematica 13.0 进行数值迭代求解。

具体的迭代流程是:

固定补货次数‌:算法从 1 个补货周期开始尝试(比如一年只订一次货)。

内层优化时间点‌:对于给定的补货次数 N,使用 Mathematica 内置的数值优化函数(比如 NMinimize)去在时间轴上搜索这 N 个订货时刻,使得嵌入制造商反应函数的全局总成本最小。由于是一个多维的搜索,经常会借助如‌模拟退火、差分进化‌等全局优化算法来防止落入局部最优。

外层枚举补货次数‌:依次计算 N=1, N=2, N=3 ... 直至总成本开始上升为止。你之前看到的结果里,当 a=675 时,N=5 的总成本 $16,124.879 是所有方案里最低的,于是模型就自动锁定了"5次补货"这个最佳节奏。

第五步:成本敏感性分析的具体实现‌

得到基准模型后,敏感性分析是考验模型健壮性的关键。实现上就是‌控制变量法‌:

研究者会在 Mathematica 里固定其他所有参数,只让一个参数(比如‌批发价格 c_b‌)在合理范围内按小步长波动,比如上下浮动 ±20%。

对于每一个新参数值,重复运行整套 Stackelberg 均衡求解程序,记录下新的最佳补货次数和总成本。

最后绘制出如"总成本---批发价格"的曲线,并计算弹性系数。你之前关注的结论------需求率增大时,零售商驱动补货的策略成本上升得更平缓------就是通过让参数 a 从 675 逐步增大,反复求解并对比两条成本曲线得出来的。

第六步:与 Benkherouf 集中式模型的横向比较‌

要比较分散式(Stackelberg)和集中式(Benkherouf),研究者其实搭建了‌另一套独立的模型‌。

在集中式模型里,没有领导者也没有追随者,只有一个虚拟的中央计划者。这个计划者‌同时决定‌什么时候生产、什么时候补货、批量各是多少,目标是整条链总成本的最小化。对于同样的 a=675 的需求曲线,这个集中式模型能算出另一个全局最优解(通常补货与生产步调完全同步,没有任何双重加价或步调错乱的成本浪费)。

实现上的对比就是把两组结果放进同一张表格:比集中式多出来的那部分成本,就是"为分散决策的灵活性所支付的管理溢价"。同时,再对比两者面对需求突变时的调整能力,就得到了"集中式更省钱,但分散式更扛波动"的经典结论。

所以,这个模型从理论落到 Mathematica 代码上,就是一场围绕 ‌"主从嵌套优化 + 次数枚举 + 参数扫描"‌ 的精确计算,每一步都在量化现实供应链中权力下放所带来的成本与弹性。

相关推荐
是Dream呀2 小时前
通道注意力机制|Channel Attention Neural Network
人工智能·python·深度学习
yaoxin5211232 小时前
430. Java 日期时间 API - 时间计算 Temporal 包
java·前端·python
qq 13740186113 小时前
医用无菌屏障系统加速老化标准解读:ASTM F1980-2016 全解析
人工智能·算法·加速老化·包装测试·astm·医疗器械包装·无菌屏障系统
wayz113 小时前
Overlap:SLOPE(线性回归斜率)技术指标详解
算法·金融·数据分析·回归·线性回归·量化交易·特征工程
RSTJ_16253 小时前
PYTHON+AI LLM DAY SEVENTY
人工智能·python·深度学习
点云兔子3 小时前
舱口检测:从点云到矩形定位的射线投影算法
opencv·算法·点云·舱口检测
小欣加油3 小时前
leetcode169 多数元素
数据结构·c++·算法·leetcode·职场和发展
财经资讯数据_灵砚智能3 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(日间)2026年6月8日
大数据·人工智能·python·ai·信息可视化·自然语言处理·灵砚智能
wayz113 小时前
Momentum:RVGI(相对活力指数)技术指标详解
算法·金融·数据分析·量化交易·特征工程