From 5f9d8e351d8fd71fa82e27b0b321264c458d65fc Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Sat, 18 Jul 2026 18:18:42 +1000 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=94=84=20resync=20mccall=5Fmodel=5Fwi?= =?UTF-8?q?th=5Fseparation.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../state/mccall_model_with_separation.md.yml | 6 + lectures/mccall_model_with_separation.md | 648 +++++++++++------- 2 files changed, 412 insertions(+), 242 deletions(-) create mode 100644 .translate/state/mccall_model_with_separation.md.yml diff --git a/.translate/state/mccall_model_with_separation.md.yml b/.translate/state/mccall_model_with_separation.md.yml new file mode 100644 index 00000000..0236381e --- /dev/null +++ b/.translate/state/mccall_model_with_separation.md.yml @@ -0,0 +1,6 @@ +source-sha: 0bfcac8105ac8c11f06797579a9f4d565db7b53d +synced-at: "2026-07-18" +model: claude-sonnet-5 +mode: RESYNC +section-count: 8 +tool-version: 0.17.0 diff --git a/lectures/mccall_model_with_separation.md b/lectures/mccall_model_with_separation.md index cc31ef39..c5bdb383 100644 --- a/lectures/mccall_model_with_separation.md +++ b/lectures/mccall_model_with_separation.md @@ -9,6 +9,29 @@ kernelspec: name: python3 display_name: Python 3 (ipykernel) language: python +translation: + title: 工作搜寻 II:搜寻与离职 + headings: + Overview: 概述 + The model: 模型 + The model::Timing and decisions: 时间安排和决策 + Solving the model: 求解模型 + Solving the model::The Bellman equations: 贝尔曼方程 + Solving the model::The reservation wage: 保留工资 + Code: 代码 + Code::Set up: 设置 + Code::Operators: 算子 + Code::Iteration: 迭代 + Code::Computing the reservation wage: 计算保留工资 + A simplifying transformation: 简化变换 + A simplifying transformation::Simplifying to a single equation: 简化为单一方程 + A simplifying transformation::Solving the Bellman equations: 求解贝尔曼方程 + Implementation: 实现 + Impact of parameters: 参数的影响 + Impact of parameters::The reservation wage and unemployment compensation: 保留工资和失业补偿 + Impact of parameters::The reservation wage and discounting: 保留工资和贴现 + Impact of parameters::The reservation wage and job destruction: 保留工资和工作破坏 + Exercises: 练习 --- (mccall_with_sep)= @@ -22,6 +45,9 @@ kernelspec: # 工作搜寻 II:搜寻与离职 +```{include} _admonition/gpu.md +``` + ```{index} single: 工作搜寻导论 ``` @@ -34,7 +60,7 @@ kernelspec: ```{code-cell} ipython3 :tags: [hide-output] -!pip install quantecon +!pip install quantecon jax myst-nb ``` ## 概述 @@ -43,29 +69,25 @@ kernelspec: 在之前的模型中,我们假设工作是永久性的,这不太符合现实。 -本讲座将通过引入离职的可能性来使McCall模型更加贴近现实。 +本讲座将通过引入离职来扩展该模型。 -一旦引入离职,个体会有不同的考虑: +一旦引入离职,个体会认为: -* 失业不仅仅是一个暂时状态,而是随时可能发生的资本损失 -* 失业期间的工作搜寻成为寻找下一份工作的*投资* +* 失去工作是一种资本损失,并且 +* 一段失业期是对寻找一份可接受工作的*投资* -我们还将引入效用函数来更好地刻画劳动者的偏好。 +另一个小的补充是,我们将引入一个效用函数,使劳动者的偏好更加复杂一些。 -让我们首先导入所需的包: +我们需要以下导入: ```{code-cell} ipython3 import matplotlib.pyplot as plt -import matplotlib as mpl -FONTPATH = "fonts/SourceHanSerifSC-SemiBold.otf" -mpl.font_manager.fontManager.addfont(FONTPATH) -plt.rcParams['font.family'] = ['Source Han Serif SC'] - -plt.rcParams["figure.figsize"] = (11, 5) #设置默认图形大小 import numpy as np -from numba import jit, float64 -from numba.experimental import jitclass +import jax +import jax.numpy as jnp +from typing import NamedTuple from quantecon.distributions import BetaBinomial +from myst_nb import glue ``` ## 模型 @@ -92,33 +114,25 @@ from quantecon.distributions import BetaBinomial 它满足$u'> 0$和$u'' < 0$。 -### 工资过程 - -为了简化模型,我们不再像{doc}`基础模型 `那样将状态过程和工资过程分开。 - -我们直接假设工资报价$\{w_t\}$是从一个共同分布$q$中独立抽取的。 - -我们用$\mathbb W$表示所有可能的工资值集合。 +工资报价$\{ W_t \}$是从一个共同分布$q$中独立同分布抽取的。 -(在后面的章节中,我们会重新引入独立的状态过程$\{s_t\}$来驱动随机结果,因为这种方式在处理更复杂的模型时会更加方便。) +所有可能的工资值集合记为$\mathbb W$。 ### 时间安排和决策 在每个时期开始时,个体可以是: * 失业,或 -* 在某个现有工资水平$w_e$就业。 +* 在某个现有工资水平$w$就业。 -在给定时期开始时,观察到当前工资报价$w_t$。 +如果当前以工资$w$就业,劳动者: -如果当前*就业*,劳动者: +1. 从当前工资中获得效用$u(w)$,并且 +1. 以某个(小的)概率$\alpha$被解雇,下一期变为失业。 -1. 获得效用$u(w_e)$,并且 -1. 以某个(小的)概率$\alpha$被解雇。 +如果当前失业,劳动者会收到随机工资报价$W_t$,并选择接受或拒绝。 -如果当前*失业*,劳动者可以接受或拒绝当前报价$w_t$。 - -如果他接受,则立即以工资$w_t$开始工作。 +如果他接受,则立即以工资$W_t$开始工作。 如果他拒绝,则获得失业补偿$c$。 @@ -134,315 +148,465 @@ from quantecon.distributions import BetaBinomial 令: -* $v(w_e)$为进入当前时期*就业*且现有工资为$w_e$的劳动者的总终身价值 -* $h(w)$为进入当前时期*失业*且收到工资报价$w$的劳动者的总终身价值。 +* $v_e(w)$为进入当前时期时以工资$w$就业的劳动者的最大终身价值 +* $v_u(w)$为进入当前时期时失业并收到工资报价$w$的劳动者的最大终身价值。 + +这里,**最大终身价值**是指当劳动者在所有未来时间点都做出最优决策时目标函数{eq}`objective`的值。 -这里的*价值*是指当劳动者在所有未来时间点都做出最优决策时目标函数{eq}`objective`的值。 +正如我们接下来将展示的,得到这些函数是求解该模型的关键。 -我们的首要目标是获得这些函数。 ### 贝尔曼方程 -假设现在劳动者可以计算函数$v$和$h$并在决策中使用它们。 +我们回忆一下,在{doc}`最初的工作搜寻模型 `中,价值函数(在给定工资报价下失业的价值)满足一个贝尔曼方程。 + +这里,该函数同样满足一个非常相似的贝尔曼方程。 -那么$v$和$h$应该满足: ```{math} -:label: bell1_mccall +:label: bell2_mccall -v(w_e) = u(w_e) + \beta - \left[ - (1-\alpha)v(w_e) + \alpha \sum_{w' \in \mathbb W} h(w') q(w') - \right] + v_u(w) = \max + \left\{ + v_e(w), \, + u(c) + \beta \sum_{w' \in \mathbb W} v_u(w') q(w') + \right\} ``` -和 +不同之处在于,接受的价值是$v_e(w)$,而不是$w/(1-\beta)$。 + +我们必须做出这个改变,因为工作不是永久性的。 + +接受工作使劳动者转为就业,因此获得回报$v_e(w)$,我们将在下面讨论这一点。 + +拒绝则导致失业补偿和明天的失业。 + +方程{eq}`bell2_mccall`表达了手中持有报价$w$而处于失业状态的价值,它是两个选项价值的最大值:接受或拒绝当前报价。 + +函数$v_e$也满足一个贝尔曼方程: ```{math} -:label: bell2_mccall +:label: bell1_mccall -h(w) = \max \left\{ v(w), \, u(c) + \beta \sum_{w' \in \mathbb W} h(w') q(w') \right\} + v_e(w) = u(w) + \beta + \left[ + (1-\alpha)v_e(w) + \alpha \sum_{w' \in \mathbb W} v_u(w') q(w') + \right] ``` -方程{eq}`bell1_mccall`表达了以工资$w_e$就业的价值,包括: +```{note} +这个方程与传统的贝尔曼方程不同,因为其中没有max运算。 -* 当前报酬$u(w_e)$加上 -* 考虑到$\alpha$被解雇概率的贴现预期价值 +之所以没有max,是因为一个就业中的个体没有选择余地。 -方程{eq}`bell2_mccall`表达了失业且手中有报价$w$的价值,作为两个选项的最大值:接受或拒绝当前报价。 +尽管如此,为了与大多数文献保持一致,我们仍然将其称为贝尔曼方程。 -接受使劳动者转为就业,因此获得价值$v(w)$。 +``` -拒绝导致失业补偿和明天的失业。 +方程{eq}`bell1_mccall`用以下内容表达了以工资$w$就业的价值: -方程{eq}`bell1_mccall`和{eq}`bell2_mccall`是该模型的贝尔曼方程。 +* 当前报酬$u(w)$加上 +* 考虑到被解雇概率$\alpha$后的贴现预期明日回报 -它们提供了足够的信息来求解$v$和$h$。 +正如我们将看到的,方程{eq}`bell1_mccall`和{eq}`bell2_mccall`提供了足够的信息来求解$v_e$和$v_u$。 -(ast_mcm)= -### 简化变换 +一旦得到这两个函数,我们就能够做出最优选择。 -与其直接求解这些方程,让我们看看是否能简化它们。 -(这个过程将类似于我们对普通McCall模型的{ref}`第二次尝试 `,在那里我们简化了贝尔曼方程。) +### 保留工资 + -首先,令: +令 ```{math} -:label: defd_mm +:label: defh_mm -d := \sum_{w' \in \mathbb W} h(w') q(w') + h := u(c) + \beta \sum_{w' \in \mathbb W} v_u(w') q(w') ``` -为明天失业的预期价值。 +这是失业个体的**继续价值**——即拒绝当前报价并随后做出最优决策所对应的价值。 -我们现在可以将{eq}`bell2_mccall`写为: +从{eq}`bell2_mccall`中可以看出,如果$v_e(w) \geq h$,失业个体会接受当前报价$w$。 -$$ -h(w) = \max \left\{ v(w), \, u(c) + \beta d \right\} -$$ +这恰恰意味着接受的价值高于拒绝的价值。 + +函数$v_e$在$w$上是递增的,因为更高的当前工资永远不会使就业中的个体变差。 -或者,将时间向前移动一个时期: +因此,我们可以将最优选择表达为:当且仅当$w \geq \bar w$时接受工资报价$w$, +其中**保留工资**$\bar w$是满足以下条件的第一个工资水平$w \in \mathbb W$: $$ -\sum_{w' \in \mathbb W} h(w') q(w') - = \sum_{w' \in \mathbb W} \max \left\{ v(w'), \, u(c) + \beta d \right\} q(w') + v_e(w) \geq h $$ -再次使用{eq}`defd_mm`现在给出: -```{math} -:label: bell02_mccall -d = \sum_{w' \in \mathbb W} \max \left\{ v(w'), \, u(c) + \beta d \right\} q(w') -``` +## 代码 -最后,{eq}`bell1_mccall`现在可以重写为: +现在让我们基于两个贝尔曼方程{eq}`bell2_mccall`和{eq}`bell1_mccall`实现一个求解方法。 -```{math} -:label: bell01_mccall -v(w) = u(w) + \beta - \left[ - (1-\alpha)v(w) + \alpha d - \right] +### 设置 + +默认效用函数是CRRA效用函数 + +```{code-cell} ipython3 +def u(x, γ): + return (x**(1 - γ) - 1) / (1 - γ) ``` -在最后一个表达式中,我们将$w_e$写为$w$以简化符号。 +另外,这是一个基于Beta-二项分布的默认工资分布: -### 保留工资 +```{code-cell} ipython3 +n = 60 # w的n个可能结果 +w_default = jnp.linspace(10, 20, n) # 10到20之间的工资 +a, b = 600, 400 # 形状参数 +dist = BetaBinomial(n-1, a, b) # 分布 +q_default = jnp.array(dist.pdf()) # 以JAX数组形式表示的概率 +``` + +这是我们的带离职的McCall模型类。 -假设我们可以使用{eq}`bell02_mccall`和{eq}`bell01_mccall`来求解$d$和$v$。 +```{code-cell} ipython3 +class Model(NamedTuple): + α: float = 0.2 # 工作离职率 + β: float = 0.98 # 贴现因子 + γ: float = 2.0 # 效用参数(CRRA) + c: float = 6.0 # 失业补偿 + w: jnp.ndarray = w_default # 工资结果空间 + q: jnp.ndarray = q_default # 工资报价上的概率 +``` -(我们很快就会这样做。) -然后我们可以确定劳动者的最优行为。 +### 算子 -从{eq}`bell2_mccall`中,我们看到失业个体接受当前报价$w$如果$v(w) \geq u(c) + \beta d$。 -这表明接受工作的价值超过了继续搜索的预期价值。 +我们将采用与{doc}`第一个工作搜寻讲座 `中类似的迭代方法来求解贝尔曼方程。 -由于更高的工资报价不会让个体更差,$v$是关于$w$的(弱)递增函数。 +第一步,为了对贝尔曼方程进行迭代,我们为每个价值函数定义一个算子,共两个算子。 -这意味着个体的最优策略可以用一个保留工资来表示 - 当且仅当工资报价$w$超过某个临界值时接受工作: +这些算子以当前的价值函数作为输入,并返回更新后的版本。 -$$ -w \geq \bar w -\quad \text{其中} \quad -\bar w \text{ 满足 } v(\bar w) = u(c) + \beta d -$$ +```{code-cell} ipython3 +def T_u(model, v_u, v_e): + """ + 应用失业贝尔曼更新规则,并返回v_u的新猜测值。 -### 求解贝尔曼方程 + """ + α, β, γ, c, w, q = model + h = u(c, γ) + β * (v_u @ q) + v_u_new = jnp.maximum(v_e, h) + return v_u_new +``` -我们将采用与{doc}`第一个工作搜寻讲座 `相同的迭代方法来求解贝尔曼方程。 +```{code-cell} ipython3 +def T_e(model, v_u, v_e): + """ + 应用就业贝尔曼更新规则,并返回v_e的新猜测值。 -具体步骤如下: + """ + α, β, γ, c, w, q = model + v_e_new = u(w, γ) + β * ((1 - α) * v_e + α * (v_u @ q)) + return v_e_new +``` -1. 首先对$d$和$v$的值进行初始猜测 -1. 将这些猜测值代入{eq}`bell02_mccall`和{eq}`bell01_mccall`右侧的表达式 -1. 计算得到新的左侧值,并用这些新值更新猜测 -1. 重复以上步骤直到收敛 -换句话说,我们使用以下规则进行迭代: +### 迭代 -```{math} -:label: bell1001 +现在我们编写一个迭代程序,更新数组对$v_u$,$v_e$直到收敛。 -d_{n+1} = \sum_{w' \in \mathbb W} - \max \left\{ v_n(w'), \, u(c) + \beta d_n \right\} q(w') -``` +更准确地说,我们迭代直到连续的实现结果之间的差异小于某个小的容差水平。 -```{math} -:label: bell2001 +```{code-cell} ipython3 +def solve_full_model( + model, + tol: float = 1e-6, + max_iter: int = 1_000, + ): + """ + 通过迭代求解v_u和v_e两个价值函数。 -v_{n+1}(w) = u(w) + \beta - \left[ - (1-\alpha)v_n(w) + \alpha d_n - \right] -``` + """ + α, β, γ, c, w, q = model + i = 0 + error = tol + 1 + v_e = v_u = w / (1 - β) + + while i < max_iter and error > tol: + v_u_next = T_u(model, v_u, v_e) + v_e_next = T_e(model, v_u, v_e) + error_u = jnp.max(jnp.abs(v_u_next - v_u)) + error_e = jnp.max(jnp.abs(v_e_next - v_e)) + error = jnp.max(jnp.array([error_u, error_e])) + v_u = v_u_next + v_e = v_e_next + i += 1 -从一些初始条件$d_0, v_0$开始。 + return v_u, v_e +``` -如前所述,系统总是收敛到真实解---在这种情况下,是满足{eq}`bell02_mccall`和{eq}`bell01_mccall`的$v$和$d$。 -(可以通过巴拿赫压缩映射定理获得证明。) -## 实现 +### 计算保留工资 -让我们实现这个迭代过程。 +现在我们已经能够求解这两个价值函数了,让我们来研究一下保留工资。 -在代码中,你会看到我们使用一个类来存储与给定模型相关的各种参数和其他对象。 +回顾上面的内容,保留工资$\bar w$是满足$v_e(w) \geq h$的第一个$w \in \mathbb W$,其中$h$是{eq}`defh_mm`中定义的继续价值。 -这有助于整理代码并提供一个易于传递给函数的对象。 +让我们比较$v_e$和$h$,看看它们是什么样子。 -默认效用函数是CRRA效用函数: +我们将使用上述代码中的默认参数化设置。 ```{code-cell} ipython3 -@jit -def u(c, σ=2.0): - return (c**(1 - σ) - 1) / (1 - σ) +model = Model() +α, β, γ, c, w, q = model +v_u, v_e = solve_full_model(model) +h = u(c, γ) + β * (v_u @ q) + +fig, ax = plt.subplots() +ax.plot(w, v_e, 'b-', lw=2, alpha=0.7, label='$v_e$') +ax.plot(w, [h] * len(w), 'g-', lw=2, alpha=0.7, label='$h$') +ax.set_xlim(min(w), max(w)) +ax.legend() +plt.show() ``` -另外,这是一个基于Beta-二项分布的默认工资分布: +价值$v_e$是递增的,因为更高的$w$在保持就业的条件下产生更高的工资流。 -```{code-cell} ipython3 -n = 60 # w的n个可能结果 -w_default = np.linspace(10, 20, n) # 10到20之间的工资 -a, b = 600, 400 # 形状参数 -dist = BetaBinomial(n-1, a, b) -q_default = dist.pdf() -``` +保留工资就是这两条线相交处的$w$值。 -这是我们的McCall模型与离职的即时编译类: +让我们明确计算这个保留工资: ```{code-cell} ipython3 -mccall_data = [ - ('α', float64), # 工作离职率 - ('β', float64), # 贴现因子 - ('c', float64), # 失业补偿 - ('w', float64[:]), # 工资值列表 - ('q', float64[:]) # 随机变量w的概率质量函数 -] - -@jitclass(mccall_data) -class McCallModel: +def compute_reservation_wage_full(model): """ - 存储与给定模型相关的参数和函数。 + 使用完整模型的解计算保留工资。 """ + α, β, γ, c, w, q = model + v_u, v_e = solve_full_model(model) + h = u(c, γ) + β * (v_u @ q) + # 找到第一个使v_e(w) >= h的w,若不存在则为+inf + accept = v_e >= h + i = jnp.argmax(accept) # 返回第一个接受的索引 + w_bar = jnp.where(jnp.any(accept), w[i], jnp.inf) + return w_bar - def __init__(self, α=0.2, β=0.98, c=6.0, w=w_default, q=q_default): +w_bar_full = compute_reservation_wage_full(model) +print(f"保留工资(完整模型):{w_bar_full:.4f}") +``` - self.α, self.β, self.c, self.w, self.q = α, β, c, w, q +这个值似乎接近这两条线相交的位置。 - def update(self, v, d): +(ast_mcm)= +## 简化变换 - α, β, c, w, q = self.α, self.β, self.c, self.w, self.q +上述方法是有效的,但对两个向量值函数进行迭代在计算上代价高昂。 - v_new = np.empty_like(v) +借助一些数学推导和一些脑力劳动,我们可以构造出一种效率高得多的求解方法。 - for i in range(len(w)): - v_new[i] = u(w[i]) + β * ((1 - α) * v[i] + α * d) +(这个过程将类似于我们对普通McCall模型的{ref}`第二次尝试 `,在那里我们把贝尔曼方程简化为一个关于未知标量值(而不是未知向量)的方程。) - d_new = np.sum(np.maximum(v, u(c) + β * d) * q) +首先,我们使用{eq}`defh_mm`中定义的继续价值$h$,将{eq}`bell2_mccall`写为 - return v_new, d_new +$$ + v_u(w) = \max \left\{ v_e(w), \, h \right\} +$$ + +对两边取期望然后贴现,得到 + +$$ +\beta \sum_{w'} v_u(w') q(w') + = \beta \sum_{w'} \max \left\{ v_e(w'), \, h \right\} q(w') +$$ + +将$u(c)$加到两边,并再次使用{eq}`defh_mm`,得到 + +```{math} +:label: bell02_mccall + +h = u(c) + \beta \sum_{w'} \max \left\{ v_e(w'), \, h \right\} q(w') ``` -现在我们迭代直到连续实现之间的差异小于某个小的容差水平。 +这是一个关于继续价值的漂亮的标量方程,已经很有用了。 -然后我们将当前迭代作为近似解返回。 +但我们还可以更进一步,即从上述方程中消去$v_e$。 -```{code-cell} ipython3 -@jit -def solve_model(mcm, tol=1e-5, max_iter=2000): - """ - 迭代求解贝尔曼方程直到收敛 - * mcm是McCallModel的实例 - """ +### 简化为单一方程 - v = np.ones_like(mcm.w) # v的初始猜测 - d = 1 # d的初始猜测 - i = 0 - error = tol + 1 +第一步,我们对定义$h$的表达式(见{eq}`defh_mm`)进行重新整理,得到 - while error > tol and i < max_iter: - v_new, d_new = mcm.update(v, d) - error_1 = np.max(np.abs(v_new - v)) - error_2 = np.abs(d_new - d) - error = max(error_1, error_2) - v = v_new - d = d_new - i += 1 +$$ +\sum_{w'} v_u(w') q(w') = \frac{h - u(c)}{\beta} +$$ + +利用这一点,{eq}`bell1_mccall`中给出的$v_e$的贝尔曼方程现在可以重写为 + +```{math} +:label: bell01_mccall - return v, d +v_e(w) = u(w) + \beta + \left[ + (1-\alpha)v_e(w) + \alpha \frac{h - u(c)}{\beta} + \right] ``` -### 保留工资:第一次尝试 +我们的下一步是求解{eq}`bell01_mccall`,将$v_e$表示为$h$的函数。 -个体的最优选择由保留工资总结。 +对{eq}`bell01_mccall`进行整理,得到 -如上所述,保留工资是满足$v(\bar w) = h$的$\bar w$,其中$h := u(c) + \beta d$是继续值。 +$$ + v_e(w) = u(w) + \beta(1-\alpha)v_e(w) + \alpha(h - u(c)) +$$ -让我们比较$v$和$h$看看它们的样子。 +或者 -我们将使用代码中的默认参数化。 +$$ + v_e(w) - \beta(1-\alpha)v_e(w) = u(w) + \alpha(h - u(c)) +$$ -```{code-cell} ipython3 -mcm = McCallModel() -v, d = solve_model(mcm) -h = u(mcm.c) + mcm.β * d +求解$v_e(w)$,得到 -fig, ax = plt.subplots() +```{math} +:label: v_e_closed -ax.plot(mcm.w, v, 'b-', lw=2, alpha=0.7, label='$v$') -ax.plot(mcm.w, [h] * len(mcm.w), - 'g-', lw=2, alpha=0.7, label='$h$') -ax.set_xlim(min(mcm.w), max(mcm.w)) -ax.legend() + v_e(w) = \frac{u(w) + \alpha(h - u(c))}{1 - \beta(1-\alpha)} +``` -plt.show() +将其代入{eq}`bell02_mccall`,得到 + +```{math} +:label: bell_scalar + +h = u(c) + \beta \sum_{w' \in \mathbb W} \max \left\{ \frac{u(w') + \alpha(h - u(c))}{1 - \beta(1-\alpha)}, \, h \right\} q(w') +``` + +最后,我们得到了一个关于$h$的单一标量方程! + +如果我们能求解出$h$,就能利用{eq}`v_e_closed`轻松地恢复出$v_e$。 + +然后我们就有足够的信息来计算保留工资。 + + +### 求解贝尔曼方程 + +为了求解{eq}`bell_scalar`,我们使用如下迭代规则 + +```{math} +:label: bell_iter + +h_{n+1} = u(c) + \beta \sum_{w' \in \mathbb W} + \max \left\{ \frac{u(w') + \alpha(h_n - u(c))}{1 - \beta(1-\alpha)}, \, h_n \right\} q(w') ``` -价值$v$是递增的,因为更高的$w$在保持就业的条件下产生更高的工资流。 +从某个初始条件$h_0$开始。 + + +(可以通过巴拿赫压缩映射定理证明{eq}`bell_iter`是收敛的。) -### 保留工资:计算 -这是一个函数`compute_reservation_wage`,它接受`McCallModel`的实例并返回相关的保留工资。 + +## 实现 + +为了实现对$h$的迭代,我们提供一个函数,实现从$h_n$到$h_{n+1}$的一次更新 ```{code-cell} ipython3 -@jit -def compute_reservation_wage(mcm): - """ - 通过找到最小的w使得v(w) >= h来计算McCall模型的保留工资。 +def update_h(model, h): + " 对标量h进行一次更新。 " + α, β, γ, c, w, q = model + v_e = compute_v_e(model, h) + h_new = u(c, γ) + β * (jnp.maximum(v_e, h) @ q) + return h_new +``` - 如果不存在这样的w,则w_bar设置为np.inf。 - """ +此外,我们提供一个函数,根据{eq}`v_e_closed`计算$v_e$。 + +```{code-cell} ipython3 +def compute_v_e(model, h): + " 使用闭式表达式从h计算v_e。 " + α, β, γ, c, w, q = model + return (u(w, γ) + α * (h - u(c, γ))) / (1 - β * (1 - α)) +``` + +一旦达到收敛,就会应用这个函数。 + +现在我们可以编写模型求解器了。 - v, d = solve_model(mcm) - h = u(mcm.c) + mcm.β * d +```{code-cell} ipython3 +@jax.jit +def solve_model(model, tol=1e-5, max_iter=2000): + " 迭代求解贝尔曼方程直到收敛。 " + + def cond(loop_state): + h, i, error = loop_state + return jnp.logical_and(error > tol, i < max_iter) + + def update(loop_state): + h, i, error = loop_state + h_new = update_h(model, h) + error_new = jnp.abs(h_new - h) + return h_new, i + 1, error_new + + # 初始化 + h_init = u(model.c, model.γ) / (1 - model.β) + i_init = 0 + error_init = tol + 1 + init_state = (h_init, i_init, error_init) + + final_state = jax.lax.while_loop(cond, update, init_state) + h_final, _, _ = final_state + + # 根据收敛后的h计算v_e + v_e_final = compute_v_e(model, h_final) - i = np.searchsorted(v, h, side='right') - w_bar = mcm.w[i] + return v_e_final, h_final +``` + +最后,这里有一个函数`compute_reservation_wage`,它利用上述所有逻辑,接受`Model`的一个实例并返回相关的保留工资。 + +```{code-cell} ipython3 +def compute_reservation_wage(model): + """ + 通过找到满足v_e(w) >= h的最小w来计算McCall模型某个实例的保留工资。 + """ + # 找到满足v_e(w_i) >= h的第一个i,并返回w[i] + # 如果不存在这样的w,则w_bar设置为np.inf + v_e, h = solve_model(model) + accept = v_e >= h + i = jnp.argmax(accept) # 取第一个接受的索引 + w_bar = jnp.where(jnp.any(accept), model.w[i], jnp.inf) return w_bar ``` +让我们验证一下,这种简化方法是否与完整模型给出相同的答案: + +```{code-cell} ipython3 +w_bar_simplified = compute_reservation_wage(model) +print(f"保留工资(简化方法):{w_bar_simplified:.4f}") +print(f"保留工资(完整模型):{w_bar_full:.4f}") +print(f"差异:{abs(w_bar_simplified - w_bar_full):.6f}") +``` + +正如我们所看到的,两种方法得到的保留工资基本相同。 + +然而,简化方法的效率要高得多。 + 接下来我们将研究保留工资如何随参数变化。 -## 参数的影响 -现在我们将研究保留工资如何随不同参数变化。 +## 参数的影响 -对于每个参数,我们会展示一副图,并在练习中让你动手来重现这些图形。 +在下面的每个例子中,我们会先展示一幅图,然后在练习中让你自己重现它。 ### 保留工资和失业补偿 首先,让我们看看$\bar w$如何随失业补偿变化。 -在下面的图中,我们使用`McCallModel`类中的默认参数,除了c(它在水平轴上取给定值) +在下面的图中,我们使用`Model`类中的默认参数,除了c(它在水平轴上取给定值) -```{figure} /_static/lecture_specific/mccall_model_with_separation/mccall_resw_c.png +```{glue:figure} mccall_resw_c +:figwidth: 600px ``` @@ -456,7 +620,8 @@ def compute_reservation_wage(mcm): 下一个图绘制了与不同$\beta$值相关的保留工资 -```{figure} /_static/lecture_specific/mccall_model_with_separation/mccall_resw_beta.png +```{glue:figure} mccall_resw_beta +:figwidth: 600px ``` @@ -468,7 +633,8 @@ def compute_reservation_wage(mcm): 更高的$\alpha$意味着劳动者在就业后每个时期面临终止的可能性更大。 -```{figure} /_static/lecture_specific/mccall_model_with_separation/mccall_resw_alpha.png +```{glue:figure} mccall_resw_alpha +:figwidth: 600px ``` @@ -490,9 +656,9 @@ def compute_reservation_wage(mcm): ```{code-cell} ipython3 grid_size = 25 -c_vals = np.linspace(2, 12, grid_size) # 失业补偿 -beta_vals = np.linspace(0.8, 0.99, grid_size) # 贴现因子 -alpha_vals = np.linspace(0.05, 0.5, grid_size) # 离职率 +c_vals = jnp.linspace(2, 12, grid_size) # 失业补偿 +β_vals = jnp.linspace(0.8, 0.99, grid_size) # 贴现因子 +α_vals = jnp.linspace(0.05, 0.5, grid_size) # 离职率 ``` ```{exercise-end} @@ -505,58 +671,56 @@ alpha_vals = np.linspace(0.05, 0.5, grid_size) # 离职率 这是第一幅图。 ```{code-cell} ipython3 -mcm = McCallModel() +def compute_res_wage_given_c(c): + model = Model(c=c) + w_bar = compute_reservation_wage(model) + return w_bar -w_bar_vals = np.empty_like(c_vals) +w_bar_vals = jax.vmap(compute_res_wage_given_c)(c_vals) fig, ax = plt.subplots() - -for i, c in enumerate(c_vals): - mcm.c = c - w_bar = compute_reservation_wage(mcm) - w_bar_vals[i] = w_bar - -ax.set(xlabel='失业补偿', - ylabel='保留工资') -ax.plot(c_vals, w_bar_vals, label=r'$\bar w$作为$c$的函数') +ax.set(xlabel='失业补偿', ylabel='保留工资') +ax.plot(c_vals, w_bar_vals, lw=2, label=r'$\bar w$作为$c$的函数') ax.legend() - +glue("mccall_resw_c", fig, display=False) plt.show() ``` 这是第二幅图。 ```{code-cell} ipython3 -fig, ax = plt.subplots() +def compute_res_wage_given_beta(β): + model = Model(β=β) + w_bar = compute_reservation_wage(model) + return w_bar -for i, β in enumerate(beta_vals): - mcm.β = β - w_bar = compute_reservation_wage(mcm) - w_bar_vals[i] = w_bar +w_bar_vals = jax.vmap(compute_res_wage_given_beta)(β_vals) +fig, ax = plt.subplots() ax.set(xlabel='贴现因子', ylabel='保留工资') -ax.plot(beta_vals, w_bar_vals, label=r'$\bar w$作为$\beta$的函数') +ax.plot(β_vals, w_bar_vals, lw=2, label=r'$\bar w$作为$\beta$的函数') ax.legend() - +glue("mccall_resw_beta", fig, display=False) plt.show() ``` 这是第三幅图。 ```{code-cell} ipython3 -fig, ax = plt.subplots() +def compute_res_wage_given_alpha(α): + model = Model(α=α) + w_bar = compute_reservation_wage(model) + return w_bar -for i, α in enumerate(alpha_vals): - mcm.α = α - w_bar = compute_reservation_wage(mcm) - w_bar_vals[i] = w_bar +w_bar_vals = jax.vmap(compute_res_wage_given_alpha)(α_vals) +fig, ax = plt.subplots() ax.set(xlabel='离职率', ylabel='保留工资') -ax.plot(alpha_vals, w_bar_vals, label=r'$\bar w$作为$\alpha$的函数') +ax.plot(α_vals, w_bar_vals, lw=2, label=r'$\bar w$作为$\alpha$的函数') ax.legend() - +glue("mccall_resw_alpha", fig, display=False) plt.show() ``` ```{solution-end} -``` +``` \ No newline at end of file From f4e18455a98c9024f2e54d0f50c707c49d1fa50f Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Sat, 18 Jul 2026 21:20:54 +1000 Subject: [PATCH 2/3] Restore CJK font configuration dropped by the resync The resynced file keeps Chinese plot labels but reverted the import cell to the source's exact form, losing the Source Han Serif setup - Chinese in figures would render as missing glyphs. Residual #107 class found in the merge review; applied wave-wide. Co-Authored-By: Claude Fable 5 --- lectures/mccall_model_with_separation.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lectures/mccall_model_with_separation.md b/lectures/mccall_model_with_separation.md index c5bdb383..0260298a 100644 --- a/lectures/mccall_model_with_separation.md +++ b/lectures/mccall_model_with_separation.md @@ -82,6 +82,10 @@ translation: ```{code-cell} ipython3 import matplotlib.pyplot as plt +import matplotlib as mpl +FONTPATH = "fonts/SourceHanSerifSC-SemiBold.otf" +mpl.font_manager.fontManager.addfont(FONTPATH) +plt.rcParams['font.family'] = ['Source Han Serif SC'] import numpy as np import jax import jax.numpy as jnp From 8d7c035d7d16f8e82fe8f5ef0958b171ddf3f07b Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Sun, 19 Jul 2026 10:24:06 +1000 Subject: [PATCH 3/3] Restore trailing newline (engine issue tracked in QuantEcon/action-translation#116) Co-Authored-By: Claude Fable 5 --- lectures/mccall_model_with_separation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lectures/mccall_model_with_separation.md b/lectures/mccall_model_with_separation.md index 0260298a..6ff6398f 100644 --- a/lectures/mccall_model_with_separation.md +++ b/lectures/mccall_model_with_separation.md @@ -727,4 +727,4 @@ plt.show() ``` ```{solution-end} -``` \ No newline at end of file +```