# Reinforcement Learning Note
1. 强化学习问题通常可以转化成马尔科夫决策流程`MDP`，由一组状态$\mathcal{S}、动作\mathcal{A}、概率\mathcal{P}、奖励\mathcal{R}、折扣因子\gamma$组成。
   1. $\mathcal{P}$表示不同转换和奖励发生的概率，通常建模为单个联合概率：任何时间步$t+1$的状态和奖励仅依赖于上一个时间步$t$的状态和采取的动作。
   2. 估算或预测的状态值$V(S)$和在特定状态下采取的动作的值$Q(S,A)$，后者用于判断应该采取什么动作，这两个映射或方法关系紧密，可以帮助我们找到问题的最优策略$\pi^*$，从而最大化接收的总奖励
   3. `MDP`本质上具有概率性，是无法完全确定的预测未来将获得什么样的奖励以及持续多久，因此计算的通常是总期望奖励
   4. 折扣因子$\gamma$：在计算状态和动作值时，用来给未来的奖励分配更低的权重
$$Framework
\begin{cases}
    MDP:(\mathcal{S}、\mathcal{A}、\mathcal{P}、\mathcal{R}、\gamma)&
        \begin{cases}
        单个联合概率:\mathbb{P}(S_{t+1},R_{t+1}\vert S_t,A_t)\\
        状态值函数：V(S)\\
        动作值函数：Q(S,A)
        \end{cases}\\ \\
    Goal:& 找到问题最优策略\pi^*，最大化总期望奖励
\end{cases}
$$
2. 强化学习算法通常分为两个大类
   1. 基于模型的方法，例如策略迭代和需要已知转换和奖励模型的值迭代。本质上通过动态规划并使用该模型以迭代方式计算期望的值函数和最优策略
   2. 不基于模型的方法，例如蒙特卡洛方法和时间差分学习等。不需要明确的模型，通过执行探索性动作对环境抽样，并使用获得的经验直接估算值函数
$$Algorithm
\begin{cases}
基于模型的方法:&
    \begin{cases}
    策略迭代\\
    值迭代
    \end{cases}\\
不基于模型的方法:&
    \begin{cases}
    蒙特卡洛方法\\
    时间差分学习
    \end{cases}\\
\end{cases}
$$

---
## 一、强化学习框架：问题

---
### 1.强化学习框架（`RL框架`）
1. __RL框架__是指智能体（`Agent`）学习如何与环境互动。
假设时间会流逝并离散化时间步（`Time Steps`），在一开始的时间步中，只能体会观察环境，可以把这种结果看成环境呈现给智能体的情形，然后他必须选择合适的响应动作（`Action In Response`）。在下一个时间步对智能体的动作做出响应时，环境向智能体呈现新的情形，同时环境向智能体提供一个奖励，作为回应，智能体必须做出一个动作表示智能体对环境是否做出了正确的响应。这一流程继续下去，在每个时间步，环境都向智能体发送一个观察结果和奖励；作为回应，智能体必须选择一个动作。
通常，我们不需要假设环境向智能体显示做出合理决策所需的一切信息。但是如果这样假设，数学会大大简化，在这章节的课程中，假设智能体能够完全观察环境所处的任何状态。此时，智能体接受的不再是观察结果，而是环境状态。

---
### 2.阶段性任务（`Episodic Task`）与连续性任务（`Continuing Task`）
1. 在强化学习任务中，具有清晰结束点的任务被称为，阶段性任务。将从头到尾的一系列完整互动称为一个阶段，当一个阶段结束，智能体根据获得的奖励总量，判断自己的表现，然后重头再开始，不过会带有前一次的任务记忆，使得每一次的表现越来越好。经历足够多的次数的训练后，智能体将会得出一个获得奖励非常高的策略。
2. 相应的，将没有清晰结束点的任务称为，连续性任务。智能体一直存活，需要学习选择动作的最佳方式同时与环境不断互动，

---
### 3.奖励假设（`Reward Hypothsis`）
- 在机器学习中有个重要的定义假设，即智能体的目标始终可以描述为最大化期望累计奖励（`Maximizing Expected Cumulative Reward`），这就是奖励假设。

---
### 4.动作（`Action`）、目标（`Goal`）和奖励（`Reward`）
1. 动作 - 智能体在每一个时间步根据环境，判断得出的响应；
2. 状态 - 提供给智能体的环境信息，使其能作出合理的动作；
3. 目标 - 最大化累计奖励；
4. 奖励 - 环境对智能体做出的动作的反馈，表示智能体是否做出了正确的动作。

---
### 5.折扣回报（`Discounted Return`）
1. 更改目标，更关注近期奖励，而不是遥远的未来的奖励。在随机时间步$t$，定义一个折扣率（`Discount Rate`）$\gamma \in \lbrack 0,1\rbrack$，则回报/折扣回报：$$G_{t} = R_{t+1}+\gamma R_{t+2}+\gamma^{2}R_{t+3}+\gamma^{3}R_{t+4}+\ldots = \sum_{k=0}^\infty \gamma^{k}R_{t+k+1}$$
   - 如果$\gamma = 0$，智能体只关心最即时的奖励。
   - 如果$\gamma = 1$，回报没有折扣。
   - $\gamma$的值越大，智能体越关心遥远的未来。$\gamma$的值越小，折扣程度越大，在最极端的情况下，智能体只关心最即时的奖励。
2. 使用折扣是为了避免无限未来带来的不良影响。
3. 当智能体在随机时间步选择动作时，就要用到折扣，这样程序就会更关注于获得更早出现的奖励，而不是稍后出现并且可能性较低的奖励

---
### 6.马尔可夫决策流程（`MDP - Markov Decision Process`）
1. 一步动态特性（`One-Step Dynamic Feature`）
   - 在随机时间步$t$，智能体环境互动变成一系列的状态、动作和奖励。$$(S_0, A_0, R_1, S_1, A_1, \ldots, R_{t-1}, S_{t-1}, A_{t-1}, R_t, S_t, A_t)$$当环境在时间步$t+1$对智能体做出响应时，它只考虑上一个时间步$(S_t,A_t)$的状态和动作。尤其是，它不关心再上一个时间步呈现给智能体的状态。（换句话说，环境不考虑任何$\lbrace S_0,\ldots,S_{t-1}\rbrace$）。
并且，它不考虑智能体在上个时间步之前采取的动作。（换句话说，环境不考虑任何$\{ A_0, \ldots, A_{t-1}\}$）。
此外，智能体的表现如何，或收集了多少奖励，对环境选择如何对智能体做出响应没有影响。（换句话说，环境不考虑任何$\{ R_0, \ldots, R_t \}$）。
因此，我们可以通过指定以下设置完全定义环境如何决定状态和奖励$$p(s',r|s,a) \doteq \mathbb{P}(S_{t+1}=s', R_{t+1}=r|S_t = s, A_t=a)$$对于每个可能的$s', r, s, \text{and } a$。这些条件概率用于指定环境的一步动态特性。
2. MDP
   - 一组状态$\mathcal{S}$
      - 在阶段性任务中，我们使用$\mathcal{S}^{+}$表示所有状态集合，包括终止状态。
   - 一组动作$\mathcal{A}$
   - 一组奖励$\mathcal{R}$
   - 一步动态特性$$p(s',r|s,a) \doteq \mathbb{P}(S_{t+1}=s', R_{t+1}=r|S_t = s, A_t=a)$$
   - 折扣率$\gamma \in [0,1]$

---
## 二、强化学习框架：解决方案
可以将解决方案理解为：智能体为了实现目的必须学会的一系列动作

---
### 1.策略（`Policy`）
1. __确定性策略__是从$\pi: \mathcal{S}\to\mathcal{A}$的映射。对于每个状态$s\in\mathcal{S}$，它都生成智能体在状态$s$时将选择的动作$a\in\mathcal{A}$
2. __随机性策略__是从$\pi: \mathcal{S}\times\mathcal{A}\to [0,1]$的映射。对于每个状态$s\in\mathcal{S}$和动作$a\in\mathcal{A}$，它都生成智能体在状态$s$时选择动作$a$的概率。

---
### 2.状态值函数（`State-Value Function`）
1. 策略$\pi$的__状态值函数__表示为$v_{\pi}$。对于每个状态$s \in\mathcal{S}$，它都生成智能体从状态$s$开始，然后在所有时间步根据策略选择动作的预期回报。即$$v_\pi(s) \doteq \text{} \mathbb{E}_\pi[G_t|S_t=s]$$我们将$v_\pi(s)$称之为__在策略__$\pi$__下的状态__$s$__的值__。
2. 记法$\mathbb{E}_\pi[\cdot]$来自推荐教科书，其中$\mathbb{E}_\pi[\cdot]$定义为随机变量的预期值（假设智能体遵守策略$\pi$）。

---
### 3.贝尔曼方程-1（`Bellman Expectation Equation`）
1. 对于一般__MDP__，我们需要使用期望值，因为通常即时奖励和下个状态无法准确地预测。因为奖励和下个状态是根据__MDP__的一步动态特性选择的。在这种情况下，奖励$r$和下个状态$s'$是从（条件性）概率分布$p(s',r|s,a)$中抽取的，__贝尔曼预期方程（对于$v_{\pi}$）__表示了任何状态$s$对于_预期即时奖励和下个状态的预期_值的值：$$v_\pi(s) = \text{} \mathbb{E}_\pi[R_{t+1} + \gamma v_\pi(S_{t+1})|S_t =s]$$

2. __计算期望值__
   1. 如果智能体的策略$\pi$是确定性策略，智能体在状态$s$选择动作$\pi(s)$，贝尔曼预期方程可以重写为两个变量 ($s'$和$r$）的和：$$v_\pi(s) = \text{} \sum_{s'\in\mathcal{S}^+, r\in\mathcal{R}}p(s',r|s,\pi(s))(r+\gamma  v_\pi(s'))$$在这种情况下，我们将奖励和下个状态的折扣值之和$(r+\gamma  v_\pi(s'))$与相应的概率$p(s',r|s,\pi(s))$相乘，并将所有概率相加得出预期值。
   2. 如果智能体的策略$\pi$是随机性策略，智能体在状态$s$选择动作$a$的概率是$\pi(a|s)$，贝尔曼预期方程可以重写为三个变量（$s'$、$r$和$a$）的和：$$v_\pi(s) = \text{} \sum_{s'\in\mathcal{S}^+, r\in\mathcal{R},a\in\mathcal{A}(s)}\pi(a|s)p(s',r|s,a)(r+\gamma  v_\pi(s'))$$在这种情况下，我们将奖励和下个状态的折扣值之和$(r+\gamma  v_\pi(s'))$与相应的概率$\pi(a|s)p(s',r|s,a)$相乘，并将所有概率相加得出预期值。

---
### 4.最优策略（`Optimal Policy`）
1. 策略$\pi '$定义为优于或等同于策略$\pi$（仅在所有$s\in\mathcal{S}$时$v_{\pi'}(s) \geq v_\pi(s)$）。
2. __最优策略$\pi_*$__对于所有策略$\pi$满足$\pi_* \geq \pi$。最优策略肯定存在，但并不一定是唯一的。
3. __最佳策略__正是智能体寻找的策略，是__MDP__的解决方案和实现目标的最佳策略。
4. 所有最优策略都具有相同的状态值函数$v_*$，称为__最优状态值函数__。

---
### 5.动作值函数（`Action-Value Function`）
1. 动作函数是指状态动作对$s, \pi(s)$的值是智能体从状态$s$开始并采取动作$\pi(s)$，然后遵守策略$\pi$所获得的预期回报。即$$q_\pi(s,a) \doteq \mathbb{E}_\pi[G_t|S_t=s, A_t=a]$$我们将$q_\pi(s,a)$称之为__在状态$s$根据策略$\pi$采取动作$a$的值__（或者称之为__状态动作对__$s,a$的值）。
2. 所有最优策略具有相同的动作值函数$q_*$，称之为__最优动作值函数__。
3. 对于确定性策略$\pi,v_\pi(s) = q_\pi(s, \pi(s))$适用于所有$s \in \mathcal{S}$。
4. 智能体确定最优动作值函数$q_*$后，它可以为所有$s\in\mathcal{S}$设置$$\pi_*(s) = \arg\max_{a\in\mathcal{A}(s)} q_*(s,a)$$快速获得最优策略。
注意，必须确保$v_*(s) = \max_{a\in\mathcal{A}(s)} q_*(s,a)$。
5. 如果在某个状态$s\in\mathcal{S}$中，有多个动作$a\in\mathcal{A}(s)$可以最大化最优动作值函数，你可以通过向任何（最大化）状态分配任意大小的概率构建一个最优策略。只需确保根据该策略给不会最大化动作值函数的动作（对于特定状态）分配的概率是 0% 即可。

