
1. 项目概述当SEIR模型遇上新冠疫情如果你是刚开始接触Python和数学建模的朋友看到“SEIR模型”和“微分方程”这些词可能有点发怵觉得这是数学系高材生才能玩转的东西。别担心我最初也是从“小白”状态过来的。今天我们就用Python这把“瑞士军刀”来拆解一个听起来高大上但实际理解起来非常直观的模型——SEIR并把它应用到我们每个人都亲身经历过的“新冠疫情”分析中。这不仅仅是一个编程练习更是一次理解传染病如何传播、防控措施为何有效的思维训练。简单来说SEIR模型是一个经典的传染病动力学模型它把人群分成了四类易感者S、潜伏者E、感染者I、康复者R。这次新冠疫情恰恰是展示这个模型威力的绝佳案例。通过Python我们可以把抽象的微分方程变成动态的曲线和动画直观地看到隔离强度、潜伏期长短如何影响疫情发展。无论你是想参加数学建模竞赛寻找思路还是单纯对疫情传播规律感到好奇亦或是想通过一个有趣的项目来巩固Python数据分析与科学计算技能这篇内容都能给你提供一条清晰的路径。我们不追求复杂的数学推导而是聚焦于“如何用代码实现”以及“每个参数背后的现实意义是什么”让你在动手的过程中真正理解模型的内涵。2. SEIR模型核心原理与新冠疫情适配性分析2.1 模型分室解析S, E, I, R 分别代表谁SEIR模型的核心在于对人群进行精细划分这比早期简单的SIR模型多了一个“E”室正是这个“E”室让模型对新冠这类存在显著潜伏期的传染病描述得更加准确。易感者 (Susceptible, S)指那些从未感染过该病毒且没有免疫力有可能被感染的人群。在疫情初期几乎整个人口都属于易感者扣除极少数初始感染者。这是疫情的“燃料”。潜伏者 (Exposed, E)这是SEIR模型的关键。指那些已经被病毒感染但还没有出现临床症状也无法传染给他人或传染性极低的个体。对于新冠病毒这对应的就是潜伏期。这部分人是疫情防控的难点因为他们难以被识别和隔离。感染者 (Infectious, I)指那些已经发病并且具有传染性的人群。他们是疫情传播的主要源头。通过核酸检测、症状筛查等手段这部分人群相对容易被发现和隔离。康复者 (Recovered, R)指那些从感染中恢复并且获得了免疫力假设在一段时间内免疫不再容易被感染也不再传染他人的人群。他们退出了传染链。注意在经典的SEIR模型中通常假设康复者获得永久免疫。但对于新冠考虑到病毒变异和免疫力衰减更精确的模型可能会考虑康复者再次变为易感者SEIRS模型但这会增加复杂度。我们本次先从基础经典模型入手。2.2 微分方程描述人群流动的“规则书”模型的分室结构决定了人群如何流动而微分方程就是用数学语言精确描述这些流动规则的“说明书”。每一类人群数量的变化率都等于“流入”减去“流出”。假设总人口数 ( N S E I R ) 保持不变不考虑出生和死亡我们有如下方程组易感者 (S) 的变化率 [ \frac{dS}{dt} -\frac{\beta I S}{N} ]为什么是负号因为易感者只会减少被感染。(\beta) 是什么这是感染率一个非常关键的参数。它综合反映了病毒的传播能力如基本再生数R0的一部分和人群的接触频率。(\beta I S / N) 表示感染者I与易感者S接触并导致感染的概率。分母N是归一化处理。潜伏者 (E) 的变化率 [ \frac{dE}{dt} \frac{\beta I S}{N} - \sigma E ]流入部分(\beta I S / N)即从易感者S中新感染进入潜伏期的人。流出部分(\sigma E)。(\sigma) 是什么这是潜伏期的倒数即 (\sigma 1 / (\text{平均潜伏期}))。例如如果平均潜伏期是5天那么 (\sigma 1/5 0.2)。这意味着每天约有20%的潜伏者会结束潜伏期进入发病状态。感染者 (I) 的变化率 [ \frac{dI}{dt} \sigma E - \gamma I ]流入部分(\sigma E)从潜伏者E转化而来。流出部分(\gamma I)。(\gamma) 是什么这是康复率或移出率的倒数即 (\gamma 1 / (\text{平均感染期}))。例如平均感染期从发病到康复或隔离不再传染为10天则 (\gamma 1/10 0.1)。这意味着每天约有10%的感染者会康复。康复者 (R) 的变化率 [ \frac{dR}{dt} \gamma I ]流入部分(\gamma I)从感染者I康复而来。没有流出在基础模型中康复者永久免疫。这组方程构成了一个封闭系统。给定初始时刻的S0, E0, I0, R0和参数 (\beta, \sigma, \gamma)我们就可以预测未来任何时刻四类人群的数量。2.3 为何SEIR特别适合模拟新冠疫情新冠疫情展现了几个典型特征使得SEIR模型比SIR模型更具解释力显著的潜伏期新冠病毒潜伏期平均约5-7天在此期间感染者没有症状但已携带病毒后期研究显示潜伏期末期可能有传染性但经典SEIR假设潜伏期无传染性已能说明很多问题。E室的引入精准刻画了这一阶段。防控措施的影响可直接映射到参数社交距离、戴口罩这些措施直接降低了人群的有效接触率体现在模型中就是降低了感染率(\beta)。我们可以通过改变(\beta)来模拟不同严格程度的防控。快速检测与隔离一旦发现感染者立即隔离相当于缩短了感染期因为被隔离的感染者不再接触易感者。这体现在模型中就是增大了康复率(\gamma)因为平均感染期变短了。密接追踪与隔离如果能快速找到并隔离潜伏者E就相当于增大了潜伏者移出率可以引入新的参数或理解为部分E不经过I直接进入R。这能极大压制疫情。直观展示疫情发展轨迹通过求解方程我们可以画出感染者人数随时间变化的曲线。这条曲线是单峰还是多峰峰值有多高何时到来这些都能直观展示不同防控策略的效果。3. Python实现SEIR模型的关键技术栈3.1 工具选型为什么是SciPy Matplotlib对于这类常微分方程ODE问题Python生态中有非常成熟且易用的工具链我们不需要从零开始编写数值积分代码。NumPy这是科学计算的基石。用于处理数组、进行向量和矩阵运算。我们的S, E, I, R在计算过程中都会被当作数组来处理。SciPy这是核心中的核心。它的scipy.integrate模块提供了多种ODE求解器我们主要使用solve_ivpInitial Value Problem初值问题求解器。它功能强大能自动处理步长支持多种数值方法如RK45即四阶-五阶Runge-Kutta法我们只需要定义好微分方程即可。Matplotlib这是绘图和可视化的标准库。我们将用它来绘制四类人群随时间变化的曲线以及制作疫情传播的动态示意图。可选Pandas如果你需要处理真实的疫情数据如每日新增确诊并用于参数估计或模型验证Pandas用于数据清洗和整理会非常方便。实操心得对于初学者建议先使用Anaconda发行版来安装Python环境它已经集成了上述所有科学计算库避免了自己配置环境时可能遇到的依赖冲突问题。在VSCode或Jupyter Notebook中编写和运行代码交互性更好便于调试和观察每一步的结果。3.2 模型求解的核心scipy.integrate.solve_ivp详解solve_ivp是我们实现模型的大脑。它的基本调用方式如下from scipy.integrate import solve_ivp solution solve_ivp(fun, t_span, y0, args(), methodRK45, t_evalNone, ...)fun这是最重要的参数是一个函数用于定义微分方程组。它必须接受两个参数(t, y)其中t是时间标量y是状态向量比如[S, E, I, R]并返回状态向量的导数dydt。t_span积分的时间区间一个二元组(t_start, t_end)。y0初始状态向量。对应我们设定的S0, E0, I0, R0。args一个元组用于传递给fun的额外参数。这里我们会把beta, sigma, gamma, N传进去。method数值方法默认‘RK45’自适应步长的Runge-Kutta法对于大多数非刚性问题我们的SEIR模型通常不是刚性的已经足够好。t_eval可选。一个时间点数组求解器会输出在这些精确时间点上的解。如果不提供求解器会自己选择输出点。理解如何定义fun函数是成功的关键。这个函数封装了我们之前在2.2节写下的那组微分方程。3.3 参数设定与初始条件的现实依据模型不是空中楼阁参数需要尽量贴近现实。以下是一些基于新冠疫情早期研究的典型参考值请注意这些值会因病毒株、地区、时期而异建模时需要根据实际情况调整或进行参数估计。总人口 N根据你要模拟的区域设定例如N 1e7表示一千万人口。有时为了方便我们会使用归一化人口令N1此时S,E,I,R表示比例。初始条件S0 N - I0 - E0 - R0。疫情初期康复者R0通常为0。I0初始感染者。可以是一个很小的数比如I0 10。E0初始潜伏者。通常设为E0 I0 * kk是一个倍数因为一个感染者背后可能已有多个被感染但处于潜伏期的人。可以简单设E0 0或E0 I0。关键流行病学参数基本再生数 R0这是一个更直观的流行病学指标表示在完全易感人群中一个感染者平均能传染多少人。在SEIR模型中它与我们的参数有关系( R_0 \beta / \gamma )这是SIR模型的关系对于SEIR由于存在潜伏期公式略有不同但概念相通。新冠原始毒株的R0估计在2.5-3.5之间。感染率 β如果我们知道了R0和平均感染期D可以通过 ( \beta \approx R_0 / D ) 进行粗略估算。例如R03, D10天则 β ≈ 0.3。潜伏期倒数 σ如果平均潜伏期是L天则 ( \sigma 1 / L )。新冠L≈5-7天取6天则 σ ≈ 0.1667。康复率 γ如果平均感染期是D天则 ( \gamma 1 / D )。新冠D≈10-14天从具有传染性到隔离或康复取10天则 γ 0.1。注意事项这些参数是高度简化的。现实中β不是一个常数它会随着防控措施封控、社交距离的加强而动态降低。一个更高级的模拟会将β设置为时间的函数例如在某个时间点后β值减半来模拟防控措施生效。4. 从零到一Python代码实现与分步解读4.1 环境准备与库导入首先确保你的Python环境已经安装了必要的库。如果使用Anaconda通常已经自带。如果没有可以通过pip安装pip install numpy scipy matplotlib然后在Python脚本或Jupyter Notebook中导入它们import numpy as np from scipy.integrate import solve_ivp import matplotlib.pyplot as plt # 为了在Notebook中显示图片 %matplotlib inline # 设置中文字体和负号显示正常可选如需中文标签 plt.rcParams[font.sans-serif] [SimHei, DejaVu Sans] plt.rcParams[axes.unicode_minus] False4.2 定义SEIR微分方程组函数这是整个代码的灵魂。我们需要定义一个函数seir_model它计算在时间t、状态y下的导数dydt。def seir_model(t, y, beta, sigma, gamma, N): 定义SEIR模型的微分方程组。 参数: t: 时间求解器自动传入 y: 当前状态向量 [S, E, I, R] beta: 感染率 sigma: 潜伏期倒数 gamma: 康复率 N: 总人口 返回: dydt: 导数向量 [dS/dt, dE/dt, dI/dt, dR/dt] S, E, I, R y # 解包状态向量 # 微分方程 dS_dt -beta * I * S / N dE_dt beta * I * S / N - sigma * E dI_dt sigma * E - gamma * I dR_dt gamma * I return [dS_dt, dE_dt, dI_dt, dR_dt]代码解读函数签名(t, y, ...)必须严格遵守solve_ivp对fun的要求。y是一个包含四个元素的列表或数组我们通过元组解包S, E, I, R y将其赋值给四个有意义的变量这样后面的方程写起来更清晰。方程部分就是直接翻译我们之前写下的数学公式。最后返回一个列表包含了四个导数值顺序必须与y中状态的顺序一致。4.3 设置参数、初始条件与求解时段接下来我们给模型赋予具体的数值。这里我们模拟一个千万级人口城市疫情初期的情况。# 总人口 N 10_000_000 # 模型参数 R0 3.0 # 基本再生数 D_infectious 10 # 平均感染期 (天) L_latent 6 # 平均潜伏期 (天) gamma 1 / D_infectious # 康复率 sigma 1 / L_latent # 潜伏期倒数 beta R0 * gamma # 感染率 (基于SIR模型的简化关系用于初步估算) print(f计算得到的参数beta{beta:.4f}, sigma{sigma:.4f}, gamma{gamma:.4f}) # 初始条件 (第0天) I0 10 # 初始感染者 E0 50 # 初始潜伏者 (假设比感染者多) R0_num 0 # 初始康复者 (为避免与R0混淆这里用R0_num) S0 N - I0 - E0 - R0_num # 初始状态向量 y0 [S0, E0, I0, R0_num] # 模拟时间范围 (0到200天) t_span (0, 200) # 我们希望输出的时间点 (每天一个点) t_eval np.linspace(t_span[0], t_span[1], 201) # 0, 1, 2, ..., 200参数计算逻辑这里我们从一个更直观的流行病学参数R0和D_infectious出发反向计算出模型所需的beta和gamma。这是一种常见的参数化方式让模型更贴近流行病学常识。4.4 调用求解器并提取结果现在万事俱备我们将所有部件组装起来调用solve_ivp。# 求解微分方程 solution solve_ivp( funseir_model, t_spant_span, y0y0, args(beta, sigma, gamma, N), # 传递给seir_model的额外参数 methodRK45, t_evalt_eval, # 指定输出时间点 rtol1e-6, # 相对误差容限控制精度 atol1e-9 # 绝对误差容限 ) # 检查求解是否成功 if solution.success: print(模型求解成功) else: print(求解失败:, solution.message) # 提取结果 t solution.t # 时间点数组 S, E, I, R solution.y # 解数组每一行对应一个状态的时间序列solve_ivp返回一个OdeResult对象solution。solution.t是时间点solution.y是一个形状为(4, len(t))的数组第0行是S第1行是E第2行是I第3行是R。我们通过解包赋值给对应的变量。4.5 结果可视化绘制疫情发展曲线一张图胜过千言万语。让我们把四类人群的变化曲线画出来。# 创建画布和坐标轴 plt.figure(figsize(12, 8)) # 绘制四条曲线 plt.plot(t, S, label易感者 Susceptible, linewidth2, colorblue) plt.plot(t, E, label潜伏者 Exposed, linewidth2, colororange, linestyle--) plt.plot(t, I, label感染者 Infectious, linewidth3, colorred) plt.plot(t, R, label康复者 Recovered, linewidth2, colorgreen) # 添加图例、标题和标签 plt.legend(fontsize12) plt.title(SEIR模型模拟 - 新冠疫情传播动力学 (无干预), fontsize16, fontweightbold) plt.xlabel(时间 (天), fontsize14) plt.ylabel(人数, fontsize14) plt.grid(True, linestyle--, alpha0.7) # 设置坐标轴范围更清晰地观察感染者曲线 plt.ylim(bottom0) # 可以添加一条峰值线 peak_I I.max() peak_day t[I.argmax()] plt.axvline(xpeak_day, colorgray, linestyle:, alpha0.5) plt.text(peak_day2, peak_I/2, f峰值: {peak_day:.0f}天\n感染人数: {peak_I:.0f}, fontsize10) plt.tight_layout() plt.show()这段代码会生成一张标准的SEIR模型动态曲线图。你会看到蓝色S曲线从高位迅速下降代表易感人群被消耗。红色I曲线感染者呈现一个典型的单峰形状。峰值的高度和出现时间是评估疫情严重程度的关键。峰值越高医疗系统压力越大峰值出现越晚给防控准备的时间越多。橙色虚线E曲线潜伏者其峰值通常略早于感染者I的峰值。绿色R曲线单调上升最终趋于平稳代表获得免疫的人群比例。实操心得在绘图时特意将感染者I的线宽加粗、颜色用红色是为了突出我们最关心的“疫情高峰”。添加峰值标注线能让读者一眼抓住关键信息。这是做数据可视化时提升信息传达效率的一个小技巧。5. 模型应用与拓展模拟防控措施与参数分析5.1 模拟不同防控强度动态感染率β现实中的防控措施如封城、强制戴口罩不是从第一天就开始也不是一成不变的。我们可以通过让感染率β随时间变化来模拟这一点。假设疫情第50天开始采取严格防控使有效接触率降低60%即β变为原来的0.4倍。def seir_model_with_intervention(t, y, sigma, gamma, N): 带干预措施的SEIR模型感染率beta是时间的函数 S, E, I, R y # 定义随时间变化的感染率 beta(t) if t 50: beta 0.3 # 初期无干预 else: beta 0.3 * 0.4 # 第50天后干预生效感染率降低60% dS_dt -beta * I * S / N dE_dt beta * I * S / N - sigma * E dI_dt sigma * E - gamma * I dR_dt gamma * I return [dS_dt, dE_dt, dI_dt, dR_dt] # 使用相同的初始条件和参数除了beta solution_int solve_ivp( funseir_model_with_intervention, t_spant_span, y0y0, args(sigma, gamma, N), # beta不再作为固定参数传入 methodRK45, t_evalt_eval ) # 提取结果并绘图对比 I_no_int I # 之前无干预的结果 I_with_int solution_int.y[2] # 新求解的感染者序列 plt.figure(figsize(12, 6)) plt.plot(t, I_no_int, label无干预, linewidth3, colordarkred) plt.plot(t, I_with_int, label第50天起严格干预, linewidth3, colordarkgreen, linestyle-) plt.axvline(x50, colorgray, linestyle--, alpha0.7, label干预开始) plt.xlabel(时间 (天)) plt.ylabel(感染人数 I) plt.title(防控措施效果对比感染者数量变化, fontsize15) plt.legend() plt.grid(True, linestyle--, alpha0.5) plt.tight_layout() plt.show()对比图会清晰地显示干预措施实施后感染者数量的上升趋势被迅速扭转峰值被显著压低和推迟。这直观地解释了“拉平曲线”的概念目的是避免医疗资源被击穿。5.2 参数敏感性分析R0与潜伏期的影响模型的预测结果严重依赖于参数。我们需要了解哪些参数影响最大以及不确定性如何影响预测。这里我们分析R0和平均潜伏期L的影响。# 定义参数范围 R0_values [2.0, 3.0, 4.0] # 低、中、高传播力 L_values [4, 6, 8] # 短、中、长潜伏期 plt.figure(figsize(15, 10)) # 子图1: 不同R0的影响 (固定L6天) for i, R0 in enumerate(R0_values): beta_temp R0 * gamma sol solve_ivp(seir_model, t_span, y0, args(beta_temp, sigma, gamma, N), t_evalt_eval) plt.subplot(2, 2, 1) plt.plot(t, sol.y[2], labelfR0{R0}, linewidth2) plt.subplot(2, 2, 1) plt.title(不同基本再生数R0的影响 (L6天)) plt.xlabel(时间 (天)) plt.ylabel(感染人数 I) plt.legend() plt.grid(True, linestyle--, alpha0.5) # 子图2: 不同潜伏期L的影响 (固定R03.0) for i, L in enumerate(L_values): sigma_temp 1 / L sol solve_ivp(seir_model, t_span, y0, args(beta, sigma_temp, gamma, N), t_evalt_eval) plt.subplot(2, 2, 2) plt.plot(t, sol.y[2], labelfL{L}天, linewidth2) plt.subplot(2, 2, 2) plt.title(不同平均潜伏期L的影响 (R03.0)) plt.xlabel(时间 (天)) plt.ylabel(感染人数 I) plt.legend() plt.grid(True, linestyle--, alpha0.5) # 子图3: 峰值感染人数与R0的关系 peak_infections [] R0_range np.linspace(1.5, 5.0, 15) for r0 in R0_range: beta_temp r0 * gamma sol solve_ivp(seir_model, (0, 300), y0, args(beta_temp, sigma, gamma, N), dense_outputTrue) # 使用更精细的插值寻找峰值 t_dense np.linspace(0, 300, 1000) I_dense sol.sol(t_dense)[2] peak_infections.append(I_dense.max()) plt.subplot(2, 2, 3) plt.plot(R0_range, peak_infections, o-, linewidth2, markersize6) plt.title(疫情峰值规模 vs. 基本再生数R0) plt.xlabel(R0) plt.ylabel(感染者峰值人数) plt.grid(True, linestyle--, alpha0.5) plt.tight_layout() plt.show()通过这个分析你会发现R0的影响是决定性的。R0从2.0增加到4.0疫情峰值可能增加数倍且峰值时间提前。这解释了为何高传染性的变异毒株如奥密克戎会带来海啸般的疫情。潜伏期L的影响更侧重于疫情发展的速度。潜伏期越短疫情爆发越快越猛潜伏期越长疫情发展更平缓但给了病毒更长的隐匿传播时间。峰值与R0的关系是非线性的可能存在一个临界点超过后峰值人口占比会急剧上升。5.3 模型局限性讨论与改进方向经典的SEIR模型是一个强大的教学和初步分析工具但它也有明显的简化在应用于复杂现实时需要知其局限性均匀混合假设模型假设人群完全均匀混合任何易感者和感染者接触机会均等。现实中接触网络具有复杂的结构家庭、学校、工作场所超级传播事件正是网络异质性的体现。改进方向是使用网络模型如基于图的模型。参数常数假设将β、γ等视为常数。现实中它们会随着防控措施、公众行为、医疗资源挤兑、病毒变异而动态变化。改进方向是引入时变参数或分段函数。忽略人口统计学未考虑年龄结构、空间地理差异。改进方向是建立分年龄组、分区域的仓室模型。免疫力假设假设康复后永久免疫。对于新冠需要考虑免疫力衰减和重复感染即SEIRS模型。未区分无症状感染模型中所有感染者I都具有相同传染性。现实中无症状感染者的传染性和传播模式可能与有症状者不同。可以扩展为SEIAR模型A代表无症状感染者。对于数学建模竞赛清晰地说明这些局限性并提出一两个可行的改进思路往往是论文的加分项。6. 常见问题与调试技巧实录在实际编码和模拟过程中你可能会遇到一些典型问题。以下是我在多次实践中总结的排查清单问题现象可能原因排查与解决方法求解失败或出现NaN1. 微分方程函数seir_model返回值顺序错误或正负号错误。2. 参数值极端不合理如β极大导致数值不稳定。3. 初始条件导致除零错误如S00。1.打印调试在seir_model函数内加入print(t, y)观察初始几步的计算值是否合理。2.检查方程逐项核对微分方程公式特别是符号和分母。3.参数归一化尝试令总人口N1S,E,I,R表示比例可以避免大数运算。4.调整求解器尝试使用methodLSODA它对刚性问题更稳健。曲线看起来“不对”如感染者数不增长或爆炸式增长1. 参数β和γ的量级不匹配。例如β太小或γ太大导致R01疫情无法传播。2. 初始感染者I0相对于N太小在绘图尺度上看不到变化。1.计算并打印R0确保R0 beta / gamma大于1对于能传播的疫情。2.检查初始值确保I0和E0不为零且S0远大于I0E0。3.绘图时使用对数坐标plt.yscale(log)可以观察数量级的微小变化。总人口数SEIR不守恒数值积分引入的微小误差。这是正常现象对于非刚性的RK45方法误差通常极小。1.验证计算total S E I R并打印其最大值和最小值看波动是否在可接受范围如1e-6量级。2.提高精度减小solve_ivp的rtol和atol参数如设为1e-8, 1e-11但会增加计算时间。模拟结果与真实数据对不上1. 模型过于简化未考虑现实中的干预措施、人口流动等。2. 参数取值与实际情况不符。1.引入时变参数如5.1节所示模拟防控措施。2.参数估计使用真实数据如每日新增确诊通过最小二乘法等优化算法来反推最优的β、σ等参数。这是数学建模中更高级的一步。代码运行慢模拟时间跨度太长或t_eval点过于密集。1. 适当缩短t_span。2. 减少t_eval的点数或让求解器自动选择输出点不设置t_eval。3. 对于非常复杂的模型考虑使用更高效的求解器或语言如Julia。独家避坑技巧在定义微分方程函数时一个常见的错误是混淆beta * I * S / N中的N。务必使用传入的总人口N而不是当前的SEIR。虽然在理论上两者相等但在数值计算中由于积分误差SEIR可能不完全等于初始的N使用它作为分母可能导致意想不到的微小偏差在长期模拟中放大。始终使用初始的、恒定的N是更稳健的做法。最后我想分享的一点个人体会是学习数学建模尤其是用代码实现它最好的方法就是“动手做-观察结果-调整参数-思考为什么”。SEIR模型就像一块积木通过修改方程比如加入隔离仓室Q、改变参数函数你可以搭建出模拟各种复杂场景的模型。这个从简单到复杂、从理想到现实的过程本身就是对系统思维和问题解决能力的绝佳训练。当你看到自己写出的几行代码能够模拟出一场疫情的兴衰并能通过调整参数来验证“戴口罩真的有用”、“早发现早隔离至关重要”这些公共卫生策略时那种成就感是无可替代的。