---
### 6.贝尔曼方程-2
1. $q_\pi$的贝尔曼预期方程是：
$$
\begin{align}
q_\pi(s,a) & = \text{} \mathbb{E}_\pi[R_{t+1} + \gamma q_\pi(S_{t+1},A_{t+1}) |S_t=s,A_t=a]\\
 & = \sum_{s' \in \mathcal{S}^+, r\in\mathcal{R}}p(s',r|s,a)(r + \gamma\sum_{a' \in \mathcal{A}(s)} \pi(a'|s') q_\pi(s',a'))\\
 & = \sum_{s'\in\mathcal{S}^+, r\in\mathcal{R}}p(s',r|s,a)(r+\gamma v_\pi(s'))
\end{align}
$$
其中最后一个形式详细介绍了如何计算任意随机策略$\pi$的预期值。该方程表示任何_状态动作对_（根据任意策略）相对于_后续状态_的值（根据同一策略）的值。
2. 贝尔曼最优性方程：
和贝尔曼预期方程相似，贝尔曼最优性方程可以证明：状态值（以及动作值函数）满足递归关系，可以将状态值（或状态动作对的值）与所有后续状态（或状态动作对）的值联系起来。侧重于最优_策略对应的值满足的关系。
   1. __$v_*$的贝尔曼最优性方程__
   $$
   \begin{align}
   v_*(s)  & = \max_{a \in \mathcal{A}(s)} \mathbb{E}[R_{t+1} + \gamma v_*(S_{t+1}) | S_t=s]\\
    & = \max_{a \in \mathcal{A}(s)}\sum_{s' \in \mathcal{S}^+, r\in\mathcal{R}}p(s',r|s,a)(r + \gamma v_*(s'))
    \end{align}
   $$
   它表示任何_状态_根据最优策略相对于_后续状态_的值（根据最优策略）的值。
   2. __$q_*$的贝尔曼最优性方程__
   $$
   \begin{align}
   q_*(s,a)  & = \mathbb{E}[R_{t+1} + \gamma \max_{a'\in\mathcal{A}(S_{t+1})}q_*(S_{t+1},a') | S_t=s, A_t=a]\\
    & = \sum_{s' \in \mathcal{S}^+, r\in\mathcal{R}}p(s',r|s,a)(r + \gamma \max_{a'\in\mathcal{A}(s')}q_*(s',a'))
    \end{align}
   $$
   它表示任何_状态动作对_根据最优策略相对于_后续状态动作对_（根据最优策略）的值的值。

---
## 三.动态规划（`Dynamic Programming`）

---
### 1.迭代方法（`Iterative Method`）
1. 为了获得策略$\pi$对应的状态值函数$v_{\pi}$，我们只需求解$v_{\pi}$的贝尔曼预期方程对应的方程组。
2. 虽然可以通过分析方式求解方程组，但是通常状态空间非常大，使得直接求解方程组变得非常困难。使用迭代方法简化这一过程。
3. 先猜测每个状态的值（通常可以设为0），然后关注某一个状态，再利用状态之间的关系以及贝尔曼期望方程，更新关注的那个状态的值。如此循环下去...

---
### 2.迭代策略评估算法（`Iterative Policy Evalution Algorithm`）
- __迭代策略评估__是在动态规划设置中用到的算法，用于估算策略$\pi$对应的状态值函数$v_{\pi}$。在此方法中，我们将向值函数估值中不断循环应用贝尔曼更新，直到估值的变化几乎不易觉察。

---
### 3.动作值的估算
1. 在动态规划设置中，可以使用以下方程从状态值函数$v_{\pi}$快速获得动作值函数$q_{\pi}$：$$q_\pi(s,a) = \sum_{s'\in\mathcal{S}, r\in\mathcal{R}}p(s',r|s,a)(r+\gamma v_\pi(s'))$$
2. 在__确定性__环境中，智能体选择某个动作后，下个状态和奖励可以 100% 确定不是随机的。对于确定性环境，所有的$s', r, s, a$为$p(s',r|s,a) \in \{ 0,1 \}$。在这种情况下，当智能体处在状态$s$并采取动作$a$ 时，下个状态$s'$和奖励$r$可以确切地预测，我们必须确保$q_\pi(s,a) = r + \gamma v_\pi(s')$
3. 在__随机性__环境中，智能体选择动作后，下个状态和奖励无法确切地预测，而是从（条件性）概率分布$p(s',r|s,a)$中随机抽取的。在这种情况下，当智能体处在状态$s$并采取动作$a$时，每个潜在下个状态$s'$的概率和奖励$r$由$p(s',r|s,a)$确定。在这种情况下，我们必须确保$$q_\pi(s,a) = \sum_{s'\in\mathcal{S}^+, r\in\mathcal{R}}p(s',r|s,a)(r+\gamma v_\pi(s'))$$，我们计算和$r + \gamma v_\pi(s')$的期望值

---
### 4.策略改进（`Policy Improvement`）
1. 该算法使用策略的值函数，提出一个至少和当前策略一样好的新策略。
2. __策略改进__对策略$\pi$对应的动作值函数$v_{\pi}$进行估算$V$，并返回改进（或对等）的策略$\pi'$，其中$\pi'\geq\pi$。该算法首先构建动作值函数估值$Q$。然后，对应每个状态$s\in\mathcal{S}$，你只需选择最大化$Q(s,a)$的动作$a$。换句话说，$\pi'(s) = \arg\max_{a\in\mathcal{A}(s)}Q(s,a)$，针对所有。
3. 实现过程：
   1. 将值函数转换成动作值函数
   2. 对每个状态，选择最大化动作值函数的动作
   3. 对于某个状态下，有多个动作可以最大化动作值函数的情况
      1. 可以随机选择一个动作
      2. 或者构建一个随机性策略，为任何/所有动作分配非零概率

---
### 5.策略迭代（`Policy Iteration`）
1. 该算法先对最优策略进行猜测，从对等随机策略（对于每个状态，选择每个动作的概率都是一样的）开始比较合适，通过策略评估获得相应的值函数，再使用该值函数和策略改进，获得一个潜在完善的新策略，然后带入该新策略，再次进行策略评估，然后进行策略改进，不断重复下去，直到收敛于最优策略。
2. __策略迭代__是一种可以在动态规划设置中解决 MDP 的算法。它包含一系列的策略评估和改进步骤，肯定会收敛于最优策略（对应任意_有限_ MDP）。

---
### 6.截断策略迭代（`Truncated Policy Evaluation`）
1. 策略迭代花费的时间，与设置的极小数$\theta$（$\theta$越小，时间越长）以及`MDP`的具体情况相关。通过修改终止条件，使得在对状态空间执行固定次数的遍历后，停止评估步骤,此方法称为__截断策略评估__。
2. 将这个修订后的策略评估算法应用到类似于策略评估的算法中，称之为__截断策略迭代__。
3. 截断策略迭代的停止条件与策略迭代的不同。在策略迭代中，当策略在一次策略改进步骤之后没有变化时，我们将终止循环。在截断策略迭代中，仅当值函数估值收敛时，我们才停止循环。

---
### 7.值迭代（`Value Iteration`）
1. __值迭代__是在动态规划设置中用来估算策略$\pi$对应的状态值函数$v_\pi$的算法。对于此方法，每次对状态空间进行遍历时，都同时进行策略评估和策略改进。
2. 如果后续值函数估值之间的差值很小，则满足了停止条件。尤其是，如果对于每个状态，差值都小于$\theta$，则循环终止。并且，如果我们希望最终值函数估值与最优值函数越接近，则需要将值$\theta$设得越小。

---
## 四.蒙特卡洛方法（`Monte_Carlo Method`）
智能体以阶段形式与环境互动，一个阶段结束后，按顺序查看每个状态动作对，如果是首次经历，则计算相应的回报并使用它来更新动作值，经历很多很多个阶段。主要注意的是：只要不在阶段之间更改策略，该算法就可以解决预测问题。只要运行概算足够长的时间，就肯定能获得一个很完美的动作值函数估计结果

---
### 1.MC预测：状态值
1. 解决预测问题的算法会确定策$\pi$对应的值函数$v_\pi$（或$q_\pi$）。
2. 通过与环境互动评估策略$\pi$的方法分为两大类别：
   1. __离线策略方法（`Off-Policy Method`）__使智能体与环境互动时遵守的策略$b$（其中$b\neq\pi$）与要评估（或改进）的策略不同。
   2. __异同策略方法（`On-Policy Method`）__使智能体与环境互动时遵守的策略$\pi$与要评估（或改进）的策略相同。
3. 状态值是该状态之后的预期回报，因此智能体体验的平均回报是个很好的估值。
4. 状态$s\in\mathcal{S}$在某个阶段中的每次出现称为$s$的一次经历。
5. 有两种类型的蒙特卡洛 (`MC`) 预测方法（用于估算$v_\pi$）：
   1. __首次经历 MC（`First-Visit MC Method`）__ 将$v_\pi(s)$估算为仅在$s$首次经历之后的平均回报（即忽略与后续经历相关的回报）。
   2. __所有经历 MC（`Every-Visit MC Method`）__ 将$v_\pi(s)$估算为$s$所有经历之后的平均回报。
   3. 两者的差别在于：
      1. 所有经历 MC 存在偏差，而首次经历 MC 不存在偏差。
      2. 一开始，所有经历 MC 具有更低的均方误差 (MSE)，但是随着经历更多的阶段，首次经历 MC 的均方误差更低。
   4. 当每个状态的经历次数接近无穷大时，首次经历和所有经历方法都__保证会收敛于__真值函数。（换句话说，只要智能体在每个状态获取足够的经验，值函数估值将非常接近真值。）

---
### 2.MC预测：动作值
1. 在强化学习设置下并不知道动态特性，为了获得动作值，不再去查看每个状态的经历，而是查看每个潜在动作对的经历，计算每个动作状态对之后的回报，取平均值
2. 状态动作对$s,a (s\in\mathcal{S},a\in\mathcal{A})$在某个阶段中的每次出现称为$s,a$的一次经历。
3. 有两种类型的蒙特卡洛 (`MC`) 预测方法（用于估算$q_\pi$）：
   1. __首次经历`MC`__将$q_\pi(s,a)$估算为仅在$s,a$首次经历之后的平均回报（即忽略与后续经历相关的回报）。
   2. __所有经历`MC`__将$q_\pi(s,a)$估算为$s,a$所有经历之后的平均回报。

---
### 3.广义策略评估（`Generalized Policy Iteration`）
指：不对策略评估周期次数进行限制，并且不对收敛接近程度进行限制的算法。在所有强化学习下都可以使用。

---
### 4.MC控制：增量均值
1. 假设一个动作状态对经历了一定的次数，将回报表示为$\lbrace x_1, x_2, \ldots, x_n\rbrace$，然后通过对这些值取均值，计算值函数逼近结果，表示为：$$\mu_n = \frac{\sum_{j=1}^{n}x_j}{n}$$
2. 在每次经历之后都迭代更新估值，通过推到可以得出第$k$的均值与第$k-1$的均值的关系：
$$
\begin{align}
\mu_k & = \frac{1}{k}\sum_{j=1}^{k}x_j\\
 & = \frac{1}{k}(x_k + \sum_{j=1}^{k-1}x_j)\\
 & = \frac{1}{k}(x_k + (k-1)\mu_{(k-1)})\\
 & = \mu_{(k-1)} + \frac{1}{k}(x_k - \mu_{(k-1)})
\end{align}
$$

---
### 5.MC控制：策略评估
1. 通过对增量均值进行改进，使得能获得多个状态动作对的值函数逼近结果。
2. 智能体先从环境中获得一个阶段样例，然后对于每个时间步，查看相应的状态动作对，如果是首次访问，则计算相应的回报，然后根据不断取平均值的算法，更新动作值的相应估值；初始化每个对的经历次数，将上述计算带入蒙特卡洛控制计算中。

---
### 6.MC控制：策略改进
1. 如果对于每个状态$s\in\mathcal{S}$，它保证会选择满足$a = \arg\max_{a\in\mathcal{A}(s)}Q(s,a)$的动作$a\in\mathcal{A}(s)$，则策略相对于动作值函数估值$Q$来说是贪婪策略。（通常将所选动作称之为贪婪动作。）
2. 如果对于每个状态$s\in\mathcal{S}$，策略相对于动作值函数估值$Q$是$\epsilon$贪婪策略。
   1. 概率为$1-\epsilon$时，智能体选择贪婪动作，以及
   2. 概率为$\epsilon$时，智能体随机（均匀地）选择一个动作。

---
### 7.Epsilon 贪婪策略
1. 为了构建一个相对于当前动作值函数估值$Q$为$\epsilon$贪婪策略的策略$\pi$，我们只需设置针对每个$s\in\mathcal{S}$和$a\in\mathcal{A}$。注意，$\epsilon$必须始终是 0-1（含）之间的值（即$\epsilon \in [0,1]$）。
2. 可以将遵守$\epsilon$贪婪策略的智能体看做始终可以操控硬币方向（可能不公平），正面朝上的概率是$\epsilon$。观察状态后，智能体就会抛掷该硬币。
   1. 如果硬币背面朝上（因此概率为$1-\epsilon$），智能体选择贪婪动作。
   2. 如果硬币正面朝上（因此概率为$\epsilon$），智能体从一组潜在（非贪婪和贪婪）动作中均匀地随机选择一个动作。

---
### 8.探索-利用
1. 探索-利用困境
   - 注意，智能体一开始不知道环境的一步动态特性。为了最大化回报，智能体必须通过互动了解环境。
在每个时间步，当智能体选择一个动作时，它都根据以前与环境的互动经验做出决策。并且，为了尽量减少求解`OpenAI Gym`中的环境所需的阶段次数，我们的第一个想法是设计一种策略，其中智能体始终选择它认为（根据过去的经验）将最大化回报的动作。因此，智能体可以遵守对动作值函数估算来说很贪婪的策略。
为了了解为何是这种情况，注意在早期阶段，智能体所了解的信息很有限（可能有缺陷）。因此很有可能智能体_估计非贪婪的动作实际上比估计_贪婪的动作更好。
因此，成功的强化学习智能体不能在每个时间步都采取贪婪的动作（即它不能始终利用自己的经验）；为了发现最优策略，它必须不断改进所有状态动作对的估算回报（换句话说，它必须继续通过经历每个状态动作对探索各种可能性）。但是，智能体应该始终保持一定的贪婪性，从而_尽快_最大化回报。这就引出了$\epsilon$贪婪策略。
我们将需要平衡这两个竞争性条件的情况称为探索-利用困境。解决该困境的一个潜在方法是在构建$\epsilon$贪婪策略时逐步修改$\epsilon$的值。
2. 设置$\epsilon$的值
   - 合理的做法是智能体一开始与环境互动时，倾向于探索环境，而不是利用已有的经验。毕竟当智能体对环境的动态特性相对知之甚少时，应该怀疑自己的有限知识并探索环境，或者尝试各种最大化回报的策略。因此，最佳起始策略是等概率随机策略，因为它在每个状态探索所有潜在动作的概率是相同的。你在上个练习中发现，$\epsilon = 1$会生成一个等同于等概率随机策略的$\epsilon$贪婪策略。
在后续时间步，合理的做法是倾向于利用已有的经验，而不是探索环境，策略在动作值函数估算方面越来越贪婪。毕竟智能体与环境的互动次数越多，它对估算动作值函数的信任就越高。你在上个练习中发现，$\epsilon = 0$会生成贪婪策略（或者非常倾向于利用已有的经验而不是探索环境的策略）。
幸运的是，此策略（一开始倾向于探索环境而不是利用已有的经验，然后逐渐倾向于利用已有的经验而不是探索环境）可以证明是最优策略。
3. 有限状态下的无限探索贪婪算法 (`GLIE`)
为了保证 MC 控制会收敛于最优策略$\pi_*$，我们需要确保满足两个条件。我们将这些条件称之为__有限状态下的无限探索贪婪算法 (GLIE)__。尤其是，如果：
   - 每个状态动作对$s, a$（针对所有$s\in\mathcal{S}$和$a\in\mathcal{A}$）被经历无限次;
   - 策略收敛相对于动作值函数估算 QQ 来说贪婪的策略。
然后，MC 控制保证会收敛于最优策略（在有限状态下运行无穷个阶段）。这些条件确保：
   - 智能体继续在所有时间步进行探索
   - 智能体逐渐倾向于利用已有的经验（而不是探索环境）。
满足这些条件的一种方式是在指定$\epsilon$贪婪策略时，修改$\epsilon$的值。尤其是，使$\epsilon_i$对应于第$i$个时间步。然后在以下情况下，这两个条件都会满足：
   - 对于所有时间步$i，\epsilon_i > 0$
   - 当时间步$i$接近无穷大时，$\epsilon_i$减小到0（即$\lim_{i\to\infty} \epsilon_i = 0$）。

---
### 9.MC控制：常量$\alpha$
1. 综合前述算法，生成一个阶段，然后对于经历的每个状态动作对$(S_t, A_t)$，都计算接下来的相应回报$G_t$（`Corresponding Return`）,然后再用该回报获取最新的估算，更新步骤表示如下：$$Q(S_t, A_t) \longleftarrow Q(S_t, A_t) + \frac{1}{N(S_t,A_t)}(G_t - Q(S_t, A_t))$$
2. 对于上述算法，可以这样理解：
   1. 计算最近取样的回报$G_t$和对应的状态动作对$(S_t, A_t)$之间的差别，记作：$\delta_t = G_t - Q(S_t, A_t)$
   2. $\delta_t$是期望回报$Q(S_t, A_t)$与实际回报$G_t$之间的差值
      1. $\delta_t > 0$，实际回报比值函数期望回报要大，说明值函数偏小，因此需要增大估值$Q(S_t, A_t)\uparrow$
      2. $\delta_t < 0$，实际回报比值函数期望回报要小，说明值函数偏大，因此需要降低预期的动作值函数$Q(S_t, A_t)\downarrow$
      3. 增大/降低的调整幅度与已经经历状态动作对的次数$N(S_t,A_t)$成反比
      4. 这种方式，前面的回报对调整影响大，后面的影响小。因此我们需要更改该算法。
3. 引入步长参数$\alpha$，并更新算法：$$Q(S_t, A_t) \longleftarrow Q(S_t, A_t) + \alpha(G_t - Q(S_t, A_t))$$
   1. 这样可以确保稍后获得的回报比之前获得的回报更受重视，这样，智能体将最信任最新的汇报，并逐渐忘记先前获得的回报。这点非常重要，因为该策略不断改变，每一步都越来越优化，因此实际上后面的时间步对估算动作值来说很重要
   2. 步长参数$\alpha$必须满足$0 < \alpha \leq 1$。$\alpha$值越大，学习速度越快，但是如果$\alpha$的值过大，可能会导致__MC__控制无法收敛于$\pi_*$。

---
## 五.时间差分方法（`Temporal Difference Learning`）
学习如何认识用时间差分法（例如Sarsa、Q学习和预期）解决阶段性任务和连续性任务

---
### 1.TD 预测：TD(0)
1. 将[四.9.3的数学式变形：](#jump)$$V(S_t) \longleftarrow V(S_t) + \alpha(G_t - V(S_t))$$根据：
$$
\begin{align}
v_\pi(s) & = \text{} \mathbb{E}_\pi[G_t|S_t=s]\\
 & = \text{} \mathbb{E}_\pi[R_{t+1} + \gamma V_(S_{t+1})|S_t =s]
\end{align}
$$代入上式，变形得：$$V(S_t) \longleftarrow V(S_t) + \alpha([R_{t+1} + \gamma V_(S_{t+1})] - V(S_t))$$任何状态的值定义为智能体遵守策略后在该状态之后很可能会出现的预期回报。对取样回报取平均值生成了很好的估值，
2. 对于上式可以这样理解：
   1. 先查看时间$t$和$t+1$处状态的值，带入奖励$R_{t+1}$，得出右式的值
   2. 将这个值作为时间$t$时的状态的新估算值赋给$V(S_t)$
   3. 这样，我们不用等待阶段结束，就可以更新值
   4. 这是一个处理连续性问题时的预测问题算法

3. 右式中，当前状态的最佳估算值$V(S_t)$，智能体采取动作并获得奖励$R_{t+1}$和下个状态$V(S_{t+1})$，使用这些后续状态的信息表达同一状态$S_t$的值替代估值，并将新的估值$R_{t+1} + \gamma V_(S_{t+1})$记为__TD目标（TD Target）__
   - 这个更新方程的作用是：找到两个估值$V(S_t)$（`Previous Estimate`）$\&$ $R_{t+1} + \gamma V_(S_{t+1})$（`TD target`）之间的中间值
4. 将数学式合并同类项后得：$$V(S_t) \longleftarrow (1 - \alpha)V(S_t) + \alpha(R_{t+1} + \gamma V_(S_{t+1}))$$可见$\alpha$是介于0-1之间的数
   1. 当$\alpha = 1$，新估值就是`TD target`
   2. 当$\alpha = 0$，新估值完全忽略`TD target`，只保留旧估值
   3. 上述两种都不利于智能体的学习，通常把$\alpha$设置为一个接近0的小值更有帮助。$\alpha$越小，对`TD target`的信任就越低，并更加依赖于状态值的现有估值。
5. 将这个更新步骤放入完整的算法中，称之为一步时间差分（`One-Step Temporal Difference/TD`）
   1. 一步是指在每个时间步之后都更新值函数，也称之为__TD(0)__
   2. 用于确认对应于某个策略$\pi$的状态值函数
      1. 对于连续性问题
         1. 先将每个状态初始化为0
         2. 在每个时间步，智能体都与环境互动，选择由策略决定的动作
         3. 在环境中获得奖励和下个状态后，立即更新上个状态的值函数
      2. 对于阶段性任务
         1. 对于每个阶段，只需要检查在每个时间步，最近的状态是否为最终状态
         2. 循环运行更新步骤更新上一个状态直到$S_t$是最终状态
         3. 然后对下一个阶段运行步骤1，2
   3. 只要互动足够长或者生成的阶段足够多，该算法就应该会返回一个很好的值函数逼近结果
   4. 相比于常量$\alpha$MC预测，TD(0)有以下优点：
      1. 虽然 MC 预测必须等到阶段结束时才能更新值函数估值，但是， TD 预测方法在每个时间步之后都会更新值函数。同样，TD 预测方法适合连续性和阶段性任务，而 MC 预测只能应用于阶段性任务。
      2. 在实践中，TD 预测的收敛速度比 MC 预测的快。（但是，没有人能够证明这一点，依然是一个需要验证的问题。）

---
### 2.TD预测：动作值
- 调整[1](#jump1)中的流程，使之返回动作值的估值：$$Q(S_t,A_t) \longleftarrow Q(S_t,A_t) + \alpha([R_{t+1} + \gamma Q(S_{t+1},A_{t+1})] - Q(S_t,A_t))$$唯一的区别就在于：智能体在每次选择动作后更新值，如果智能体与环境互动足够长的时间，将能够准确的估算动作值函数

---
### 3.TD控制：Sarsa
1. 该算法用于确定最优策略
2. Sarsa算法具体步骤：
   1. 在每个时间步使用一个针对当前动作估值的Epsilon贪婪策略选择一个动作，在初始时间步，先将$\epsilon$设为1
   2. 根据对等概率随机策略（`Equal Probable Random Policy `）选择$A_0和A_1$
   3. 对选顶动作之后的所有未来时间步，都更新动作值函数并构建相应的Epsilon贪婪策略
   4. 只要为$\epsilon$指定合适的值，该算法就肯定会收敛于最优策略
3. 每个动作只更新都使用状态动作奖励、后续状态、后续动作、互动元组
4. 只要步长参数$\alpha$足够小，并且满足有限状态下的无限探索贪婪算法 (GLIE) 条件，Sarsa(0) __保证会收敛于__最优动作值函数。虽然有满足 GLIE 条件的很多方式，但是有一种方式在构建$\epsilon$贪婪策略时会逐渐降低$\epsilon$的值。尤其是，使$\epsilon_i$ 对应于第 ii 个时间步。然后，如果我们按照以下条件设置$\epsilon_i$：
   1. 对于所有时间步$i，\epsilon_i > 0$，以及
   2. 当时间步$i$接近无穷大时，$\epsilon_i$降低到 0（即$\lim_{i\to\infty} \epsilon_i = 0$），

   然后该算法会保证产生一个很好的$q_*$ 估值，只要我们运行该算法足够长的时间。然后，可以通过对所有$s\in\mathcal{S}$设置$\pi_*(s) = \arg\max_{a\in\mathcal{A}(s)} q_*(s, a)$，获得相应的最优策略$\pi_*$。

---
### 4.TD控制：Sarsamax（aka Q-Learning）
1. 与Sarsa相比，采用相同的初始动作值$Q(S_0, A_0)$和策略$\pi$，智能体接收初始状态$S_0$，依然根据初始策略选择第一个动作$A_0$，接受奖励$R_0$和下一个状态$S_1$，然后在选择下一个动作之前使用贪婪动作更新动作值$$Q(S_0,A_0) \longleftarrow Q(S_0,A_0) + \alpha([R_{1} + \gamma \max_{a \in A}Q(S_{1},a)] - Q(S_0,A_0))$$和策略$$\pi \longleftarrow \epsilon-greedy(Q)$$再使用刚更新的动作值对应的Epsilon贪婪策略选择$A_1$，收到奖励和下个状态后继续这一流程，像之前使用贪婪动作更新$(S_1,A_1)$对应的动作一样，使用相应的Epsilon贪婪策略选择$A_2$，以此类推$$Q(S_t,A_t) \longleftarrow Q(S_t,A_t) + \alpha([R_{t+1} + \gamma \max_{a \in A}Q(S_{t+1},a)] - Q(S_t,A_t))$$
2. 与[Sarsa](#jump_to_sarsa)对比：
   1. 在Sarsa中更新步骤使动作值更接近于智能体当前遵守Epsilon贪婪策略获得的动作值
   2. Sarsamax直接在每个时间步估算最优值函数

---
### 5.TD控制：Sarsa期望（`Expected Sarsa`）
- 与[Sarsamax](#jump_to_sarsamax)带入最大化下个状态对应的动作估值选择在此处采取的值$\gamma \max_{a \in A}Q(S_{t+1},a)$不同，期望Sarsa使用下个状态动作对的期望值$\gamma \sum_{a \in A}\pi(a\bracevert S_{t+1})Q(S_{t+1},a) - Q(S_t,A_t)$，期望值会考虑智能体从下个状态选则每个可能的动作的概率

---
### 6.分析性能
1. 在以下情况下，我们讨论过的所有`TD`控制算法（Sarsa、Sarsamax、预期 Sarsa）都会收敛于最优动作值函数$q_*$（并生成最优策略$\pi_*$）：
   1. $\epsilon$的值根据__GLIE__条件逐渐降低
   2.  步长参数$\alpha$足够小
2. 这些算法之间的区别总结如下：
   1. Sarsa 和预期 Sarsa 都是[__异同策略__ ](#jump_to_onpm)TD 控制算法。在这种情况下，我们会根据要评估和改进的相同（$\epsilon$贪婪策略）策略选择动作。
   2. Sarsamax 是[__离线策略__](#jump_to_offpm)方法，我们会评估和改进（$\epsilon$贪婪）策略，并根据另一个策略选择动作。
   3. 既定策略 TD 控制方法（例如预期 Sarsa 和 Sarsa）的在线效果比新策略 TD 控制方法（例如 Sarsamax）的要好。
   4. 预期 Sarsa 通常效果比 Sarsa 的要好。

---
## 六.深度强化学习（`Deep Reinforcement Learning`）
1. 深度强化学习指：使用深度学习方法来解决强化学习问题。
2. 强化学习通常包括有限的`MDP`，即状态和动作数量是有限的。但是有太多问题具有非常大的状态和动作空间，甚至由连续的实数组成，传统算法使用表格或字典或其他有限结构来记录状态和动作值无法解决此类问题。因此通过深度强化学习
   1. 泛化已有算法以便适合大型连续空间
   2. 基于值的技巧，如深度Q学习`Deep Q Learning`
   3. 直接尝试优化策略的方法，如策略梯度`Policy Gradients`
   4. 行动者-评论者方法`Actor-Critic Methods`

---
### 1.离散空间（`Discrete Space`）和连续空间（`Continuous Space`）
1. 离散空间
   1. 假设任何时间的环境状态来自于一组潜在状态，称之为离散状态空间。动作也类似，如果有一组有限的动作，则表示环境有一个离散动作空间
2. 连续空间
   1. 连续空间中的量值是一定范围的值，这个范围通常是实数
   2. 连续空间之所以重要，是因为真实世界的问题往往无法用网格话来建立模型，也就不可能是离散空间。
   3. 为了能处理连续空间问题，使用离散化（`Discretization`）和函数逼近（`Function Approximation`）策略

---
### 2.离散化（`Discretization`）
1. 离散化就是将__连续空间__使用区域划分的方法划分成__离散空间__。主要的方法有`Tile Coding`、`Coarse Coding`。并且__状态空间可以被不均匀地细分__。
2. 虽然底层环境是连续的，但是在__离散化__状态空间表示法中，仅需要某些位置标识信息。虽然在确定坐标时使用四舍五入的处理会导致位置不准确，但对某些环境，离散化状态空间效果很好，可以几乎不加修改就可以使用现有的算法。
3. 动作也可以离散化，例如：角可以拆分为完整度数。
4. 对于网格表示法，当物体超出单元格可以表示的精度，可以通过再拆分分单元格的方式提升精度。
5. `Tile Coding`和`Coarse Coding`都是在随机环境中均能使用的离散化方法（_下面本该有一个位和位向量的概念，但是我无法理解这两个概念，所以先回避这俩_）
   1. `Tile Coding`
      1. 底层状态空间是连续的二维空间，在该空间上方叠加多个网格或拼贴每一层稍微不对齐。状态空间里的任何位置$\mathcal{s}$，都可以粗略的通过它所激活的拼贴识别
      2. 由于Tile Coding需要提前手动选择拼贴大小、偏移量、拼贴数量等等，因此就有了更灵活的方法`Adaptive Tile Coding`。
         1. 先从非常大的拼贴开始，然后在合适的时候将每个拼贴一分为二。
         2. 通过试探法，来决定何时拆分单元格，即当前不再学到很多规律时，也就是值函数不再改变时，就需要拆分状态空间；达到拼贴数量上限或迭代上限时，停止拆分。
         3. 跟踪子拼贴以及它们的预测权重，选择子拼贴之间权重差别最大的拼贴，这个拼贴就是对值函数影响最大的，也就是需要被拆分的拼贴。
         4. `Adaptive Tile Coding`的优势在于：不需要手动提前指定离散化方式，最终得到一个基于空间复杂程度的状态空间的划分
   2. `Coarse Coding`
      1. 与`Tile Coding`很相似，但是使用一组更稀疏的特征来表示状态空间
      2. 在二位连续状态空间上放下一堆圆圈，选择任何一个状态$\mathcal{s}$即在该空间中的位置，并标记它所属的所有圆圈，这些圆圈用`1`表示，剩余圆圈用`0`表示，这就是状态的稀疏编码表示法。这个方法可以拓展到更高维度的空间，圆圈变成球面和超球面。
      3. `Coarse Coding`的一些特性：
         1. 圆圈越小，空间的泛化程度越低，算法需要持续时间越长，但是可以获得更有效的分辨率
         2. 圆圈越大，泛化程度越高，通常会形成更平缓的值函数
         3. 可以使用更少的大圆圈来涵盖空间，从而缩小表示法，但是会损失一定的分辨率
         4. 不仅可以改变圆圈的大小，还可以改变他们的宽度、高度，甚至是任何形状，以便在一个维度里获得更高的分辨率
      4. 根据状态$\mathcal{s}$到每个圆圈中心的距离衡量该特征的活跃程度，可以用高斯或钟形曲线，是这种衡量方式或响应更加平缓，曲线在圆圈上居中，称之为径向基函数$$\phi_i(s)\doteq exp\left( -\frac{\Arrowvert s-c_i\Arrowvert^2}{2\sigma_i^2}\right)$$形成的状态值不再离散化，因此又是另一个连续状态向量，但是可以显著降低特征数量。
6. 离散化的缺陷在于：
   1. 当底层空间很复杂时，所需的离散状态数量可能会很大
   2. 对于状态空间中邻近的位置，他们的值应该相似或者平滑地变化，但离散化并非始终会利用这一特性，无法跨空间的有效泛化

---
### 3.函数逼近（`Function Approximation`）
1. 对于那么很复杂的问题，无法得知真正的底层函数，定义此类函数逼近的一般方法是引入一个表示函数形状的参数向量$W$，通过调整这个参数向量，找到理想的逼近结果$$\hat{v}(s,W) \approx v_\pi (s)\\ \hat{q}(s,a,W) \approx q_\pi (s,a)$$
2. 逼近函数可以将状态映射到其值或将状态动作对映射到相应的q值，另一种形式是从一个状态映射到一堆不同的q值同时映射到每个动作
3. 以第一种情况（将状态映射到其值）为例，定义一个转换算法，将向量形式的给定状态$s$转换成特征向量$X(s)$，这样就避免直接运算原始状态。然后将特征向量$X(s)$与参数向量$W$点积，即可得出逼近的值函数
$$
\begin{align}
\hat{v}(s,W) & = \left( x_1(s) \ldots x_n(s)\right) · \left( \begin{matrix}
w_1\\
\vdots\\
w_n
\end{matrix}\right)\\
 & = x_1(s)·w_1+\ldots+x_n(s)·w_n\\
 & = \sum_{j=1}^{n}x_j(s)·w_j
\end{align}
$$
4. 线性函数逼近（`Linear Function Approximation`）
   1. 线性函数是所有特征乘以相应的权重并求和的结果
   2. 使用梯度下降的方法调整$W$找到最优参数向量
      1. `Value Function`$\hat{v}(s,W)=X(s)^T·W$，其相对于$W$的导数：$\nabla_W\hat{v}(s,W)=X(s)$
      2. 以平方差的形式计算真值函数$V_\pi$与逼近值函数$\hat{v}$的差异：$$Minimize Error: J(W)=\mathbb{E}_\pi \left[\left(v_\pi(s)-X(s)^T·W\right)^2\right]$$
      3. 使用梯度下降计算该函数相对于$W$的梯度或导数。使用差分链式法则，结果是-2乘以差值乘以$\hat{v}$的导数（也就是特征向量）$$\nabla_WJ(W)=-2\left(v_\pi(s)-X(s)^T·W\right)X(s)$$去掉了期望符号，侧重于单个状态$s$表示的误差梯度
      4. 假设状态是随机选择的，如果能抽取足够多的样本，则可以非常接近预期值，将这个带入梯度下降更新规则的一般公式：$$\begin{align}\Delta W & = -\alpha \frac{1}{2}\nabla_WJ(W)\\& = \alpha\left(v_\pi(s)-X(s)^T·W\right)X(s)\end{align}$$$\alpha$是步长或学习速率参数，$\frac{1}{2}$是为了消去到书中的-2
      5. 将这个基本公式迭代到降低每个样本状态的误差，知道逼近函数和真函数几乎相等
5. 梯度下降优化参数向量的原理：在每次迭代时，朝着误差的相反方向小步地更改权重。其中，特征向量$X(s)$指出了哪个方向不合适。
6. 为了解决不基于模型的控制问题，即在未知环境中采取动作，需要逼近动作值函数。定义一个利用状态和动作的特征转换$$X(s,a)=\left(\begin{matrix}x_1(s,a)\\ x_2(s,a)\\ \vdots \\ x_n(s,a)\end{matrix}\right)$$然后使用状态值函数中用到的梯度下降方法
7. 对于逼近函数同时计算所有动作值的情况：可以看作生成动作向量，可以继续使用相同的特征向量$X(s,a)=\left(\begin{matrix}x_1(s,a)\\ x_2(s,a)\\ \vdots \\ x_n(s,a)\end{matrix}\right)$，传入状态和动作
   - 生成不同的动作值的方法：尝试找到n个不同的动作值函数，每个动作维度对应一个函数，同时计算这些函数，拓展权重向量并转化为矩阵。矩阵的每列模拟一个单独的线性函数，根据状态和动作计算的共同特征，使这些函数相互保持关联性
$$
\begin{align}
\hat{q}(s,a,W) & = \left(x_1(s,a) \ldots x_n(s,a)\right)·\left(
\begin{matrix}
w_{11} & \cdots & w_{1m}\\
\vdots & \ddots & \vdots\\
w_{n1} & \cdots & w_{nm}
\end{matrix}\right)\\
 & = \left(\hat{q}(s,a_1,W) \ldots \hat{q}(s,a_m,W)\right)
\end{align}
$$如果问题领域具有连续状态空间，但具有离散动作空间，并且这是很常见的类型，可以选择值最大的动作。如果动作空间也是连续的，那么这种形式能够同时输出多个值
8. 线性函数逼近的主要限制条件是：只能表示输入和输出之间的线性关系

---
### 4.内核函数
1. 对于特征向量$X(s)=\left(\begin{matrix}x_1(s)\\ x_2(s)\\ \vdots \\ x_n(s)\end{matrix}\right)$中的每一个元素都可以用单独的函数生成，可以是非线性函数，这些函数就是__内核函数__或者__基函数__。他们将输入状态转换成不同的空间，并且因为值函数是这些特征的线性组合，依然可以使用线性函数逼近，使得值函数能够表示输入状态和输出状态之间的非线性关系。径向基函数是常见的用于此目的的内核。
2. 径向基函数$\phi_i(s)\doteq exp\left( -\frac{\Arrowvert s-c_i\Arrowvert^2}{2\sigma_i^2}\right)$
   1. 本质上，将当前状态$s$看作连续空间内的位置，状态空间表示为矩形平面。每个基函数都显示为一个水泡，状态越接近水泡的中心，函数返回值越高。距离越远，返回结果沿半径逐渐减小。
   2. 均值表示水泡中心，标准偏差决定了返回结果降低的平缓程度
   3. 对于任何给定状态，都可以将状态表示法简化为这些径向基函数的返回结果向量，然后使用相同的函数逼近方法

---
### 5.非线性函数逼近（`Non-Linear Function Approximation`）
1. 如果底层的值函数相对这些特征值的组合来说非线性，为了捕获这种复杂的关系，把通过点积获得线性返回结果传入某个非线性函数$f$，也就是最初的激活函数，其大大提高了逼近器的表示能力。$$\hat{v}(s,W)=f\left(X(s)^T·W\right)$$
2. 可以使用梯度下降迭代的更新任何此类函数的参数$\Delta W=\alpha\left(v_\pi(s)-\hat{v}(s,W)\right)\nabla_W\hat{v}(s,W)\tag{梯度下降更新规则}$学习速率$\alpha$乘以值差异乘以函数相对于权重的导数


(MPRIMALDUALBARRERALOG)=

# 4.5 Método primal-dual de barrera logarítmica (BL)

```{admonition} Notas para contenedor de docker:

Comando de docker para ejecución de la nota de forma local:

nota: cambiar `<ruta a mi directorio>` por la ruta de directorio que se desea mapear a `/datos` dentro del contenedor de docker.

`docker run --rm -v <ruta a mi directorio>:/datos --name jupyterlab_optimizacion_2 -p 8888:8888 -p 8787:8787 -d palmoreck/jupyterlab_optimizacion_2:3.0.0`

password para jupyterlab: `qwerty`

Detener el contenedor de docker:

`docker stop jupyterlab_optimizacion_2`

Documentación de la imagen de docker `palmoreck/jupyterlab_optimizacion_2:3.0.0` en [liga](https://github.com/palmoreck/dockerfiles/tree/master/jupyterlab/optimizacion_2).

```

---

Nota generada a partir de [liga1](https://drive.google.com/file/d/16-_PvWNaO0Zc9x04-SRsxCRdn5fxebf2/view), [liga2](https://drive.google.com/file/d/1oulU1QAKyLyYrkpJBLSPlbWnKFCWpllX/view), [liga3](https://drive.google.com/file/d/1RMwUXEN_SOHKue-J9Cx3Ldvj9bejLjiM/view)

```{admonition} Al final de esta nota la comunidad lectora:
:class: tip

* Conocerá el método primal dual de barrera logarítmica para resolver programas lineales.

* Aprenderá que tal método:

    * es un caso particular de métodos de penalización para resolver problemas de optimización con restricciones.

    * tiene la capacidad de resolver problemas convexos.

    * trabaja con bloques de matrices lo que ayuda a aprovechar operaciones vectoriales o matriciales.

    * puede implementarse con cómputo en paralelo.

```

El método primal-dual de barrera logarítmica (BL) es un método iterativo que realiza un manejo de las variables primales y duales del problema de optimización a resolver. Se le clasifica como un método por puntos interiores.

```{sidebar} Un poco de historia ...

El [método símplex](https://en.wikipedia.org/wiki/Simplex_algorithm) desarrollado por Dantzig en los $40$'s hizo posible que se formularan y analizaran modelos grandes en una forma sistemática y eficiente. Hoy en día continúa siendo uno de los métodos más utilizados para resolver programas lineales. No obstante puede ser ineficiente en problemas lineales "patológicos" (ver [Klee-Minty cube](https://en.wikipedia.org/wiki/Klee%E2%80%93Minty_cube)) pues el tiempo para resolver tales problemas es exponencial respecto al tamaño del problema (medido como el número de variables y la cantidad de almacenamiento para los datos del problema). Para la mayoría de problemas prácticos el método símplex es mucho más eficiente que estos casos "patológicos" pero esto motivó la investigación y desarrollo de nuevos algoritmos con mejor desempeño. En 1984 Karmarkar publicó el [algoritmo](https://en.wikipedia.org/wiki/Karmarkar%27s_algorithm) que lleva su mismo nombre que tiene una complejidad polinomial y en la práctica resultó ser eficiente. Pertenece a la clase de métodos con el nombre de [puntos interiores](https://en.wikipedia.org/wiki/Interior-point_method). Hay diferentes tipos de métodos por puntos interiores siendo los de la clase primal-dual ampliamente usados en la práctica.

```

## Métodos por puntos interiores (PI)

Los métodos por puntos interiores (PI) son esquemas iterativos que en un inicio se utilizaron para resolver PL's, sin embargo, se ha extendido su uso al caso no lineal. Por ejemplo, distintos tipos de métodos por PI han sido usados para resolver problemas de optimización convexos, ver {ref}`problemas de optimización convexa en su forma estándar o canónica <PROBOPTCONVEST>`.

```{margin}

Recuérdese que nombramos problemas de optimización con restricciones *large scale* a aquellos problemas de optimización que tienen un número de variables y restricciones mayor o igual a $10^5$ (ambas).

```

En cada iteración de los métodos PI las restricciones de desigualdad del problema de optimización se satisfacen de forma estricta. Cada iteración es costosa de calcular y realiza avance significativo a la solución en contraste con el método símplex que requiere un gran número de iteraciones no costosas. Una característica que tienen los métodos PI es que los problemas *large scale* no requieren muchas más iteraciones que los problemas *small scale* a diferencia del método símplex. Sin embargo para problemas *small scale* en general realizan más iteraciones que el método símplex.

En cada iteración el método símplex se mueve de la solución FEV actual a una solución FEV adyacente por una arista de la frontera de la región factible, ver {ref}`método símplex <METODOSIMPLEX>`. Los problemas del tipo *large scale* tienen una cantidad enorme de soluciones FEV. Para ver esto piénsese en un PL al que se le van añadiendo restricciones funcionales. Entonces se añadirán aristas y por tanto soluciones FEV.  Los métodos PI evitan tal comportamiento pues avanzan por el interior de la región factible hacia los puntos óptimos y tiene muy poco efecto el ir añadiedo restricciones funcionales al PL para el desempeño de los métodos PI.

```{admonition} Observación
:class: tip

Los métodos PI han mostrado "buena" eficiencia (en términos del número de iteraciones realizadas) en resolver problemas de optimización *large scale*. Además son métodos que pueden implementarse para procesamiento con cómputo en paralelo.

```

Los métodos PI conforme avanzan en las interaciones aproximan a los puntos óptimos en el límite. Por ejemplo, para el {ref}`ejemplo prototipo <EJPROTOTIPO>` de un programa lineal (PL) a continuación se presenta una trayectoria obtenida por un método PI que se aproxima a la solución óptima $(2, 6)$:

$$\displaystyle \max_{x \in \mathbb{R}^2} 3x_1 + 5x_2$$

$$\text{sujeto a: }$$

$$x_1 \leq 4$$

$$2x_2 \leq 12$$

$$3x_1 + 2x_2 \leq 18$$

$$x_1 \geq 0, x_2 \geq 0$$

```python
import numpy as np
import matplotlib.pyplot as plt
```

```python
np.set_printoptions(precision=3, suppress=True)
```

```python
#x_1 ≤ 4

point1_x_1 = (4,0)

point2_x_1 = (4, 10)

point1_point2_x_1 = np.row_stack((point1_x_1, point2_x_1))

#x_1 ≥ 0
point3_x_1 = (0,0)

point4_x_1 = (0, 10)

point3_point4_x_1 = np.row_stack((point3_x_1, point4_x_1))

#2x_2 ≤ 12 or x_2 ≤ 6

point1_x_2 = (0, 6)

point2_x_2 = (8, 6)

point1_point2_x_2 = np.row_stack((point1_x_2, point2_x_2))

#x_2 ≥ 0

point3_x_2 = (0, 0)

point4_x_2 = (8, 0)

point3_point4_x_2 = np.row_stack((point3_x_2, point4_x_2))

#3x_1 + 2x_2 ≤ 18

x_1_region_1 = np.linspace(0,4, 100)

x_2_region_1 = 1/2*(18 - 3*x_1_region_1)

x_1 = np.linspace(0,6, 100)

x_2 = 1/2*(18 - 3*x_1)

plt.plot(point1_point2_x_1[:,0], point1_point2_x_1[:,1],
         point3_point4_x_1[:,0], point3_point4_x_1[:,1],
         point1_point2_x_2[:,0], point1_point2_x_2[:,1],
         point3_point4_x_2[:,0], point3_point4_x_2[:,1],
         x_1, x_2)

optimal_point = (2, 6)

plt.scatter(optimal_point[0], optimal_point[1], marker='o', s=150,
            facecolors="none", edgecolors='b')

plt.legend(["$x_1 = 4$", "$x_1 = 0$",
            "$2x_2 = 12$", "$x_2 = 0$",
            "$3x_1+2x_2 = 18$", "(óptimo coordenada 1, óptimo coordenada 2)"], bbox_to_anchor=(1, 1))

point_1_interior_points = (1, 2)
point_2_interior_points = (1.27, 4)
point_3_interior_points = (1.38, 5)
point_4_interior_points = (1.56, 5.5)

points_interior_points = np.row_stack((point_1_interior_points,
                                       point_2_interior_points,
                                       point_3_interior_points,
                                       point_4_interior_points))

plt.plot(points_interior_points[:, 0], points_interior_points[:, 1],
         marker='o', color="blue"
         )

plt.fill_between(x_1_region_1, 0, x_2_region_1, where=x_2_region_1<=6, color="plum")
x_1_region_2 = np.linspace(0,2, 100)
plt.fill_between(x_1_region_2, 0, 6, color="plum")
plt.title("Región factible del PL")
plt.show()

```

```{margin}

Recuérdese que los parámetros de un PL son $b_i, c_i, a_{ij}$.

```

Aunque los métodos PI son una buena alternativa para resolver PL's perdemos ventajas que tiene el método símplex como es el análisis de sensibilidad y el análisis posterior que puede realizarse al modificar los parámetros del PL. Ver las referencias al final de la nota para tales análisis.

## Método primal-dual

Se describirán dos ideas que se utilizan en los métodos primal dual y posteriormente una tercera idea que utiliza la función de barrera logarítmica (FBL). Para esto, considérese la forma estándar de un PL (PLE):

$$
\displaystyle \min_{x \in \mathbb{R}^n} c^Tx\\
\text{sujeto a:} \\
Ax=b\\
x \geq 0
$$

donde: $A \in \mathbb{R}^{m \times n}, b \in \mathbb{R}^m$, $m < n$ con *rank* completo por renglones y las restricciones se interpretan de una forma *pointwise*.

```{margin}

Las restricciones $Ax = b$ se pueden escribir con funciones $h: \mathbb{R}^n \rightarrow \mathbb{R}$ , $h_i(x) = b_i-a_i ^Tx$, $a_i$ $i$-ésimo renglón de $A \in \mathbb{R}^{m \times n}$ y $b_i$ $i$-ésima entrada de $b$ para $i=1, \cdots, m$

```

La función Lagrangiana del problema anterior es:

$$\mathcal{L}(x, \lambda, \nu) = f_o(x) + \displaystyle \sum_{i=1}^n \lambda_i f_i(x) + \sum_{i=1}^m \nu_i h_i(x) = c^Tx + \lambda^T(-x)  + \nu^T(b-Ax)$$

donde: $\mathcal{L}: \mathbb{R}^n \times \mathbb{R}^n \times \mathbb{R}^m \rightarrow \mathbb{R}$.

El problema dual asociado es:

```{margin}

El problema primal es:

$\displaystyle \min_{x \in \mathbb{R}^n} c^Tx \\
\text{sujeto a:}\\
Ax = b \\
x \geq 0
$

$A \in \mathbb{R}^{m \times n}$ y *rank* de A igual a $m < n$.

```

$$\displaystyle \max_{\nu \in \mathbb{R}^m, \lambda \in \mathbb{R}^n} b^T \nu \\
\text{sujeto a :} \\
c - A^T \nu - \lambda = 0 \\
\lambda \geq 0
$$

Las condiciones KKT son:

```{margin}

Ver {ref}`las condiciones KKT para un PL en su forma estándar<CONDKKTPLESTANDAR>`.

```

$$
\begin{eqnarray}
\nabla_x \mathcal{L}(x, \lambda, \nu) &=& c - A^T\nu - \lambda = 0 \nonumber \\
\lambda^Tx &=& 0 \nonumber \\
Ax &=& b \nonumber \\
-x &\leq& 0 \nonumber \\
\lambda &\geq& 0
\end{eqnarray}
$$

Los métodos de la clase primal-dual encuentran soluciones $(x^*, \lambda^*, \nu^*)$ para las igualdades anteriores y modifican las direcciones de búsqueda y tamaños de paso para que las desigualdades se satisfagan de forma **estricta** en cada iteración.

En los métodos de la clase primal-dual reescribimos las condiciones KKT de optimalidad anteriores mediante una función $F: \mathbb{R}^{2n + m} \rightarrow \mathbb{R}^{2n+m}$ dada por:

$$F(x, \lambda, \nu ) = \left [ \begin{array}{c} c - A^T \nu - \lambda \\ X \Lambda e \\  b - Ax \end{array} \right ]$$

y resolvemos la ecuación **no lineal** $F(x, \lambda, \nu )=0$ para $(x, \lambda) \geq 0$, donde: $X = \text{diag}(x_1, \dots, x_n)$, $\Lambda = \text{diag}(\lambda_1, \dots, \lambda_n)$ y $e$ es un vector de $1$'s en $\mathbb{R}^n$. Además en cada iteración se cumple $x^{(k)} > 0$ y $\lambda^{(k)} > 0$ para $(x^{(k)}, \lambda^{(k)}, \nu^{(k)})$, por esto tales métodos son considerados como puntos interiores.

Como la mayoría de los métodos iterativos en optimización, los métodos primal-dual tienen un procedimiento para determinar la dirección de búsqueda y una cantidad que debe ser monitoreada cuyo valor alcance un valor objetivo. En el caso de los PLE's tal cantidad es la *duality gap* medida como: $\lambda^Tx$, ver {ref}`brecha dual <BRECHADUAL>`.

(PRIMIDEAMETPRIMDUAL)=

### Primera idea: determinar la dirección de búsqueda

```{margin}

Sistema de ecuaciones no lineales a resolver:

$F(x, \lambda, \nu ) = \left [ \begin{array}{c}  c - A^T \nu - \lambda \\ X \Lambda e \\ b - Ax \end{array} \right ] = 0$

```

La dirección de búsqueda se determina aplicando el método de Newton al sistema de ecuaciones no lineales que se muestra en el margen del PLE. Por tanto, se resuelve el sistema de ecuaciones lineales:

$$J_F(x, \lambda, \nu) \left [ \begin{array}{c} \Delta x \\ \Delta \lambda \\ \Delta \nu \end{array} \right ] = - F(x, \lambda, \nu)$$

donde: $J_F$ es la Jacobiana de $F$ cuya expresión es:

$$J_F(x, \lambda, \nu) = \left [ \begin{array}{ccc} 0 & I & -A^T \\ \Lambda & X & 0 \\ -A & 0 & 0 \end{array} \right ].$$

para el vector de incógnitas $\left [ \begin{array}{c} \Delta x \\ \Delta \lambda \\ \Delta \nu \end{array} \right ]$.

Una vez calculado tal vector de incógnitas se realiza la actualización:

$$\left [ \begin{array}{c} x \\ \lambda \\ \nu \end{array} \right ]^{(k+1)} = \left [ \begin{array}{c} x \\ \lambda \\ \nu \end{array} \right ]^{(k)} + \left [ \begin{array}{c} \Delta x \\ \Delta \lambda \\ \Delta \nu \end{array} \right ]$$

donde: $k$ hace referencia a la $k$-ésima iteración.

Ver {ref}`Sistema de ecuaciones no lineales<SISTECNOLINEALES>`.

```{admonition} Observación
:class: tip

Si bien podría elegirse otra dirección de búsqueda, la dirección de Newton (o variantes de ésta) se prefiere por sus propiedades de convergencia e invarianza ante transformaciones afín.

```

Si denotamos $r_d = c - A^T \nu - \lambda, r_p = b - Ax$ como el residual para factibilidad dual y residual para factibilidad primal respectivamente entonces el sistema de ecuaciones lineales a resolver es:

$$\left [ \begin{array}{ccc} 0 & I & -A^T \\ \Lambda & X & 0 \\ -A & 0 & 0 \end{array} \right ]  \left [ \begin{array}{c} \Delta x \\ \Delta \lambda \\ \Delta \nu \end{array} \right ] =  - \left [ \begin{array}{c} r_d \\ X \Lambda e \\ r_p \end{array} \right ]$$

```{admonition} Comentarios

* El sistema de ecuaciones lineales anterior para problemas *large scale* no se construye pues es un sistema cuadrado de tamaño $2n + m \times 2n + m$ y se resuelve reduciéndolo a sistemas de ecuaciones equivalentes. Representa el paso más costoso del método primal-dual.

* Se pueden eliminar los signos negativos que están en los bloques de la matriz del sistema de ecuaciones lineales anterior que contienen $A, A^T$ pero por consistencia con lo desarrollado en la nota de dualidad para un PL se mantienen los signos negativos (si se eliminan también debe de ajustarse el lado derecho del sistema). Esta modificación se relaciona con la definición de la función Lagrangiana.

```

(SEGUNIDEAPRIMDUAL)=

### Segunda idea: cortar el paso

Si se toma un paso completo es muy posible que en la siguiente iteración se encuentre muy cerca de alguna de las fronteras de restricción o bien se salga de la región factible. Para esto se define un parámetro $t^{(k)} \in (0, 1]$ y por tanto la actualización es:

$$\left [ \begin{array}{c} x \\ \lambda \\ \nu \end{array} \right ]^{(k+1)} = \left [ \begin{array}{c} x \\ \lambda \\ \nu \end{array} \right ]^{(k)} + t^{(k)}  \left [ \begin{array}{c} \Delta x \\ \Delta \lambda \\ \Delta \nu \end{array} \right ]$$

donde: $k$ hace referencia a la $k$-ésima iteración.

```{admonition} Comentario

El parámetro $t^{(k)}$ se calcula con metodologías como búsqueda de línea o regiones de confianza, ver [line search](https://en.wikipedia.org/wiki/Line_search), {ref}`método de búsqueda de línea por backtracking <MBUSLINBACK>`, [trust region](https://en.wikipedia.org/wiki/Trust_region).

```

## Método primal-dual de barrera logarítmica (BL)

### Tercera idea: reducir la *duality gap* y centrar. Uso de la función de barrera logarítmica (FBL)

En cada iteración los métodos primal-dual buscan reducir la *duality gap* o bien mantenerse "cerca" de la trayectoria nombrada trayectoria central.

```{margin}

Sistema de ecuaciones lineales $\left [ \begin{array}{ccc} 0 & I & -A^T \\ \Lambda & X & 0 \\ -A & 0 & 0 \end{array} \right ]  \left [ \begin{array}{c} \Delta x \\ \Delta \lambda \\ \Delta \nu \end{array} \right ] =  - \left [ \begin{array}{c} r_d \\ X \Lambda e \\ r_p \end{array} \right ]$

```

```{admonition} Comentario

Recuérdese que la *duality gap* en un PLE para el par $(x, \nu)$ primal-dual factible está dada por la diferencia: $c^Tx - b^T \nu$. La *duality gap* en un PLE es igual a $\lambda^Tx$. En el sistema de ecuaciones lineales que se muestra en el margen se representa cada sumando de $\lambda^Tx$ con el producto $X \Lambda e$ (recuérdese $X, \Lambda$ son matrices diagonales).

```

La trayectoria central se define a partir de la FBL, ver [Barrier function](https://en.wikipedia.org/wiki/Barrier_function). La definición siguiente se da para un POCE de forma general.

```{margin}

Las restricciones $Ax = b$ se pueden escribir con funciones $h_i: \mathbb{R}^n \rightarrow \mathbb{R}$ , $h_i(x) = b_i-a_i ^Tx$, $a_i$ $i$-ésimo renglón de $A \in \mathbb{R}^{p \times n}$ y $b_i$ $i$-ésima entrada de $b$ para $i=1, \cdots, p$

```

```{admonition} Definición

Considérese el problema de optimización convexa en la forma estándar (POCE):

$$
\begin{eqnarray}
\displaystyle \min_{x \in \mathbb{R}^n} &f_o(x)& \nonumber \\
&\text{sujeto a:}& \nonumber\\
f_i(x) &\leq& 0  \quad i=1,\dots,m \nonumber \\
Ax &=& b
\end{eqnarray}
$$

con $A \in \mathbb{R}^{p \times n}$ y *rank* de $A$ igual a $p < n$.

Se define la función de barrera logarítmica (FBL) como:

$$\phi(x) =-\displaystyle \sum_{i=1}^m \log(-f_i(x))$$

```

```{sidebar} Un poco de historia ...

La metodología para resolver el problema de barrera logarítmica (PBL) está fundamentada en la *sequential unconstrained minimization technique (SUMT)*, [A. V. Fiacco, G. P. McCormick, 1965](https://www.jstor.org/stable/168637?seq=1). Es una técnica para resolver problemas no lineales sin restricciones que genera una secuencia de puntos interiores factibles que convergen a la solución del problema. Se eligen funciones de barrera con propiedades como la convexidad. Hay versiones de la SUMT para puntos exteriores que inician con puntos no factibles y vía la penalización se busca la convergencia hacia la región factible. En cada iteración de SUMT se define un valor del parámetro de barrera y se resuelve un problema de optimización más sencillo que el original con el método de Newton. La solución de tal problema se utiliza para definir puntos iniciales del siguiente problema a resolver con un valor del parámetro de barrera diferente. A medida que se avanza en las iteraciones la función objetivo del PBL se aproxima cada vez más a $f_o$, al valor óptimo y al conjunto óptimo.

```

El POCE se resuelve planteando el siguiente problema:

$$
\displaystyle \min_{x \in \mathbb{R}^n} f_B(x|t_B) \\
\text{sujeto a:} \\
Ax = b
$$

donde: $f_B(x|t_B) = f_o(x) + \frac{1}{t_B} \phi(x) = f_o(x) -  \frac{1}{t_B} \displaystyle \sum_{i=1}^m \log(-f_i(x))$, $\phi: \mathbb{R}^n \rightarrow \mathbb{R}$ con $t_B$ un parámetro positivo que nombramos **parámetro de barrera**. Denotamos a este problema como **problema de barrera logarítmica (PBL)**.

```python
x = np.linspace(-2, -.1, 100)
log_barrier = -np.log(-x)
t_B1 = 0.2
t_B2 = 0.5
t_B3 = 1
t_B4 = 2
t_B5 = 10
plt.plot(x, 1/t_B1*log_barrier, "r",
         x, 1/t_B2*log_barrier, "b",
         x, 1/t_B3*log_barrier, "g",
         x, 1/t_B4*log_barrier, "m",
         x, 1/t_B5*log_barrier, "c")
plt.legend(["$t_{B1}=0.2$",
            "$t_{B2}=0.5$",
            "$t_{B3}=1$",
            "$t_{B4}=2$",
            "$t_{B5}=10$"], bbox_to_anchor=(1,1))
plt.axhline(color="black")
plt.axvline(color="black")
plt.title("Gráfica de la FBL variando el parámetro $t_B$")
plt.show()
```

Valores más grandes de $t_B$ hacen que $f_B(x|t_B)$ tienda a $f_o(x)$. Como se observa en la gráfica anterior al elegir un valor de $t_B$ cada vez más grande se tiene: $f_B(x|t_B) = f_o(x) + \frac{1}{t_B} \phi(x) \approx f_o(x)$.

```{margin}

El POCE recuérdese es:

$
\begin{eqnarray}
\displaystyle \min_{x \in \mathbb{R}^n} &f_o(x)& \nonumber \\
&\text{sujeto a:}& \nonumber\\
f_i(x) &\leq& 0  \quad i=1,\dots,m \nonumber \\
Ax &=& b
\end{eqnarray}
$

y el PBL es:

$
\displaystyle \min_{x \in \mathbb{R}^n} f_B(x|t_B) \\
\text{sujeto a:} \\
Ax = b
$

con $A \in \mathbb{R}^{p \times n}$ y *rank* de $A$ igual a $p < n$,

$\begin{eqnarray}
f_B(x|t_B) &=& f_o(x) + \frac{1}{t_B} \phi(x) \nonumber \\
&=& f_o(x) -  \frac{1}{t_B} \displaystyle \sum_{i=1}^m \log(-f_i(x))
\end{eqnarray}
$.

```

```{admonition} Comentarios

* La función $\phi(x) = -\frac{1}{t_B} \log(-x)$ para $x < 0$ es convexa, diferenciable y aproxima a la función indicadora:

$$I(x) = \begin{cases} \infty \text{ si } x > 0 \\ 0 \text{ si } x \leq 0 \end{cases}$$

la cual es una función discontinua:

En el dibujo anterior sólo se visualiza para el eje horizontal el intervalo $(-\infty, 0)$.

* La función indicadora del punto anterior ayuda a reescribir el POCE como:

$$\displaystyle \min_{x \in \mathbb{R}^n} f_o(x) + \displaystyle \sum_{i=1}^m I(f_i(x))$$

$$\text{sujeto a:}$$

$$Ax = b$$

por esto resolver el PBL es equivalente a resolver el POCE para valores más grandes de $t_B$.

* La FBL es un caso particular de funciones de barrera que penalizan al no satisfacer las restricciones de desigualdad, ver [Penalty method](https://en.wikipedia.org/wiki/Penalty_method).

* En general las funciones de barrera deben tener las siguientes propiedades para $x$ primal factibles:

1. Tener valores "pequeños" si $x$ está "lejos" de la frontera de la región factible.

2. Tener valores "grandes" si $x$ está "cerca" de la frontera de la región factible.

3. Tener propiedades como convexidad o diferenciabilidad (ventajas al tener tales propiedades).

Por lo anterior las funciones de barrera evitan que se cruce o llegue a la frontera de la región factible del problema primal.

* Otra función de barrera para un PL es: $\phi(x) = -\displaystyle \sum_{i=1}^m \frac{1}{f_i(x)}$ para $x$ factibles.

* Los problemas de optimización convexos con únicamente restricciones de igualdad pueden resolverse aplicando extensiones del método de Newton.

```

### Trayectoria central determinada por los puntos centrales

```{margin}

El PBL para un POCE recuérdese es:

$
\displaystyle \min_{x \in \mathbb{R}^n} f_B(x|t_B) \\
\text{sujeto a:} \\
Ax = b
$

con $A \in \mathbb{R}^{p \times n}$ y *rank* de $A$ igual a $p < n$,

$\begin{eqnarray}
f_B(x|t_B) &=& f_o(x) + \frac{1}{t_B} \phi(x) \nonumber \\
&=& f_o(x) -  \frac{1}{t_B} \displaystyle \sum_{i=1}^m \log(-f_i(x))
\end{eqnarray}
$.

```

```{admonition} Definición

Para cada valor del parámetro de barrera $t_B$, se definen los **puntos centrales** $x^*(t_B)$ como la solución del PBL y el conjunto de puntos centrales se le nombra trayectoria central, *central path*.

```

Revisemos las condiciones KKT de optimalidad que deben cumplir los puntos centrales para un PLE:

$$
\displaystyle \min_{x \in \mathbb{R}^n} c^Tx\\
\text{sujeto a:} \\
Ax=b\\
x \geq 0
$$

con $A \in \mathbb{R}^{m \times n}$ y *rank* de $A$ igual a $m < n$.

Se tiene:

```{margin}

Recuérdese que en un PLE $f_i(x) = - x_i \forall i=1, \cdots, n$.

```

$$
\begin{eqnarray}
\phi(x) &=& -\displaystyle \sum_{i=1}^n \log(-f_i(x)) \nonumber \\
&=& - \sum_{i=1}^n \log(x_i)
\end{eqnarray}
$$

y por tanto el PBL para el PLE (PBL-PLE) es:

$$\displaystyle \min_{x \in \mathbb{R}^n} c^Tx - \frac{1}{t_B} \displaystyle \sum_{i=1}^n \log(x_i)$$

$$\text{sujeto a:}$$

$$Ax=b$$

La función Lagrangiana del PBL-PLE es:

$$
\begin{eqnarray}
\mathcal{L}_B(x, \nu) &=& f_B(x|t_B) + \sum_{i=1}^m \nu_i h_i(x) \nonumber \\
&=& c^Tx - \frac{1}{t_B} \displaystyle \sum_{i=1}^n \log(x_i)  + \sum_{i=1}^m \nu_i(b_i-a_i ^Tx) \nonumber \\
&=& c^Tx - \frac{1}{t_B} \displaystyle \sum_{i=1}^n \log(x_i)  + \nu^T(b-Ax) \nonumber
\end{eqnarray}
$$

con $a_i$ $i$-ésimo renglón de $A$ y $b_i$ $i$-ésima entrada de $b$. Las condiciones necesarias y suficientes KKT de optimalidad del PBL-PLE son:

```{margin}

Recuérdese que las condiciones de KKT para un PLE son:

$
\begin{eqnarray}
\nabla_x \mathcal{L}(x, \lambda, \nu) &=& c - A^T\nu - \lambda = 0 \nonumber \\
\lambda^Tx &=& 0 \nonumber \\
Ax &=& b \nonumber \\
-x &\leq& 0 \nonumber \\
\lambda &\geq& 0
\end{eqnarray}
$

```

$$
\begin{eqnarray}
\nabla_x \mathcal{L}_B(x, \nu) &=& c - A^T\nu - \frac{1}{t_B}d = 0 \nonumber \\
Ax &=& b \nonumber \\
\end{eqnarray}
$$

donde: $d = X^{-1}e = \left [ \begin{array}{c} \frac{1}{x_1} \\ \vdots \\ \frac{1}{x_n} \\ \end{array} \right ]$. Ver {ref}`condiciones KKT para un PL en su forma estándar <CONDKKTPLESTANDAR>` (se muestran en el margen).

Los puntos centrales $x^*(t_B)$ resuelven el PBL-PLE y por tanto satisfacen:

$$
\begin{eqnarray}
\nabla_x \mathcal{L}_B(x^*(t_B), \nu) &=& c - A^T\nu - \frac{1}{t_B} d(t_B) = 0 \nonumber \\
Ax^*(t_B) &=& b \nonumber \\
\end{eqnarray}
$$

donde: $d(t_B) = X^{*-1}(t_B)e = \left [ \begin{array}{c} \frac{1}{x_1^*(t_B)} \\ \vdots \\ \frac{1}{x_n^*(t_B)} \\ \end{array} \right ]$.

### Relación entre las condiciones KKT de optimalidad del PLE y las del PBL-PLE

Para establecer la relación entre las condiciones KKT de optimalidad del PLE y las del PBL-PLE considérese **sólo** en esta sección que el PBL-PLE es:

$$\displaystyle \min_{x \in \mathbb{R}^n} t_B c^Tx - \displaystyle \sum_{i=1}^n \log(x_i)$$

$$\text{sujeto a:}$$

$$Ax=b$$

```{margin}

La FBL en el PBL-PLE recuérdese es:

$
\begin{eqnarray}
\phi(x) &=& -\displaystyle \sum_{i=1}^n \log(-f_i(x)) \nonumber \\
&=& - \sum_{i=1}^n \log(x_i)
\end{eqnarray}
$

```

```{admonition} Observación
:class: tip

Esta forma del PBL-PLE es equivalente a la revisada anteriormente en la que la FBL se divide por el parámetro $t_B$. Es una cuestión sólo de escritura matemática lo que se realiza a continuación.

```

Las condiciones KKT son iguales a las revisadas en la sección anterior salvo la posición en la que se tiene el parámetro $t_B$:

$$
\begin{eqnarray}
\nabla_x \mathcal{L}_B(x^*(t_B), \hat{\nu}) &=& t_Bc - A^T\hat{\nu} - d(t_B) = 0 \nonumber \\
Ax^*(t_B) &=& b \nonumber \\
\end{eqnarray}
$$

donde: $\hat{\nu} = t_B \nu$.

```{margin}

Recuérdese que las condiciones de KKT para un PLE son:

$
\begin{eqnarray}
\nabla_x \mathcal{L}(x, \lambda, \nu) &=& c - A^T\nu - \lambda = 0 \nonumber \\
\lambda^Tx &=& 0 \nonumber \\
Ax &=& b \nonumber \\
-x &\leq& 0 \nonumber \\
\lambda &\geq& 0
\end{eqnarray}
$

```

Las condiciones KKT para un PLE se muestran en el margen y obsérvese que si $\lambda_i^*(t_B) = - \frac{1}{t_B f_i(x^*(t_B))} = - \frac{1}{t_B (-x_i^*(t_B))}$ con $x_i^*(t_B)$ $i$-ésima componente de $x^*(t_B)$ $\forall i = 1, \dots, n$ entonces se cumple:

$$\lambda_i^*(t_B) > 0$$

pues por la definición de la FBL en el PBL-PLE debe cumplirse: $f_i(x)  = -x_i < 0$ o bien para los puntos centrales $-x_i^*(t_B) < 0 \forall i=1, \dots, n$. Lo anterior resulta del dominio de la función $\log$ la cual está definida únicamente en $\mathbb{R}_{++}$ (reales positivos). Esto satisface las desigualdades de factibilidad primal y de factibilidad dual de las condiciones KKT de optimalidad.

La holgura complementaria de las condiciones KKT de optimalidad para un PBL-PLE son:

$$
\begin{eqnarray}
\lambda^*(t_B)^Tx^*(t_B) &=& \displaystyle \sum_{i=1}^n \lambda_i^*(t_B) x_i^*(t_B) \nonumber \\
&=& \displaystyle \sum_{i=1}^n - \frac{x_i^*(t_B)}{t_B (-x_i^*(t_B))} \nonumber \\
&=& \displaystyle \sum_{i=1}^n \frac{1}{t_B} = \frac{n}{t_B}
\end{eqnarray}
$$

```{margin}

El POCE recuérdese es:

$
\begin{eqnarray}
\displaystyle \min_{x \in \mathbb{R}^n} &f_o(x)& \nonumber \\
&\text{sujeto a:}& \nonumber\\
f_i(x) &\leq& 0  \quad i=1,\dots,m \nonumber \\
Ax &=& b
\end{eqnarray}
$

y el PBL es:

$
\displaystyle \min_{x \in \mathbb{R}^n} f_B(x|t_B) \\
\text{sujeto a:} \\
Ax = b
$

con $A \in \mathbb{R}^{p \times n}$ y *rank* de $A$ igual a $p < n$,

$\begin{eqnarray}
f_B(x|t_B) &=& f_o(x) + \frac{1}{t_B} \phi(x) \nonumber \\
&=& f_o(x) -  \frac{1}{t_B} \displaystyle \sum_{i=1}^m \log(-f_i(x))
\end{eqnarray}
$.

```

Por tanto la *duality gap* asociada con $x^*(t_B), \lambda^*(t_B), \nu^*(t_B)$ es: $\frac{n}{t_B}$ donde: $\nu^*(t_B) = \frac{\hat{\nu}}{t_B}$.

```{margin}

Recuérdese que las condiciones de KKT para un PLE son:

$
\begin{eqnarray}
\nabla_x \mathcal{L}(x, \lambda, \nu) &=& c - A^T\nu - \lambda = 0 \nonumber \\
\lambda^Tx &=& 0 \nonumber \\
Ax &=& b \nonumber \\
-x &\leq& 0 \nonumber \\
\lambda &\geq& 0
\end{eqnarray}
$

```

```{admonition} Comentarios

* Por la forma de la *duality gap* anterior para los puntos centrales si $t_B$ se incrementa entonces la *duality gap* tiende a cero en el método primal dual de BL.

* Para un PBL que se obtiene de un POCE la *duality gap* anterior para los puntos centrales es $\frac{m}{t_B}$ pues se tienen $m$ funciones $f_i$ de desigualdad.

* Las condiciones KKT de optimalidad del PBL-PLE son las condiciones KKT de optimalidad del PLE (que se muestran en el margen) pero perturbadas por el parámetro $t_B$:

$$
\begin{eqnarray}
\nabla_x \mathcal{L}_B(x(t_B), \hat{\nu}) &=& t_Bc - A^T\hat{\nu} - d(t_B) = 0 \nonumber \\
Ax(t_B) &=& b \nonumber \\
\lambda_i(t_B)x_i(t_B) &=& \frac{1}{t_B}
\end{eqnarray}
$$

donde: $\hat{\nu} = t_B \nu$, $d(t_B) = X^{-1}(t)e = \left [ \begin{array}{c} \frac{1}{x_1(t_B)} \\ \vdots \\ \frac{1}{x_n(t_B)} \\ \end{array} \right ]$ y la *duality gap* se estima como: $\lambda(t_B)^Tx(t_B) = \frac{n}{t_B}$.

```

### ¿Cómo calcular los puntos centrales?

Para calcular los puntos centrales del PBL-PLE se utiliza la {ref}`primera idea: determinar la dirección de búsqueda <PRIMIDEAMETPRIMDUAL>` en la que se resuelve el siguiente sistema de ecuaciones no lineales con el método de Newton:

```{margin}

El PBL-PLE recuérdese es:

$
\displaystyle \min_{x \in \mathbb{R}^n} c^Tx - \frac{1}{t_B} \displaystyle \sum_{i=1}^n \log(x_i) \nonumber \\
\text{sujeto a:} \nonumber \\
Ax=b
$

con $A \in \mathbb{R}^{m \times n}$ y *rank* de $A$ igual a $m < n$. Las condiciones KKT son:

$
\begin{eqnarray}
\nabla_x \mathcal{L}_B(x, \nu) &=& c - A^T\nu - \frac{1}{t_B}d = 0 \nonumber \\
Ax &=& b \nonumber \\
\end{eqnarray}
$

donde: $d = X^{-1}e = \left [ \begin{array}{c} \frac{1}{x_1} \\ \vdots \\ \frac{1}{x_n} \\ \end{array} \right ]$.

```

$$F(x, \nu) = \left [ \begin{array}{c} c - A^T\nu - \frac{1}{t_B}d(t_B) \\ b- Ax(t_B) \end{array} \right ] = 0$$

donde: $d(t_B) = X^{*-1}(t)e = \left [ \begin{array}{c} \frac{1}{x_1^*(t_B)} \\ \vdots \\ \frac{1}{x_n^*(t_B)} \\ \end{array} \right ]$.

Este sistema de ecuaciones no lineales conduce a resolver el sistema de ecuaciones lineales:

$$J_F(x, \nu) \left [ \begin{array}{c} \Delta x \\ \Delta \nu \end{array} \right ] = - F(x, \nu)$$

donde: $J_F(x, \nu) = \left [ \begin{array}{cc} \nabla_{xx} ^2 \mathcal{L}_B(x, \nu)  &  \nabla_{\nu x} \mathcal{L}_B(x,\nu) \\ -A & 0\end{array} \right ] = \left [ \begin{array}{cc} \frac{1}{t_B} D^2(t_B) &  -A^T \\ -A & 0\end{array} \right ]$ y $D^2(t_B) = \text{diag}^2(d(t_B)) \in \mathbb{R}^{n \times n}$.

La actualización en el método de Newton es:

$$\left [ \begin{array}{c} x \\ \nu \end{array} \right ]^{(k+1)} = \left [ \begin{array}{c} x \\ \nu \end{array} \right ]^{(k)} + t^{(k)}  \left [ \begin{array}{c} \Delta x \\ \Delta \nu \end{array} \right ]$$

donde se utilizó la {ref}`segunda idea: cortar el paso <SEGUNIDEAPRIMDUAL>`.

```{admonition} Comentarios

* Se pueden eliminar los signos negativos que están en los bloques de la matriz del sistema de ecuaciones lineales anterior que contienen $A, A^T$ pero por consistencia con lo desarrollado en la nota de dualidad para un PL se mantienen los signos negativos (si se eliminan también debe de ajustarse el lado derecho del sistema). Esta modificación se relaciona con la definición de la función Lagrangiana.

* También una modificación que se realiza para que en el primer bloque del sistema de ecuaciones lineales anterior no tengamos del lado izquierdo y del lado derecho $\frac{1}{t_B}$ se puede trabajar con el problema de optimización equivalente:

$$
\displaystyle \min_{x \in \mathbb{R}^n} f_B(x|t_B) \\
\text{sujeto a:} \\
Ax = b
$$

donde: $f_B(x|t_B) = t_Bf_o(x) + \phi(x) = t_Bf_o(x) - \displaystyle \sum_{i=1}^m \log(-f_i(x))$.

**Estas dos modificaciones se utilizan para implementar el método primal-dual de BL**.

```

## Método primal-dual de BL aplicado al ejemplo prototipo

```python
!pip install --quiet "git+https://github.com/ITAM-DS/analisis-numerico-computo-cientifico.git#egg=opt&subdirectory=src"
```

    WARNING: You are using pip version 20.3.3; however, version 21.0.1 is available.
    You should consider upgrading via the '/usr/bin/python3 -m pip install --upgrade pip' command.

Se utiliza el paquete de *Python* [opt](https://analisis-numerico-computo-cientifico.readthedocs.io/) en los siguientes `import`'s.

```python
from opt.utils_logarithmic_barrier import log_barrier_aux_eval_constraints, \
constraint_inequalities_funcs_generator, constraint_inequalities_funcs_eval, \
phi, logarithmic_barrier, line_search_for_log_barrier_by_backtracking
```

Problema de optimización:

$$
\displaystyle \max_{x \in \mathbb{R}^2} 3x_1 + 5x_2\\
\text{sujeto a: } \\
x_1 \leq 4 \nonumber    \\
2x_2 \leq 12            \\
3x_1 + 2x_2 \leq 18     \\
x_1 \geq 0              \\
x_2 \geq 0              \\
$$

Definimos la función $\phi: \mathbb{R}^n \rightarrow \mathbb{R}$ como $\phi(x) = - \displaystyle \sum_{i=1}^m \log(-(a_i ^Tx-b_i)) - \sum_{i=1}^n \log(-{e}_i ^T(-x))$ con $a_i$ $i$-ésimo renglón de $A = \left [ \begin{array}{cc} 1 & 0 \\0 & 2 \\ 3 & 2 \end{array} \right ]$, $b_i$ $i$-ésima entrada del vector $b = \left [ \begin{array}{c} 4 \\ 12 \\ 18 \end{array} \right ]$ y $e_i$ $i$-ésimo vector canónico.

Para este problema las curvas de nivel de la función $\phi$ se ven como sigue:

```python
const_ineq_two_pars = {0: lambda x1,x2: x1 - 4,
                       1: lambda x1,x2: 2*x2 - 12,
                       2: lambda x1,x2: 3*x1 + 2*x2 - 18,
                       3: lambda x1,x2: -x1,
                       4: lambda x1,x2: -x2
                       }
def const_ineq_funcs_eval_two_pars(x1,x2,
                                   const_ineq):
    """
    Auxiliary function for the evaluation of constraint inequalities
    in logarithmic barrier function using two parameters as input.
    """
    const_ineq_funcs_eval = np.array([const(x1,x2) for const in \
                                      constraint_inequalities_funcs_generator(const_ineq)])
    return const_ineq_funcs_eval
def phi_two_pars(x1,x2, const_ineq):
    """
    Implementation of phi function for logarithmic barrier using two
    parameters as input.
    """
    const_ineq_funcs_eval = -const_ineq_funcs_eval_two_pars(x1,
                                                            x2,
                                                            const_ineq)
    log_barrier_const_eval = np.log(const_ineq_funcs_eval)
    return -np.sum(log_barrier_const_eval, axis=0)
```

```python
density=1e-1
x1l=0.1
x2d=0.1
x1r=4
x2u=8
x1_p=np.arange(x1l,x1r,density)
x2_p=np.arange(x2d,x2u,density)
x1_mesh,x2_mesh = np.meshgrid(x1_p,x2_p)
z = phi_two_pars(x1_mesh,x2_mesh,const_ineq_two_pars)
plt.plot(point1_point2_x_1[:,0], point1_point2_x_1[:,1], "--", color="black", label="_nolegend_")
plt.plot(point3_point4_x_1[:,0], point3_point4_x_1[:,1], "--", color="black", label="_nolegend_")
plt.plot(point1_point2_x_2[:,0], point1_point2_x_2[:,1], "--", color="black", label="_nolegend_")
plt.plot(point3_point4_x_2[:,0], point3_point4_x_2[:,1], "--", color="black", label="_nolegend_")
plt.plot(x_1, x_2, "--", color="black", label="_nolegend_")
plt.contour(x1_p, x2_p, z)
plt.title("Curvas de nivel de $\phi$")

plt.show()
```

Reescribimos el problema anterior sin las restricciones $A x \leq b$ como:

$
\displaystyle \min_{x \in \mathbb{R}^2} t_B(-3x_1 -5x_2) - [\log(4-x_1) + \log(12 - 2x_2) + \log(18 - (3 x_1 + 2 x_2)) + \log(x_1) + \log(x_2) ]\\
$

Realizamos la actualización:

$$\left [ \begin{array}{c}x_1 \\ x_2 \end{array} \right ] = \left [ \begin{array}{c}x_1 \\ x_2 \end{array} \right ] + t \left [ \begin{array}{c}\Delta x_1 \\ \Delta x_2 \end{array} \right ]$$

donde: $t$ es parámetro de *backtracking* y $\left [ \begin{array}{c}\Delta x_1 \\ \Delta x_2 \end{array} \right ]$ es solución del sistema de ecuaciones lineales:

$$
\nabla^2f_{B}(x) \left [ \begin{array}{c}\Delta x_1 \\ \Delta x_2 \end{array} \right ] = - \nabla f_{B}(x) \nonumber \\
$$

que para este problema es:

$$
\begin{eqnarray}
\tilde{A}^T \text{diag}^2(d(t_B))\tilde{A} \left [ \begin{array}{c}\Delta x_1 \\ \Delta x_2 \end{array} \right ] &=& -(t_Bc + \tilde{A}^Td(t_B)) \nonumber
\end{eqnarray}
$$

donde:

$c = \left [ \begin{array}{c}-3 \\ -5 \end{array} \right ],
\tilde{A} = \left [ \begin{array}{c} A \\ I \end{array} \right ] = \left [ \begin{array}{cc} 1 & 0 \\0 & 2 \\ 3 & 2 \\ 1 & 0 \\ 0 & 1 \end{array} \right ]$,
$b_i$ $i$-ésima entrada del vector $b = \left [ \begin{array}{c} 4 \\ 12 \\ 18 \end{array} \right ]$.

Y el vector $d(t_B) = \left [ \begin{array}{c} \frac{1}{b_1 - a_1^Tx(t_B)} \\ \frac{1}{b_2 - a_2^Tx(t_B)} \\ \frac{1}{b_3 - a_3^Tx(t_B)} \\ \frac{1}{-x_1(t_B)} \\ \frac{1}{-x_2(t_B)} \end{array}\right ] = \left [ \begin{array}{c} \frac{1}{4-x_1(t_B)} \\ \frac{1}{12-2x_2(t_B)} \\ \frac{1}{18-3x_1(t_B)-2x_2(t_B)} \\ \frac{1}{-x_1(t_B)} \\ \frac{1}{-x_2(t_B)} \end{array}\right ]$.

````{admonition} Observación
:class: tip

Aunque podríamos definir las siguientes líneas de acuerdo al desarrollo matemático anterior:

```python
A = np.array([[1, 0],
              [0, 2],
              [3, 2]])
m = 3
n = 2
b = np.array([4, 12, 18])
A_tilde = np.row_stack((A, np.eye(n)))

d = np.array([1/(b[0]-A[0,:].dot(x)),
              1/(b[1]-A[1,:].dot(x)),
              1/(b[2]-A[2,:].dot(x)),
              1/(-x[0]),
              1/(-x[1])])
system_matrix = (A_tilde.T*(d*d))@A_tilde
rhs = -(t_B*c +A_tilde.T@d)

```

usamos *SymPy* para uso de diferenciación simbólica (no se recomienda el uso de *SymPy* para problemas *medium* o *large scale*).

````

```{admonition} Comentario

Valores más "grandes" de $t_B$ hacen que la Hessiana de la función objetivo del PBL varíe rápidamente cerca de la frontera del conjunto factible. En este ejemplo prototipo algunas de las entradas de $\text{diag}^2(d(t_B))$ serán muy grandes en tales valores de $t_B$.

```

```python
import sympy
from sympy.tensor.array import derive_by_array
```

```python
x1, x2 = sympy.symbols("x1, x2")
c = np.array([-3, -5])
fo_sympy = c[0]*x1 + c[1]*x2
phi_sympy = -(sympy.log(4-x1) + sympy.log(12-2*x2) + sympy.log(18-3*x1-2*x2) + sympy.log(x1) + sympy.log(x2))

gf_sympy = derive_by_array(fo_sympy, (x1, x2))
Hf_sympy = derive_by_array(gf_sympy, (x1, x2))
gphi_sympy = derive_by_array(phi_sympy, (x1, x2))
Hphi_sympy = derive_by_array(gphi_sympy, (x1, x2))

constraints_ineq = {0: lambda x: x[0] - b[0],
                    1: lambda x: 2*x[1] - b[1],
                    2: lambda x: 3*x[0] + 2*x[1] - b[2],
                    3: lambda x: -x[0],
                    4: lambda x: -x[1]
                   }
x_0 = np.array([1, 2], dtype=float)

x = x_0
fo = lambda x: np.dot(c, x)

t_B_0 = 10
b = np.array([4, 12, 18], dtype=float)
n = x_0.size

gf_B = lambda x, t_B: np.array([component.subs({"x1": x[0],
                                                "x2": x[1],
                                                "t_B": t_B}) for component in t_B*gf_sympy + gphi_sympy],
                                dtype = float)
Hf_B = lambda x, t_B: np.array([second_partial_derivative.subs({"x1": x[0],
                                                                "x2": x[1],
                                                                "t_B": t_B}) for second_partial_derivative in t_B*Hf_sympy + Hphi_sympy],
                                dtype=float).reshape(n,n)

```

### Primera iteración

```{margin}

Aquí evaluamos la FBL del ejemplo prototipo:

$\begin{eqnarray}
f_B(x|t_B) &=& t_B(-3x_1 -5x_2) \nonumber \\
&-& \log(4-x_1) - \log(12 - 2x_2) \nonumber \\
&-& \log(18 - (3 x_1 + 2 x_2)) \nonumber \\
&-& \log(x_1) - \log(x_2)
\end{eqnarray}
$

```

```python
log_barrier_eval = logarithmic_barrier(fo,x,t_B_0,constraints_ineq)
```

```python
print(log_barrier_eval)
```

    -136.26909628370626

```python
const_ineq_funcs_eval = -constraint_inequalities_funcs_eval(x,constraints_ineq)
```

```python
print(const_ineq_funcs_eval)
```

    [ 3.  8. 11.  1.  2.]

```{margin}

Aquí revisamos que al evaluar las restricciones esté dentro del dominio de la función $\log$ (valores estrictamente positivos). Si no están los puntos en el dominio debemos devolver un mensaje y detener el método.

```

```python
if(sum(const_ineq_funcs_eval < -np.nextafter(0,1)) >=1):
    print("Some constraint inequalities evaluated in x were nonpositive, check approximations")
```

```python
fo_eval = fo(x)
```

```python
print(fo_eval)
```

    -13.0

```{margin}

Resolvemos el sistema de ecuaciones lineales para calcular la dirección de Newton.

```

```python
system_matrix = Hf_B(x, t_B_0)
rhs = -gf_B(x, t_B_0)
```

```python
dir_Newton = np.linalg.solve(system_matrix, rhs)
```

```python
print(dir_Newton)
```

    [ 19.696 142.065]

```{margin}

Aquí calculamos el decremento de Newton cuya definición se da al finalizar las iteraciones.

```

```python
dec_Newton_squared = rhs.dot(dir_Newton)
```

```python
print(dec_Newton_squared)
```

    7711.552360817478

```python
stopping_criteria = dec_Newton_squared/2
```

```python
print(stopping_criteria)
```

    3855.776180408739

```python
der_direct = -dec_Newton_squared
```

```{margin}

Aquí cortamos el paso con la metodología de búsqueda de línea por *backtracking*.

```

```python
t = line_search_for_log_barrier_by_backtracking(fo,dir_Newton,x_0,t_B_0,
                                                constraints_ineq,
                                                der_direct)
```

    /home/miuser/.local/lib/python3.7/site-packages/opt/utils_logarithmic_barrier.py:17: RuntimeWarning: invalid value encountered in log
      eval_f_const_inequality = np.log(eval_f_const_inequality)

```python
print(t)
```

    0.015625

```python
x = x + t*dir_Newton
```

```python
print(x)
```

    [1.308 4.22 ]

### Segunda iteración

```python
system_matrix = Hf_B(x, t_B_0)
rhs = -gf_B(x, t_B_0)
```

```python
log_barrier_eval = logarithmic_barrier(fo,x,t_B_0,constraints_ineq)
```

```python
print(log_barrier_eval)
```

    -255.91817648985682

```python
const_ineq_funcs_eval = -constraint_inequalities_funcs_eval(x,constraints_ineq)
```

```python
print(const_ineq_funcs_eval)
```

    [2.692 3.56  5.637 1.308 4.22 ]

```python
if(sum(const_ineq_funcs_eval < -np.nextafter(0,1)) >=1):
    print("Some constraint inequalities evaluated in x were nonpositive, check approximations")
```

```python
fo_eval = fo(x)
```

```python
print(fo_eval)
```

    -25.022042371388302

```python
dir_Newton = np.linalg.solve(system_matrix, rhs)
```

```python
print(dir_Newton)
```

    [11.93  94.597]

```python
dec_Newton_squared = rhs.dot(dir_Newton)
```

```python
print(dec_Newton_squared)
```

    5021.819828077723

```python
stopping_criteria = dec_Newton_squared/2
```

```python
print(stopping_criteria)
```

    2510.9099140388616

```python
der_direct = -dec_Newton_squared
```

```python
t = line_search_for_log_barrier_by_backtracking(fo,dir_Newton,x,t_B_0,
                                                constraints_ineq,
                                                der_direct)
```

```python
print(t)
```

    0.015625

```python
x = x + t*dir_Newton
```

```python
print(x)
```

    [1.494 5.698]

### Tercera iteración

```python
system_matrix = Hf_B(x, t_B_0)
rhs = -gf_B(x, t_B_0)
```

```python
log_barrier_eval = logarithmic_barrier(fo,x,t_B_0,constraints_ineq)
```

```python
print(log_barrier_eval)
```

    -333.0255657565843

```python
const_ineq_funcs_eval = -constraint_inequalities_funcs_eval(x,constraints_ineq)
```

```python
print(const_ineq_funcs_eval)
```

    [2.506 0.604 2.122 1.494 5.698]

```python
if(sum(const_ineq_funcs_eval < -np.nextafter(0,1)) >=1):
    print("Some constraint inequalities evaluated in x were nonpositive, check approximations")
```

```python
fo_eval = fo(x)
```

```python
print(fo_eval)
```

    -32.97166508298113

```python
dir_Newton = np.linalg.solve(system_matrix, rhs)
```

```python
print(dir_Newton)
```

    [9.648 2.785]

```python
dec_Newton_squared = rhs.dot(dir_Newton)
```

```python
print(dec_Newton_squared)
```

    406.3137761754695

```python
stopping_criteria = dec_Newton_squared/2
```

```python
print(stopping_criteria)
```

    203.15688808773476

```python
der_direct = -dec_Newton_squared
```

```python
t = line_search_for_log_barrier_by_backtracking(fo,dir_Newton,x,t_B_0,
                                                constraints_ineq,
                                                der_direct)
```

```python
print(t)
```

    0.03125

```python
x = x + t*dir_Newton
```

```python
print(x)
```

    [1.796 5.785]

````{admonition} Ejercicio
:class: tip

Utiliza las definiciones:

```python
A = np.array([[1, 0],
              [0, 2],
              [3, 2]])
m = 3
n = 2
b = np.array([4, 12, 18])
A_tilde = np.row_stack((A, np.eye(n)))

d = np.array([1/(b[0]-A[0,:].dot(x)),
              1/(b[1]-A[1,:].dot(x)),
              1/(b[2]-A[2,:].dot(x)),
              1/(-x[0]),
              1/(-x[1])])
system_matrix = (A_tilde.T*(d*d))@A_tilde
rhs = -(t_B*c +A_tilde.T@d)
```

y realiza cuatro iteraciones recalculando lo necesario para el sistema de ecuaciones lineales con `system_matrix` y `rhs` dadas por las últimas dos líneas del código que está en este ejercicio. Corrobora que obtienes los mismos resultados que con *SymPy*.

````

```{margin}

El POCE recuérdese es:

$
\begin{eqnarray}
\displaystyle \min_{x \in \mathbb{R}^n} &f_o(x)& \nonumber \\
&\text{sujeto a:}& \nonumber\\
f_i(x) &\leq& 0  \quad i=1,\dots,m \nonumber \\
Ax &=& b
\end{eqnarray}
$

y el PBL es:

$
\displaystyle \min_{x \in \mathbb{R}^n} f_B(x|t_B) \\
\text{sujeto a:} \\
Ax = b
$

con $A \in \mathbb{R}^{p \times n}$ y *rank* de $A$ igual a $p < n$,

$\begin{eqnarray}
f_B(x|t_B) &=& f_o(x) + \frac{1}{t_B} \phi(x) \nonumber \\
&=& f_o(x) -  \frac{1}{t_B} \displaystyle \sum_{i=1}^m \log(-f_i(x))
\end{eqnarray}
$.

```

```{admonition} Comentarios

* La forma general de la condición de KKT de optimalidad $\nabla_x \mathcal{L}_B(x^, \nu) = 0$ para un PBL que se obtuvo de un POCE es:

$$
\begin{eqnarray}
\nabla_x \mathcal{L}_B(x, \nu) &=& \nabla f_o(x) + \frac{1}{t_B}\nabla \phi(x) + A^T \nu = 0 \nonumber \\
&=& \nabla f_o(x) + \frac{1}{t_B} \displaystyle \sum_{i=1}^m \frac{\nabla f_i(x)}{-f_i(x)} + A^T \nu \nonumber \\
\end{eqnarray}
$$

con: $\mathcal{L}: \mathbb{R}^n \times \mathbb{R}^p \rightarrow \mathbb{R}$,

$$\begin{eqnarray} \mathcal{L}_B(x, \nu) &=& f_{B}(x|t_B) + \sum_{i=1}^p \nu_i h_i(x) \nonumber \\
&=& f_o(x|t_B) + \frac{1}{t_B} \phi(x) + \nu^T(b-Ax)
\end{eqnarray}
$$

y al aplicar el método de Newton al sistema de ecuaciones no lineales conduce a resolver el sistema de ecuaciones lineales siguiente:

$$\left [ \begin{array}{cc} \nabla^2f_o(x) + \frac{1}{t_B} \nabla^2 \phi(x)  & -A^T \\ -A & 0\end{array} \right ] \left [ \begin{array}{c} \Delta x \\ \Delta \nu \end{array} \right ] = -\left [ \begin{array}{c} \nabla f_o(x) + \frac{1}{t_B} \nabla \phi(x) \\ r_p \end{array} \right ]$$

donde: $r_p = b - Ax$ es el residual para factibilidad primal.

Ver {ref}`la función Lagrangiana <FUNLAGRANGIANA>`.

```

## Definición decremento de Newton

Para problemas de optimización convexos sin restricciones:

$$\min_{x \in \mathbb{R}^n} f_o(x)$$

en los que utilizamos el método de Newton para resolverlos, se utiliza una cantidad en criterios de paro y en resultados de convergencia nombrada el decremento de Newton.

```{admonition} Definición

El decremento de Newton para $f_o: \mathbb{R}^n \rightarrow \mathbb{R}$ en $x$ es la cantidad:

$$\lambda(x) = (\nabla f_o(x)^T \nabla^2f_o(x)^{-1} \nabla f_o(x))^{1/2}$$

en donde se asume que $f_o \in \mathcal{C}^2(\text{dom}f_o)$ y su Hessiana es definida positiva.

```

```{admonition} Comentarios

* Asumiendo que existe un punto óptimo $x^*$ y el valor óptimo se denota por $p^* = f_o(x^*)$ el decremento de Newton tiene propiedades como son:

    * $\frac{1}{2} \lambda ^2 (x)$ estima $f_o(x)-p^*$.

    * $|| \nabla^2 f_o(x)^{-1} \nabla f_o(x)||_{\nabla ^2f_o(x)} = \left ( \nabla f_o(x)^T \nabla^2 f_o(x)^{-1} \nabla ^2 f(x) \nabla ^2 f_o(x)^{-1} \nabla f_o(x) \right )^{1/2} = \lambda(x) $ que indica que $\lambda(x)$ es la norma del paso de Newton en la norma cuadrática definida por la Hessiana.

    * En el método de búsqueda de línea por *backtracking* $-\lambda (x) ^2$ es la derivada direccional de $f_o$ en $x$ en la dirección de $\Delta x_{\text{nt}}$:

$$\frac{df(x+t \Delta x_{\text{nt}})}{dt} \Bigr|_{t=0} = \nabla f_o(x)^T \Delta x_{\text{nt}} = \nabla f_o(x)^T (-\nabla^2 f_o(x)^{-1} \nabla f_o(x)) = -\lambda(x)^2$$

donde: $t$ es el parámetro de búsqueda de línea por *backtracking*, $\Delta x_{\text{nt}} = -\nabla ^2 f_o(x)^{-1} \nabla f_o(x)$ para $x \in \text{dom}f_o$ es la dirección de Newton para $f_o$ en $x$.

* En el método primal-dual para resolver un PBL el decremento de Newton se utiliza en las *inner iterations*.

```

## Algoritmo primal-dual de BL para un PL con únicamente desigualdades

Para un problema de la forma:

$$\displaystyle \min_{x \in \mathbb{R}^n} c^Tx$$

$$\text{sujeto a:}$$

$$Ax \leq b$$

$$x \geq 0$$

>**Dados** $x$ un punto estrictamente factible, esto es: $x > 0$, $Ax < b$ (todas las entradas de $x$ son positivas y $a_i^Tx < b_i$), $t_B^{(0)}$ parámetro de barrera, $\mu > 1$, $tol > 0$.
>
>$t_B:= t_B^{(0)}$.
>
>**Repetir** el siguiente bloque para $k=1,2,\dots$
>
>***Outer iterations***:
>>**Paso de centrado o *inner iterations***:
>>
>>Calcular $x^*(t_B)$ que resuelva: $\displaystyle \min_{x \in \mathbb{R}^n} t_Bf_o(x) + \phi(x)$ iniciando con $x$.
>>
>>Utilizar criterio de paro para *inner iterations*.
>
>Actualizar $x:=x^*(t_B)$.
>
> Incrementar $t_B$ por $t_B=\mu t_B$.
>
> **hasta** convergencia: satisfacer criterio de paro en el que se utiliza $tol$ y $maxiter$.

```{admonition} Observación
:class: tip

Para un PL únicamente con desigualdades recuérdese:

$$t_Bf_o(x) + \phi(x) = t_B c^Tx - \displaystyle \sum_{i=1}^m \log(-(a_i ^Tx-b_i)) - \sum_{i=1}^n \log(-{e}_i ^T(-x))$$

con $a_i$ $i$-ésimo renglón de $A$.

```

````{admonition} Comentarios

* $\mu$ es un parámetro que realiza un *trade-off* en el número de *inner* y *outer iterations*. Controla el seguimiento de la trayectoria central en las *inner iterations*. Valores grandes causan un mayor número de *inner iterations* y valores cercanos a $1$ causan un mayor número de *outer iterations*.

* La elección de $t_B^{(0)}$ ayuda a dar una estimación del recíproco de la *duality gap* (recuérdese que la estimación en un PBL-PLE es $\frac{n}{t_B}$). Es similar el efecto que con el parámetro $\mu$. Valores grandes causan que se realicen mayor número de *inner iterations* y valores pequeños un mayor número de *outer iterations*.

* El criterio de paro de las *outer iterations* en un PBL-PLE es de la forma:

```
while n/t_B > tol && iterations < max_iters
```

y el de las *inner iterations* es de la forma:

```
while dec_Newton/2 > tol && iterations < max_iters
```

con `dec_Newton/2` el decremento de Newton, `tol` una cantidad pequeña y positiva (comúnmente menor o igual a 10−8), `iterations` un contador de iteraciones.

* El algoritmo también puede regresar estimaciones para $\lambda$ con $\lambda^*(t_B)$ y $\nu$ dada por $\nu^*(t_B)$.

````

## Método primal-dual de BL aplicado al ejemplo prototipo (completo)

Problema de optimización:

$$
\displaystyle \max_{x \in \mathbb{R}^2} 3x_1 + 5x_2\\
\text{sujeto a: } \\
x_1 \leq 4 \nonumber    \\
2x_2 \leq 12            \\
3x_1 + 2x_2 \leq 18     \\
x_1 \geq 0              \\
x_2 \geq 0              \\
$$

```python
tol_outer_iter = 1e-6
tol=1e-8
tol_backtracking=1e-12
max_inner_iter=30
mu=10
x_ast = np.array([2, 6], dtype=float)
p_ast = fo(x_ast)
```

Se utiliza la función [primal_dual_method](https://analisis-numerico-computo-cientifico.readthedocs.io/en/latest/_autosummary/opt.logarithmic_barrier.linear_program_inequalities.primal_dual_method.html#opt.logarithmic_barrier.linear_program_inequalities.primal_dual_method) del paquete [opt](https://analisis-numerico-computo-cientifico.readthedocs.io/en/latest/) en la siguiente celda:

```python
from opt.logarithmic_barrier.linear_program_inequalities import primal_dual_method
```

```python
[x,total_iter,t,x_plot] = primal_dual_method(fo, constraints_ineq,
                                             x_0, tol,
                                             tol_backtracking,
                                             t_B_0,
                                             x_ast=x_ast,
                                             p_ast=p_ast,
                                             max_inner_iter=max_inner_iter,
                                             mu=mu,
                                             tol_outer_iter=tol_outer_iter,
                                             gf_B=gf_B,
                                             Hf_B=Hf_B,
                                             )
```

```{admonition} Observación
:class: tip

Obsérvese que se realizan más iteraciones con el método primal-dual para este ejemplo prototipo que con el método símplex.

```

```python
from opt.utils_logarithmic_barrier import plot_central_path
```

```python
plt.contour(x1_p, x2_p, z)
plt.xlim(-0.1, 5)
plt.ylim(-0.1,6.5)

#level curves for fo
x_1_line_1 = np.linspace(0, 6, 100)
x_2_line_1 = 1/5*(-3*x_1_line_1 + 23)
x_1_line_2 = np.linspace(0, 6, 100)
x_2_line_2 = 1/5*(-3*x_1_line_2 + 29)
x_1_line_3 = np.linspace(0, 6, 100)
x_2_line_3 = 1/5*(-3*x_1_line_3 + 36)

plt.plot(x_1_line_1, x_2_line_1, "green",label="_nolegend_")
plt.plot(x_1_line_2, x_2_line_2, "indigo",label="_nolegend_")
plt.plot(x_1_line_3, x_2_line_3, "darkturquoise", label="_nolegend_")

#central path
plot_central_path(x_plot)

```

```{admonition} Comentario

En **este ejemplo** las curvas de nivel de la función objetivo $f_o$ representadas con rectas en la gráfica anterior son tangentes a las curvas de nivel de $\phi$ en $x^*(t_B)$ pues: $t_B \nabla f_o(x^*(t_B)) + \nabla \phi(x^*(t_B)) = 0$ por lo que:

$$\nabla \phi(x^*(t_B)) = -t_B \nabla f_o(x^*(t_B)) = -t_Bc$$

```

```{admonition} Ejercicio
:class: tip

Resolver el siguiente problema con el método primal-dual de BL y corrobora con algún software tu respuesta:

$$
\displaystyle \min_{x \in \mathbb{R}^2} x_1 + x_2 - 4x_3\\
\text{sujeto a:} \\
x_1 + x_2 + 2x_3 \leq 9 \nonumber    \\
x_1 + x_2 - x_3 \leq 2 \nonumber \\
-x_1 + x_2 + x_3 \leq 4 \nonumber \\
x_1 \geq 0, x_2 \geq 0, x_3 \geq 0
$$

```

```{admonition} Comentario

El método primal-dual puede modificarse para el caso en el que se tengan puntos no primal-dual factibles. En este caso se le nombra *path following method*.

```

```{admonition} Ejercicios
:class: tip

1.Resuelve los ejercicios y preguntas de la nota.
```

**Preguntas de comprehensión**

1)¿Qué es un método por puntos interiores?

2)¿Qué se busca con el método primal-dual y cuáles son las ideas que se utilizan para su desarrollo?

3)¿Por qué al método primal-dual se le nombra así?

4)¿Qué efecto y ventajas tienen añadir funciones de barrera que penalizan al no satisfacer las restricciones de desigualdad de un problema de optimización?

5)¿Qué propiedades se buscan que satisfagan las funciones de barrera?

6)¿Por qué se elige el método de Newton para resolver el problema PBL?

7)¿Qué son los puntos centrales y la trayectoria central?

8)¿Qué relación existe entre las condiciones KKT de optimalidad del PLE y las del PBL-PLE?

9)¿Cómo se define y qué propiedades tiene el decremento de Newton?

10)Explica la tarea que tienen los parámetros $\mu$ y $t_B$ en el problema PBL. Puedes apoyar tu respuesta considerando el efecto que resulta de elegir valores grandes, pequeños de tales parámetros.

**Referencias:**

1. S. P. Boyd, L. Vandenberghe, Convex Optimization, Cambridge University Press, 2009.

2. J. Nocedal, S. J. Wright, Numerical Optimization, Springer, 2006.

3. F. Hillier, G. Lieberman, Introduction to Operations Research, Mc Graw Hill, 2014.