November 24, 2017: seminar time

November 11-23, 2017: minor reviews

November 9, 2017: refutation methods

November 8, 2017: sets

November 7, 2017: init

```python
from IPython.display import Markdown, Image, Latex

from collections import defaultdict

from muk.core import *
from muk.ext import *
from mclock import *

from sympy import IndexedBase, symbols, latex, init_printing, Eq, Matrix, binomial
init_printing()

toc = ["", "exams & courses & conferences", "what I've done", "what I'm working on"]#, "thesis arguments"]
toc_iter = iter(toc[1:])

def reference_to_this_talk():
    src = '<a href="{href}">{href}</a>'
    return Markdown(src.format(href=r'http://massimo-nocentini.github.io/PhD/second-year-summary/talk.html'))

def table_of_contents():
    src = r'# TOC'
    return Markdown('\n- '.join(toc))

def greetings(smiley=True):
    return Markdown("<h1>{greet} {smile}</h1>".format(
        greet="Thanks for coming ", smile=":)" if smiley else ""))

def next_topic():
    return Markdown("# {topic}".format(topic=next(toc_iter)))
```

```python
__AUTHOR__ = ("Massimo Nocentini",
              "massimo.nocentini@unifi.it",
              "https://github.com/massimo-nocentini/")

__ACKNOWLEDGEMENT__ = {"Beatrice Donati", "Marco Maggesi", }

__ABSTRACT__ = '''

    The relational language __microkanren__ is presented as a goal-based,
    Pythonic implementation with a *fair, complete* search strategy.

'''

__SELF__ = r'http://massimo-nocentini.github.io/PhD/mkpy/talk.html'
```

# raw outline

- programming *abstractions* and *paradigms*: *logic* to the rescue
- *microkanren* relational language
- examples of *puzzles* and *combinatorics*

## it comes down to *sets*, eventually

- our main track concerns generation of __sets__, possibly *inductively* defined
- how programming languages allow us to do that?
- is it easy in every paradigm?
    - what about imperative? functional? ...*relational*?
- by Church-Turing thesis, it should be possible even in *assembly* code
    - what if some kind of *elegance* is required?
    - don't you code not-so-easy to grasp implementation, do you?

# *primes* numbers

generate them using the [sieve of Eratosthenes][sieve]:

>iteratively mark as composite the multiples of each prime, starting with the first prime number, 2. The multiples of a given prime are generated as a sequence of numbers starting from that prime, with constant difference between them that is equal to that prime.

[sieve]:https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes

## imperative style

```python
def filter_primes(primes):

    if not primes: return [] # base case for recursion

    prime, *nats = primes # get the next prime

    for i in range(len(nats)): # for every other number
        if nats[i] % prime == 0: # check if it is composite
            nats[i] = None # if yes, cancel it

    return [prime] + filter_primes([n for n in nats if n]) # recur
```

```python
assert (filter_primes(range(2,52)) ==
        [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47])
```

## functional style

```python
def filter_primes(primes):

    if not primes: return [] # base case for recursion

    prime, *nats = primes # get the next prime

    multiples = {number for number in nats
                 if number % prime == 0} # enum multiples of prime

    return [prime] + filter_primes(sorted(set(nats) - multiples)) # recur
```

```python
assert (filter_primes(range(2,52)) ==
        [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47])
```

## declarative style

```haskell
> primes = filte_prime [2..] -- infinite list!
    where filter_prime (p:xs) =
        p : filter_prime [x | x <- xs, x `mod` p /= 0] -- lazily

> take 15 primes == [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47]
True
```

# *inductively-defined* sets

## a curious numbers machine

Let $▢, \triangle$ be natural numbers in machine

$$
\mathcal{C} =  \left \lbrace{ \over 2▢ \stackrel{\circ}{\rightarrow} ▢} , {▢ \stackrel{\circ}{\rightarrow} \triangle \over 3▢ \stackrel{\circ}{\rightarrow} \triangle 2 \triangle} \right \rbrace
$$

Questions:
- enumerate $ \alpha \stackrel{\circ}{\rightarrow} \beta $, namely the set of numbers that the machine can produce
- does exist a number $\alpha$ such that $ \alpha \stackrel{\circ}{\rightarrow} \alpha $?
- does exist a number $\beta$ such that $ \beta \stackrel{\circ}{\rightarrow} \alpha\beta $, for every number $\alpha$?

*what paradigm do you feel comfortable with in order to answer that requests?*

*are you able to generalize the algorithm to handle an arbitrary, inductively defined machine*?

---
this is part of a puzzle in the chapter *The mystery of the Montecarlo lock*

of the book [*The Lady or the Tiger*][book] by logician Raymond Smullyan.

[book]:https://www.amazon.com/Lady-Tiger-Other-Puzzles-Recreational/dp/048647027X

## context-free grammars

let $\mathcal{D}$ be the set of [*Dyck paths*][dyck] and let $\leadsto$ be a *CFG* defined as follows

$$
\left\lbrace\begin{array}{l}
\leadsto = \varepsilon \\
\leadsto = \diagup \leadsto \diagdown \leadsto \\
\end{array}\right\rbrace
$$

Questions:
- enumerate $\mathcal{D}$ using $\leadsto$
- what values for α and β work for the following path to be in $\mathcal{D}$?

      α
     / β
    /   \

---
this CFG has a nice combinat interpretation, actually any other CFG would work

[dyck]:http://mathworld.wolfram.com/DyckPath.html

## recurrence relations

```python
P = symbols(r'\mathcal{P}_{6}') # the *Pascal matrix*
Eq(P, Matrix(6, 6, binomial), evaluate=False)
```

$d_{nk}$ denotes the coefficient at row $n$ and column $k$ satisfying

$$d_{n, k} = d_{n-1, k-1} + d_{n-1, k}$$

Questions:
- are you able to unfold the recurrence at different depths for the generic coeff?
- can you generalize for an arbitrary recurrence?

# a bit of theory: *resolutions methods*

## by refutation

Let $\alpha$ be a *CNF* sentence and $M(\alpha)$ the set of models that satisfy it;

a model is a set of assignments that make $\alpha$ true.

$\alpha$ is *valid* if it is true in *all* models;
oth, it is *satisfiable* if it is true in *some* model.

*Logical reasoning* boils down to *entailment* relation $\models$ between sentences

$$
    M(\alpha) \subseteq M(\beta) \leftrightarrow \alpha \models \beta \leftrightarrow
    (\alpha \Rightarrow \beta) \text{ is valid } \leftrightarrow \neg(\neg\alpha \vee \beta) \text{ is unsatisfiable}
$$

the *resolution rule* is a *complete* inference algorithm, let $l_{q}, m_{w} \in\lbrace 0,1\rbrace$ in

$$
    {
    l_{0}\vee \ldots \vee l_{i} \vee \ldots \vee l_{j-1} \quad m_{0}\vee \ldots\vee m_{o}\vee\ldots\vee m_{k-1} \quad l_{i} = \neg m_{o}
    \over
    l_{0}\vee \ldots \vee l_{i-1}\vee l_{i+1} \vee \ldots\vee l_{j-1} \vee
    m_{0}\vee \ldots \vee m_{o-1}\vee m_{o+1}\vee\ldots\vee m_{k-1}
    }
$$

finally, if two clauses resolve to yield the empty clause $\varepsilon$ then $\alpha\models\beta$ holds

algorithm [DPLL][dpll] is a recursive, depth-first enumeration of models using resolution

paired with *early termination*, *pure symbol* and *unit clause* heuristics to speed up.

[dpll]:https://en.wikipedia.org/wiki/DPLL_algorithm

## by unification

it is a process of solving *equations between symbolic expressions*;

a *solution* is denoted as a *substitution* $\theta$, namely a mapping assigning

a symbolic value to each variable of the problem's expressions

*finite terms*: $$\lbrace cons(x,cons(x,nil)) = cons(2,y)\rbrace \theta \leftrightarrow \theta = \lbrace x \mapsto 2, y \mapsto cons(2,nil) \rbrace$$
*infinite terms*: $$ \lbrace y = cons(2,y) \rbrace \theta \leftrightarrow \theta = \lbrace y \mapsto cons(2,cons(2,cons(2,...))) \rbrace$$

let $G$ be a set of equations, unification proceeds according to the following rules:
- *delete*: $$G \cup \lbrace t = t \rbrace \rightarrow G$$
- *decompose*: $$G \cup \lbrace f(s_{0}, \ldots, s_{k}) = f(t_{0}, \ldots, t_{k})\rbrace \rightarrow G \cup \lbrace s_{0}=t_{0},\ldots, s_{k}=t_{k} \rbrace$$
- *conflict*: if $f\neq g \vee k\neq m$ then $$G \cup \lbrace f(s_{0}, \ldots, s_{k}) = g(t_{0}, \ldots, t_{m})\rbrace \rightarrow \,\perp$$
- *eliminate*: 	if $x \not\in vars(t)$ and $x \in vars(G)$ then $$G \cup \lbrace x = t\rbrace \rightarrow G\lbrace x \mapsto t\rbrace \cup \left\lbrace x \triangleq t\right\rbrace $$
- *occur check*: if $x \in vars(f(s_{0},\ldots,s_{k}))$ then $$G \cup \lbrace x = t(s_{0}, \ldots, s_{k}\rbrace \rightarrow \,\perp$$

without *occur checks*, generating a substitution $\theta$ is a *recursive enumerable* problem

# microkanren

meet _microKanren_
- a DSL for relational programming, in Scheme
- purely functional of [_miniKanren_][mk]
- *explicit streams* of satisfying states, _goal-based_ approach
- _unification_ instead of _SLD-NF resolution_
- complete, _unfair_ search strategy

my contribution
- _Pythonic_ [implementation][mkpy]: functional at the core, objective at the interface
- generators subsume _countably_-satisfiable relations; complete, _fair_ [search][dovetail]
- _The Reasoned Schemer_ fully tested via [Travis CI][travis]; moreover, [read the docs][rtfd]
- case studies: Smullyan puzzles and combinatorics
- tweaking HOL Light for _certified deductions_, [wip][klight]

[mk]:http://minikanren.org/
[travis]:https://travis-ci.org/massimo-nocentini/microkanrenpy
[rtfd]:http://microkanrenpy.readthedocs.io/en/latest/
[klight]:https://github.com/massimo-nocentini/kanren-light
[mkpy]:https://github.com/massimo-nocentini/microkanrenpy
[dovetail]:http://microkanrenpy.readthedocs.io/en/latest/under_the_hood.html#muk.core.mplus

```python
rvar(0) # a logic variable
```

    ▢₀

```python
run(succeed) # a goal that always succeeds
```

    [Tautology]

```python
run(fail) # a goal that always fails
```

```python
run(fresh(lambda q: succeed)) # a free variable without association
```

    [▢₀]

```python
run(unify(3, 3)) # unification of ground values
```

    [Tautology]

```python
run(fresh(lambda q: unify(3, q))) # unification of a variable
```

```python
run(fresh(lambda q: unify([[2, 3], 1, 2, 3], [q, 1] + q))) # list unification
```

```python
run(fresh(lambda q, z: unify(q, z) & unify(z, 3))) # co-sharing
```

```python
run(fresh(lambda q: unify(q, False) | unify(q, True))) # disjunction
```

    [False, True]

```python
run(fresh(lambda q:
          fresh(lambda q: unify(q, False)) &
          unify(q, True))) # conjunction
```

    [True]

```python
def father(p, s):
    return conde([unify(p, 'paul'), unify(s, 'jason')],
                 [unify(p, 'john'), unify(s, 'henry')],
                 [unify(p, 'jason'), unify(s, 'tom')],
                 [unify(p, 'peter'), unify(s, 'brian')],
                 [unify(p, 'tom'), unify(s, 'peter')])

def grand_father(g, s):
    return fresh(lambda p: father(g, p) & father(p, s))

run(fresh(lambda rel, p, s: grand_father(p, s) & unify([p, s], rel)))
```

    [['paul', 'tom'], ['jason', 'peter'], ['tom', 'brian']]

# a curious numbers machine

Recap, let $▢, \triangle$ be natural numbers in machine

$$
\mathcal{C} =  \left \lbrace{ \over 2▢ \stackrel{\circ}{\rightarrow} ▢} , {▢ \stackrel{\circ}{\rightarrow} \triangle \over 3▢ \stackrel{\circ}{\rightarrow} \triangle 2 \triangle} \right \rbrace
$$

```python
def machine(*, rules): # an abstract machine
    def M(a, b):
        return condi(*[[r(a, b, machine=M), succeed] for r in rules])
    return M

def associateo(g, g2g): # a number ctor
    return appendo(g, [2]+g, g2g)

def mcculloch_first_ruleo(a, b, *, machine): # machine's first rule
    return unify([2]+b, a)

def mcculloch_second_ruleo(a, g2g, *, machine): # machine's second rule
    return fresh(lambda n, g: unify([3]+n, a)
                            & associateo(g, g2g)
                            & machine(n, g))

mccullocho = machine(rules=[ mcculloch_second_ruleo, mcculloch_first_ruleo, ])
```

## about the first rule

```python
run(mccullocho([2,3,4,5], [3,4,5])) # check
```

    [Tautology]

```python
run(fresh(lambda α, β: mccullocho([4]+β, α))) # attempt to break rules
```

```python
run(fresh(lambda α: mccullocho([2,3,4,5], α))) # computing forwards
```

```python
run(fresh(lambda α: mccullocho(α, [3,4,5]))) # computing backwards
```

## about the second rule

```python
run(mccullocho([3,2,5], [5,2,5]))
```

    [Tautology]

```python
run(fresh(lambda α: mccullocho([3,3,2,5], α)), n=1)
```

```python
run(fresh(lambda β: mccullocho(β, [5,2,5,2,5,2,5])))
```

## answers

```python
run(fresh(lambda p, α, β: mccullocho(α, β) & unify([α, β], p)), n=10) # enum
```

    [[[3, 2], [2]],
     [[2] + ▢₀, ▢₀],
     [[3, 3, 2], [2, 2, 2]],
     [[3, 2, ▢₀], [▢₀, 2, ▢₀]],
     [[3, 2, ▢₀, ▢₁], [▢₀, ▢₁, 2, ▢₀, ▢₁]],
     [[3, 3, 3, 2], [2, 2, 2, 2, 2, 2, 2]],
     [[3, 2, ▢₀, ▢₁, ▢₂], [▢₀, ▢₁, ▢₂, 2, ▢₀, ▢₁, ▢₂]],
     [[3, 3, 2, ▢₀], [▢₀, 2, ▢₀, 2, ▢₀, 2, ▢₀]],
     [[3, 2, ▢₀, ▢₁, ▢₂, ▢₃], [▢₀, ▢₁, ▢₂, ▢₃, 2, ▢₀, ▢₁, ▢₂, ▢₃]],
     [[3, 3, 2, ▢₀, ▢₁], [▢₀, ▢₁, 2, ▢₀, ▢₁, 2, ▢₀, ▢₁, 2, ▢₀, ▢₁]]]

```python
run(fresh(lambda α: mccullocho(α, α)), n=1) # fixpoint
```

```python
run(fresh(lambda out, γ, αγ:
            mcculloch_lawo(γ, αγ)
          & unify([γ, αγ], out)), n=5) # McCulloch's law
```

    [[[3, 2, ▢₀, 3], [▢₀, 3, 2, ▢₀, 3]],
     [[3, 2, ▢₀, ▢₁, 3], [▢₀, ▢₁, 3, 2, ▢₀, ▢₁, 3]],
     [[3, 2, ▢₀, ▢₁, ▢₂, 3], [▢₀, ▢₁, ▢₂, 3, 2, ▢₀, ▢₁, ▢₂, 3]],
     [[3, 2, ▢₀, ▢₁, ▢₂, ▢₃, 3], [▢₀, ▢₁, ▢₂, ▢₃, 3, 2, ▢₀, ▢₁, ▢₂, ▢₃, 3]],
     [[3, 2, ▢₀, ▢₁, ▢₂, ▢₃, ▢₄, 3],
      [▢₀, ▢₁, ▢₂, ▢₃, ▢₄, 3, 2, ▢₀, ▢₁, ▢₂, ▢₃, ▢₄, 3]]]

# Dyck paths' CFG

```python
def dycko(α):
    return conde([nullo(α), succeed],
                  else_clause=fresh(lambda β, γ:
                                    appendo(['(']+β, [')']+γ, α) @
                                            (dycko(β) @ dycko(γ))))

paths = run(fresh(lambda α: dycko(α)), n=80)
D = defaultdict(list)
for α in map(lambda α: ''.join(α), paths):
    D[len(α)//2].append(α)
```

```python
from collections import namedtuple

dyck = namedtuple('dyck', ['paths', 'count'])
```

```python
[dyck(paths, len(paths)) for i in range(5) for paths in [D[i]]]
```

    [dyck(paths=[''], count=1),
     dyck(paths=['()'], count=1),
     dyck(paths=['()()', '(())'], count=2),
     dyck(paths=['()()()', '(())()', '()(())', '(()())', '((()))'], count=5),
     dyck(paths=['(())()()', '()()()()', '(())(())', '()(())()', '(()()())', '(()())()', '()()(())', '(()(()))', '((()))()', '()(()())', '((())())', '()((()))', '((()()))', '(((())))'], count=14)]

# Pascal triangle

```python
P = IndexedBase('P')
n, m = symbols('n m')

def pascalo(depth, r, c, α):
    if not depth: return unify([P[r,c]], α)
    return fresh(lambda β, γ: (pascalo(depth-1, r-1, c-1, β) @
                               pascalo(depth-1, r-1, c, γ) @
                               appendo(β, γ, α)))

unfoldings = {d:sum(addends)
              for d in range(6)
              for addends in run(fresh(lambda α: pascalo(d, n, m, α)))}
```

```python
Matrix(5, 1, lambda i, j: unfoldings[i+1])
```

```python
import this # Easter egg!!
```

    The Zen of Python, by Tim Peters

    Beautiful is better than ugly.
    Explicit is better than implicit.
    Simple is better than complex.
    Complex is better than complicated.
    Flat is better than nested.
    Sparse is better than dense.
    Readability counts.
    Special cases aren't special enough to break the rules.
    Although practicality beats purity.
    Errors should never pass silently.
    Unless explicitly silenced.
    In the face of ambiguity, refuse the temptation to guess.
    There should be one-- and preferably only one --obvious way to do it.
    Although that way may not be obvious at first unless you're Dutch.
    Now is better than never.
    Although never is often better than *right* now.
    If the implementation is hard to explain, it's a bad idea.
    If the implementation is easy to explain, it may be a good idea.
    Namespaces are one honking great idea -- let's do more of those!

```python
greetings(smiley=True)
```

Thanks for coming  :)

```python
def fives(x):
    return unify(5, x) | fives(x)

try:
    run(fresh(lambda x: fives(x)))
except RecursionError:
    pass
```

```python
def fives(x):
    return unify(5, x) | fresh(lambda y: fives(y))

run(fresh(fives), n=5)
```

    [5, ▢₀, ▢₀, ▢₀, ▢₀]

```python
g = fresh(lambda x: fives(x))
states = g(emptystate())
[next(states) for i in range(5)]
```

    [state(sub={x₀: 5}, next_index=1),
     state(sub={y₁: 5}, next_index=2),
     state(sub={y₂: 5}, next_index=3),
     state(sub={y₃: 5}, next_index=4),
     state(sub={y₄: 5}, next_index=5)]

```python
def fives(x):
    return unify(5, x) | fresh(lambda: fives(x))

run(fresh(fives), n=5)
```

```python
g = fresh(lambda x: fives(x))
states = g(emptystate())
[next(states) for i in range(5)]
```

    [state(sub={x₀: 5}, next_index=1),
     state(sub={x₀: 5}, next_index=1),
     state(sub={x₀: 5}, next_index=1),
     state(sub={x₀: 5}, next_index=1),
     state(sub={x₀: 5}, next_index=1)]

```python
def nats(x, n=0):
    return unify(n, x) | fresh(lambda: nats(x, n+1))

run(fresh(lambda x: nats(x)), n=10)
```

```python
def nullo(l):
    return unify([], l)

def appendo(r, s, out):
    def A(r, out):
        return conde([nullo(r), unify(s, out)],
                     else_clause=fresh(lambda a, d, res:
                                           unify([a]+d, r) &
                                           unify([a]+res, out) &
                                           fresh(lambda: A(d, res))))
    return A(r, out)
```

```python
run(fresh(lambda l, q: appendo([1,2,3]+q, [4,5,6], l)), n=5)
```

    [[1, 2, 3, 4, 5, 6],
     [1, 2, 3, ▢₀, 4, 5, 6],
     [1, 2, 3, ▢₀, ▢₁, 4, 5, 6],
     [1, 2, 3, ▢₀, ▢₁, ▢₂, 4, 5, 6],
     [1, 2, 3, ▢₀, ▢₁, ▢₂, ▢₃, 4, 5, 6]]

```python
run(fresh(lambda r, x, y:
          appendo(x, y, ['cake', 'with', 'ice', 'd', 't']) &
          unify([x, y], r)))
```

    [[[], ['cake', 'with', 'ice', 'd', 't']],
     [['cake'], ['with', 'ice', 'd', 't']],
     [['cake', 'with'], ['ice', 'd', 't']],
     [['cake', 'with', 'ice'], ['d', 't']],
     [['cake', 'with', 'ice', 'd'], ['t']],
     [['cake', 'with', 'ice', 'd', 't'], []]]

---

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.


 Métodos numéricos

 Segundo cuatrimestre 2021

 Práctica 1: Diferencias finitas y análisis de error

 Cátedra: Pablo Dmitruk

 Fecha límite de entrega: 10 de septiembre de 2021 23:59

### Entrega de:   **COMPLETAR CON SUS NOMBRES**

- [Ejercicios](#ejercicios)

- [Repaso teórico](#explicacion)

- [Instrucciones de entrega](#instrucciones)

# Ejercicios

## Problema 1: Primera derivada con diferencias finitas

Considerando una grilla equiespaciada, halle expresiones para la derivada primera utilizando la mínima cantidad de puntos discretos en las siguientes aproximaciones:

 diferencias finitas adelantadas;

 diferencias finitas atrasadas;

 diferencias finitas centradas.

En cada caso realice un esquema indicando a que puntos discretos corresponden los valores considerados y en cada caso especificar el orden de aproximación.

---
**Su resolución acá**

---

## Problema 2: Verificación gráfica del orden de convergencia

Verifique lo hallado en el ejercicio anterior hallando numéricamente la derivada de la función $f(x)=\mathrm{sen}(x)$. Para ello,

 Evalué las diferencias finitas en $x=1$ para espaciamientos de grilla $h=10^{-1}, 5\times 10^{-2}, 10^{-2}, 5 \times 10^{-3}, 1 \times 10^{-3}$ y grafique en una misma figura la cantidad $\epsilon (h)= |\mathrm{f'}_h(1) - \cos(1)|$ en función de $1/h$, donde $\mathrm{f'}_{h}$ denota la aproximación numérica de la derivada con espaciado $h$. ¿Qué tendencia espera encontrar en $\epsilon(h)$ para cada esquema? Ayuda: Los resultados le resultarán más fáciles de analizar graficando en  loglog.

 Para cada aproximante (adelantado, atrasado y centrado) proponga una relación funcional $\epsilon = a h^b$ e intente ajustar (con prueba y error) $a$ y $b$ de modo que esta relación funcional ajuste los puntos hallados en a. Ayuda: piense que valor debería tomar $b$ en cada caso.

 Realice la misma prueba para $x=0$. Debería notar que en algunos casos la convergencia es mejor que la esperada. ¿A qué se debe esto?

_Ayuda: obviando la estética del mismo, sus datos deberían ser compatibles con la siguiente figura:_

---

```python
# Su resolución acá
```

---
## Problema 3: Derivadas superiores

Halle expresiones usando diferencias finitas centradas con la mínima cantidad de puntos discretos para

 la derivada segunda;

 la derivada tercera.

En ambos casos realice un esquema indicando a qué puntos discretos corresponden los valores considerados y especifique el orden de aproximación. Verifique numéricamente el orden del aproximante obtenido calculando la correspondiente derivada de $f(x)=\mathrm{sen}(x)$ en $x=1$.

---

**Su resolución acá**

```python
# Su resolución acá
```

---
## Problema 4: Órdenes superiores

Encontrar en la aproximación de diferencias finitas centradas la expresión de la derivada primera a cuarto orden. Verifique numéricamente el orden del aproximante obtenido calculando la correspondiente derivada de $f(x)=\mathrm{sen}(x)$ en $x=1$.

---

**Su resolución acá**

```python
# Su resolución acá
```

---
## Problema 5: Diferencias finitas no centradas

Encontrar en la aproximación de diferencias finitas _no centradas_ (_asimétricas_) a orden 2 de la derivada primera.  Suponga que la información solo está disponible hacia adelante del punto en que se aproxima la derivada. Indicar mediante un esquema a qué puntos discretos corresponden los valores considerados. Compare el término más importante del error de truncamiento de esta aproximación con el obtenido para diferencias finitas centradas. Verifique numéricamente el orden del aproximante obtenido calculando la correspondiente derivada de $f(x)=\mathrm{sen}(x)$ en $x=1$.

---

**Su resolución acá**

```python
# Su resolución acá
```

---
## Problema 6: Derivadas en todo el dominio

Considere ahora la función $f(x) = \mathrm{sen}(x)$ definida en $[0, 2\pi)$. Utilizando $N=32, 64, 128$ puntos de grilla:

calcule numéricamente $f'(x)$ utilizando diferencias finitas centradas de segundo orden;

comparando con la derivada analítica, grafique el error absoluto $\epsilon(x)$ en función de $x$ para cada valor de $N$;

 definiendo $\epsilon_\infty = \max\limits_x \left\{\epsilon(x) \right\}$, es decir, la norma infinito del error, grafique $\epsilon_\infty$ en función de $N$ y muestre que va como $N^{-2}$, recuperando lo que obtuvo en el Problema 2.

Dado que la función a considerar es periódica, tal vez le resulte de interés utilizar [`numpy.roll`](https://numpy.org/doc/stable/reference/generated/numpy.roll.html) para construir las diferencias finitas.

_Ayuda 1: puede usar este código para generar una grilla equiespaciada de $N$ puntos en el intervalo $[0, 2\pi)$._
```python
import numpy as np
import matplotlib.pyplot as plt

N    = # Definir la cantidad de puntos
x, h = np.linspace(0, 2*np.pi, N, endpoint=False, retstep=True)
```
_Ayuda 2: estética a parte, su gráfico debería ser similar al que adjuntamos a continuación._

---

```python
# Su resolución acá
```

---
## Problema 7: Aproximaciones a la ecuación de advección

Dada la ecuación de advección lineal:
$$
\frac{\partial u}{\partial t} + c \frac{\partial u}{\partial x} = 0,
$$
se realiza la aproximación en diferencias finitas según los esquemas
\begin{align}
  \mathrm{a. \qquad} &\frac{u_j^{n+1} - u_j^{n}}{\Delta t} + c \frac{u_j^n - u_{j-1}^n}{\Delta x} = 0;\\
  \mathrm{b. \qquad} &\frac{2u_j^{n+1} - (u_{j+1}^n + u_{j-1}^n)}{2\Delta t} + c \frac{u_{j+1}^n - u_{j-1}^n}{2\Delta x} = 0,
\end{align}

Con $\Delta x$ el espaciamiento espacial entre puntos, mientras que $\Delta t$ denota el paso temporal. Indique mediante un diagrama a qué puntos discretos corresponden los valores utilizados en ambos casos.   Determine el error de truncamiento en ambos  esquemas.  ¿En qué condiciones constituyen una aproximación consistente con la ecuación de advección lineal?

Puede comprobarse fácilmente que $U(x,t)=e^{x-ct}$ verifica las ecuación de advección bajo condiciones inciales y de contorno apropiadas. Considerando dicha solución analítica:

$\mathrm{c.}$ Para los esquemas propuestos en $\mathrm a.$ y $\mathrm b.$, grafique el error de truncamiento $\delta U$ en función de $\Delta x$ para $\Delta x = 10^{-2}, 10^{-3}, 10^{-4}$ y considerando $\Delta t = (\Delta x)^{1/2}, \Delta x, (\Delta x)^2$. Considere en particular el punto $(x,t) = (1,2)$ y $c=1$. ¿Observa una disminución del error de truncamiento a medida que las grillas se vuelven más finas en todos los casos?

---

**Su resolución acá**

```python

```

---
## Problema 8: Aproximaciones a la ecuación de difusión

Dada la ecuación de difusión
\begin{equation}
    \frac{\partial u}{\partial t} - k \frac{\partial^2 u}{\partial x^2} = 0,
\end{equation}
se realizan las aproximaciones en diferencias finitas:
\begin{align}
  \mathrm{a. \qquad} &\frac{u_j^{n+1} - u_j^{n}}{\Delta t} - k \frac{u_{j+1}^n - 2u_{j}^n + u_{j-1}^n}{(\Delta x)^2} = 0;\\
  \mathrm{b. \qquad} &\frac{u_j^{n+1} - u_j^{n-1}}{2\Delta t} - k \frac{u_{j+1}^n - (u_j^{n+1} + u_j^{n-1}) + u_{j-1}^n}{(\Delta x)^2} = 0.
\end{align}

Indique mediante un diagrama a qué puntos discretos corresponden los valores utilizados en ambos casos. Determine el error de truncamiento en ambos esquemas. ¿En qué condiciones constituyen una aproximación consistente con la ecuación de difusión?

Resulta inmediato ver que $U(x,t)=\cos(x)e^{-kt}$ verifica las ecuación de difusión bajo condiciones inciales y de contorno apropiadas. Considerando dicha solución analítica:

$\mathrm{c.}$ Para los esquemas propuestos en $\mathrm a.$ y $\mathrm b.$, grafique el error de truncamiento $\delta U$ en función de $\Delta x$ para $\Delta x = 10^{-2}, 10^{-3}, 10^{-4}$ y considerando $\Delta t = (\Delta x)^{1/2}, \Delta x, (\Delta x)^2$. Realice el cálculo para el punto $(x,t) = (1,2)$ con $k=1$. ¿ En todos los casos observa una disminución del error de truncamiento a medida que las grillas se vuelven más finas?

---

**Su resolución acá**

```python

```

---
## Problema 9: Representación matricial del operador diferencial

Escriba una función `matriz_2do_orden` que devuelva la matriz de representación del operador derivada primera de segundo orden (centrado). La misma recibirá como valores de entrada la cantidad de puntos de grilla interiores al dominio $N$ (contando la condición de contorno la grilla tendrá $N+1$ puntos) y la distancia entre puntos $h$. Verifique para valores razonables de $N$ (por ejemplo, $N=3$, $N=6$) que obtiene la matriz esperada.

Considere que el dominio de interés es $0 \le x \le L$ y la condición de contorno provista es en $x=0$. _Ayuda: el esquema de diferencias finitas más simple que podrá construir será aquel correspondiente a diferencias centradas_.

---

```python
# Su resolución acá
```

## Problema 10: Pendulo simple

_No sería esta una materia de física si no usáramos como ejemplo al oscilador armónico en algún momento, así que saquemos eso del medio._

Como recordarán de física elemental, la ecuación que rige la dinámica de una masa suspendida sujeta a un punto fijo mediante una cuerda inextensible de longitud $\ell$ (i.e. la ecuación que del péndulo simple) es
\begin{equation}
    \frac{\mathrm{d}^2 \theta}{\mathrm{d}t^2} + \frac{g}{\ell} \mathrm{sen}(\theta) = 0,
\end{equation}
que para pequeños apartamientos del equilibrio ($\theta = 0$) puede aproximarse como
\begin{equation}
    \frac{\mathrm{d}^2 \theta}{\mathrm{d}t^2} + \omega^2 \theta = 0,
\end{equation}
donde llamamos $\omega^2 = g/\ell$.

Aunque la forma usual de plantear este problema es como uno de valores iniciales (a partir de la posición y velocidad iniciales), si suponemos que parte del reposo y que tiene un período $T$, puede plantearse también como un problema de valores de contorno
\begin{equation}
    \theta(0) = \theta_0 \qquad \wedge \qquad \theta(T) = \theta_0.
\end{equation}

También en este régimen de pequeñas oscilaciones, el período resulta $T=2\pi/\omega$ y conocemos la solución analítica, dada por
\begin{equation}
    \theta(t) = \theta_0 \cos(\omega t).
\end{equation}

Aprovecharemos este hecho para resolver nuestra primer ecuación diferencial numéricamente y poder estimar el error cometido$^\dagger$. Para ello, considerando pequeñas oscilaciones:

 Reformule el problema del péndulo simple como uno de diferencias finitas de primer orden, de manera matricial. Considere $\omega = 2\pi$ y $\theta_0 = 0,1$.

 Encuentre la posición del tiempo $\theta$ para una grilla temporal equiespaciada de $N=32$ puntos entre $0$ y $T$ (excluyendo los contornos).

 Comparando con la solución analítica, obtenga la norma infinito del error y guárdelo (puede usar una lista para esto).

 Calcule el error para $64$ y $128$ puntos de grilla. Grafique el error en función de $N$.

 Repita los incisos anteriores para un esquema de diferencias finitas centradas de segundo orden.

 Ayuda 1: pueden serle útiles las siguientes expresiones:
\begin{align}
    f''_j &= \frac{f_{j+2} - f_{j+1} - f_j + f_{j-1}}{2h^2} &+& \mathcal{O}(h)^{\ddagger}, \\
%
    f''_j &= \frac{f_{j+1} - f_{j} - f_{j-1} + f_{j-2}}{2h^2} &+& \mathcal{O}(h)^{\ddagger}, \\
%
    f''_j &= \frac{f_{j+1} -2f_j + f_{j-1}}{h^2} &+& \mathcal{O}(h^2),
\end{align}

 Ayuda 2: El siguiente código construye la matriz de diferenciación para el operador de primer orden:

```python
def D2_primer_orden(N, h):
    D2 = np.zeros ( (N,N) )

    # Primera fila
    D2[0,0] = -1
    D2[0,1] = -1
    D2[0,2] =  1

    # Primera mitad de la matriz (DDFF adelantadas)
    for i in range(1, N//2):
        D2[i,i-1] =  1
        D2[i,i  ] = -1
        D2[i,i+1] = -1
        D2[i,i+2] =  1

    # Segunda mitad de la matriz (DDFF atrasadas)
    for i in range(N//2, N-1):
        D2[i,i-2] =  1
        D2[i,i-1] = -1
        D2[i,i  ] = -1
        D2[i,i+1] =  1

    # Última fila
    D2[N-1,N-3] =  1
    D2[N-1,N-2] = -1
    D2[N-1,N-1] = -1

    return 1/(2*h**2)*D2
```

$\dagger$: Noten que en prácticas posteriores, nos toparemos con algunos problemas que no poseerán solución analítica y, por tanto, ya no podremos realizar fácilmente este tipo de comparaciones.

$\ddagger$: Reiteramos que este aproximante a la derivada segunda es muy poco eficiente (usa 4 puntos para una aproximación de primer orden). Consideren su uso en este ejercicio una cuestión puramente pedagógica.

---

```python
# Su solución acá
```

---

# Diferencias finitas

## Aproximación asimétrica de primer orden para la primera derivada

Supongamos que tenemos una función suficientemente suave $f(x)$ cuyos valores conocemos sobre una cierta grilla equiespaciada con paso $\Delta x$, es decir, $x_k = x_0 + k \Delta x, \ \ 0 \le k \le N-1$, con $N$ la cantidad de puntos de la grilla. Llamemos $f_k$ a $f(x_k$).

Bajo estas condiciones, podemos estimar numéricamente su derivada primera utilizando, por ejemplo, el valor de $f$ en el punto de interés $x_j$ y un punto adicional. Naturalmente, la aproximación será, en general, más precisa si éste punto adicional se encuentra cerca de $x_j$.

  Para encontrar una posible aproximación a la derivada, usando dos puntos, intentemos utilizar información conocida en $x_j$ y $x_{j+1}$, es decir el punto de interés y el siguiente al mismo. Luego, utilizando el [teorema de Taylor](https://es.wikipedia.org/wiki/Teorema_de_Taylor) para escribir $f_{j+1}$, tenemos
\begin{equation}
    f_{j+1} = f_j + \frac{1}{1!} f'_j h + \frac{1}{2!} f''(\xi) h^2 \qquad x_j \le \xi \le x_{j+1}.  \tag{1}
\end{equation}
donde $h = \Delta x = x_{j+1} - x_{j}$. Despejando la derivada de la primer ecuación obtenemos la _diferencia finita adelantada_ (ya que usa información adelante del punto de interés) _de primer orden_,
\begin{equation}
    f'_j = \frac{f_{j+1}-f_j}{h} - f''(\xi_\mathrm{f}) h = \frac{f_{j+1}-f_j}{h} + \mathcal{O} (h) \qquad x_j \le \xi_\mathrm{f} \le x_{j+1} \tag{2}.
\end{equation}
Utilizamos $\mathcal{O}(h)$ para denotar un término orden $h$. Finalmente, el aproximante buscado resulta
\begin{equation}
    f'_j = \frac{f_{j+1}-f_j}{h},
\end{equation}
y conlleva, en general, un error de orden $h$ en la estimación de la derivada. De esta manera, en un entorno lo suficientemente chico alrededor de $x_j$, al disminuir $h$ en un factor 2 el error disminuirá también en un factor $2$. Por este motivo, se dice que el método es de primer orden. Noten, sin embargo, que el orden solo nos dice cómo se modifica (asintóticamente) el error a medida que la resolución de la grilla aumenta o disminuye, pero no nos permite calcular la magnitud del error (que dependerá, en este caso, de la magnitud de la derivada segunda en un entorno del punto).

De manera similar, utilzando expansiones de Taylor alrededor de $x_{j+2}$, $\dots$, $x_{j+p}$, y también $x_{j-1}$, $\dots$, $x_{j-q}$ pueden obtenerse aproximaciones de mayor orden. Veamos como hacer esto para la derivada segunda.

## Aproximación simétrica (centrada) de cuarto orden para la derivada segunda

Utilicemos ahora la misma idea para obtener un aproximador a la derivada segunda, utilizando información conocida en $x_{j-2}$, $x_{j-1}$, $x_j$, $x_{j+1}$ y $x_{j+2}$. Escribiendo expresiones de Taylor análogas a las de (1) tenemos

\begin{alignat}{10}
    f_{j-2} - f_j &= \ &-& \frac{ f^{(1)}_j}{1!} 2h &+& \frac{f^{(2)}_j}{2!}(2h)^2 &-& \frac{f^{(3)}_j }{3!}(2h)^3 &+& \frac{ f^{(4)}_j}{4!} (2h)^4 &-& \frac{f^{(5)}_j }{5!} (2h)^5 &+& \frac{1}{6!} f^{(6)}(\xi_{j-2}) (2h)^6 \qquad &x_{j-2} \le \xi_{j-2} \le x_{j-1}, \tag{3}\\
    f_{j-1} - f_j &= \ &-& \frac{f^{(1)}_j }{1!} h &+&  \frac{f^{(2)}_j}{2!} h^2 &-& \frac{f^{(3)}_j}{3!} h^3 &+& \frac{f^{(4)}_j}{4!} h^4 &-& \frac{f^{(5)}_j }{5!} h^5 &+& \frac{1}{6!} f^{(6)}(\xi_{j-1}) h^6 \qquad & x_{j-1} \le \xi_{j-1} \le x_j, \tag{4}\\
    f_{j+1} - f_j &= \ &&\frac{f^{(1)}_j }{1!} h &+& \frac{f^{(2)}_j}{2!}h^2 &+& \frac{f^{(3)}_j }{3!} h^3 &+& \frac{f^{(4)}_j }{4!} h^4 &+& \frac{f^{(5)}_j}{5!} h^5 &+& \frac{1}{6!} f^{(6)}(\xi_{j+1}) h^6 \qquad &x_j \le \xi_{j+1} \le x_{j+1}, \tag{5}\\
    f_{j+2} - f_j &= \ &&\frac{f^{(1)}_j }{1!} 2h &+& \frac{f^{(2)}_j}{2!} (2h)^2 &+& \frac{f^{(3)}_j }{3!} (2h)^3 &+& \frac{f^{(4)}_j }{4!} (2h)^4 &+& \frac{f^{(5)}_j }{5!} (2h)^5 &+& \frac{1}{6!} f^{(6)}(\xi_{j+2}) (2h)^6 \qquad &x_{j+1} \le \xi_{j+2} \le x_{j+2}, \tag{6}
\end{alignat}
donde utilizamos ahora la notación $f^{(i)}$ para denotar la $i$-ésima derivada.

Proponiendo ahora una combinación lineal de la forma $A(3) + B(4) + C(5) + D(6)$, y recordando que queremos obtener la mejor aproximación posible a la derivada segunda, esta combinación lineal debe verificar

\begin{align}
    \left(-2A - B + C + 2D \right) f^{(1)}_j h &= 0, \\
    \left( 2A + \frac{B}{2} + \frac{C}{2} + 2D \right) f^{(2)}_j h^2 &= f^{(2)}_j h^2, \\
    \left(-\frac{8}{6}A - \frac{1}{6} B + \frac{1}{6} C + \frac{8}{6} D \right) f^{(3)}_j h^3     &= 0, \\
    \left( \frac{16}{24} A + \frac{1}{24} B + \frac{1}{24} C + \frac{16}{24} D \right) f^{(4)}_j h^4  &= 0, \\
\end{align}
que resolviendo para $A$, $B$, $C$ y $D$, permite obtener
\begin{equation}
    A = \frac{-1}{12} \qquad B = \frac{4}{3} \qquad C = \frac{4}{3} \qquad D = \frac{-1}{12}.
\end{equation}
(pueden verificar este resultado [acá](https://www.wolframalpha.com/input/?i=%7B%7B-2%2C+-1%2C+1%2C+2%7D%2C+%7B2%2C+1%2F2%2C+1%2F2%2C+2%7D%2C+%7B-8%2F6%2C+-1%2F6%2C+1%2F6%2C+8%2F6%7D%2C+%7B16%2F12%2C+1%2F12%2C+1%2F12%2C+16%2F12%7D%7D+*+%7BA%2C+B%2C+C%2C+D%7D+%3D+%7B0%2C+1%2C+0%2C+0%7D)). Notemos que esta solución anula, además, los coeficientes correspondientes a la derivada quinta, como podemos ver reemplazando
\begin{equation}
    \left[-  \frac{32}{120} \left(\frac{-1}{12}\right) - \frac{1}{120} \left(\frac{4}{3}\right) + \frac{1}{120} \left(\frac{4}{3}\right) + \frac{32}{120} \left(\frac{-1}{12} \right) \right] f^{(5)}_j h^5 = 0.
\end{equation}
Como vieron en teóricas, este tipo de cancelaciones suceden frecuentemente para esquemas centrados.

Tenemos entonces que, por construcción,
\begin{equation}
   \left(f_{j-2} - f_j\right) \left[ \frac{-1}{12} \right] + \left(f_{j-1} - f_j\right) \left[ \frac{4}{3} \right] + \left(f_{j+1} - f_j\right) \left[\frac{4}{3} \right] +  \left(f_{j+2} - f_j\right) \left[ \frac{-1}{6}\right] = f^{(2)}_jh^2 + \mathcal{O}(h^6),
\end{equation}
y despejando obtenemos
\begin{equation}
    f''_j = \frac{-f_{j+2} + 16 f_{j+1} -30 f_j + 16 f_{j-1} - f_{j-2}}{12h^2} + \mathcal{O}(h^4).
\end{equation}

Vemos que, efectivamente, utilizando los valores de $f$ sobre 5 puntos, pudimos encontrar un aproximante centrado a su derivada segunda que resulta de 4to orden.

## Uso de aproximantes para construir aproximantes a derivadas superiores

Otra manera de obtener esquemas en diferencias finitas para derivadas superiores es usar recursivamente aproximantes conocidos. Por ejemplo, si conocemos la derivada primera de manera exacta en cada punto, podemos escribir una aproximación de primer orden a la derivada segunda de la siguiente manera
\begin{equation}
    f''_j = \frac{f'_{j+1} - f'_{j}}{h} + \mathcal{O}(h).
\end{equation}
Esto es, ni más ni menos, que la fórmula que vimos en $(2)$, aplicada sobre la derivada segunda. Podemos ver fácilmente que no es necesario conocer $f'$ de manera exacta, ya que hacerlo con orden $h^2$ alcanzará para que el error en toda la expresión anterior se mantenga de orden $h$.

Como demostrarán en el problema 1, el siguiente es un aproximante de segundo orden a la derivada primera
\begin{equation}
    f'_j = \frac{f_{j+1} - f_{j-1}}{2h} + \mathcal{O}(h^2),
\end{equation}
resulta en
\begin{equation}
    f''_j = \dfrac{ \left[ \frac{f_{j+2} - f_{j}}{2h} + \mathcal{O}(h^2) \right] - \left[ \frac{f_{j+1} - f_{j-1}}{2h} + \mathcal{O}(h^2) \right] }{h} + \mathcal{O}(h) = \frac{f_{j+2} - f_{j+1} - f_{j} + f_{j-1}}{2h^2} + \mathcal{O}(h).
\end{equation}
Si bien este no es un aproximante óptimo considerando que utiliza valores de la función sobre 4 puntos, ilustra la idea de cómo construir esquemas de diferencias finitas para derivadas superiores a partir de aquellos conocidos para la primer derivada, siendo una alternativa a la construcción mediante expansiones de Taylor o, como vieron en teóricas, el polinomio interpolador de Lagrange.

## Resolución matricial de problemas de contorno

En caso que nos interese resolver numéricamente un problema de valores de contorno lineal de primer orden, es decir,
\begin{align}
    \frac{\mathrm{d} u}{\mathrm{d} x} &= f(x)u + g(x), \qquad \qquad \mathrm{para} \quad 0 < x < L \\
    u(0) &= u_0,
\end{align}
con $f$ y $g$ conocidas, una forma de hacerlo es integrando (analítica o numéricamente) el miembro derecho. Otra forma de realizar esta tarea, más relacionada con los contenidos del curso, es transformar esta ecuación diferencial en una ecuación de diferencias, usando los aproximantes que vimos en los problemas previos. Por ejemplo, si discretizamos nuestro dominio usando $N$ puntos, $x_i = i \frac{L}{N-1}, \quad i=0, \dots, N-1$, y dado que conocemos $u_0$, podemos escribir una ecuación para $u_1$ usando diferencias finitas adelantadas como
\begin{equation}
    \frac{u_1 - u_0}{h} = f_1 u_1 + g_1,
\end{equation}
con $h=L/(N-1)$. Podemos despejar $u_1$ como
\begin{equation}
    u_1 = - \frac{g_1 + u_0/h}{f_1 - 1/h}.
\end{equation}
Siguiendo este procedimiento, podríamos hallar $u_2$ a partir de $u_1$ y así hasta $u_{N-1}$, aunque arrastrando un cierto error a cada paso.

Alternativamente, la misma idea puede utilizarse si reescribimos nuestro problema de contorno sobre una grilla discreta de la siguiente manera
\begin{equation}
    D\mathbf{u} + \mathbf{b} = F\mathbf{u} + \mathbf{g},
\end{equation}
con
\begin{equation}
    D = \frac{1}{h} \begin{pmatrix}
          1 &  0 & 0 & \dots &  0 & 0 \\
         -1 &  1 & 0 & \dots &  0 & 0 \\
         0 & -1 & 1 & \dots &  0 & 0 \\
         \vdots & \vdots & \vdots & \ddots & \vdots & \vdots \\
         0 &  0 & 0 & \dots & -1 & 1
    \end{pmatrix}, \qquad
    F = \begin{pmatrix}
    f_1    &      0 &  \dots &      0 \\
    0      &    f_2 &  \dots &      0 \\
    \vdots & \vdots & \ddots & \vdots \\
    0      &   0    &  \dots & f_{N-1}
    \end{pmatrix}, \qquad
    \mathbf{u} = \begin{pmatrix}
        u_1 \\ u_2 \\ \vdots \\ u_{N-2} \\ u_{N-1}
    \end{pmatrix}, \qquad
    \mathbf{b} = \frac{1}{h}\begin{pmatrix}
        - u_0 \\ 0 \\ 0 \\ \vdots \\ 0
    \end{pmatrix}, \qquad
    \mathbf{g} = \begin{pmatrix}
    g_1 \\g_2 \\ \vdots \\  g_{N-2} \\ g_{N-1} \\
    \end{pmatrix}.
\end{equation}

En particular, la matriz $D$ es la representación matricial del operador de diferenciación y $\mathbf{b}$ contiene las condiciones de contorno (escaladas) del problema. Luego, $\mathbf{u}$ puede hallarse como la solución al sistema lineal
\begin{equation}
    A \mathbf{u} = \mathbf{c},
\end{equation}
con $A = D-F$ y $\mathbf{c} = \mathbf{g} - \mathbf{b}$. Para esto pueden utilizarse métodos clásicos de álgebra lineal (reducción gaussiana, metodos iterativos, matriz inversa, etc.).

Si bien cuando la cantidad de puntos de grilla es reducida esto funcionará aceptablemente, dado que las matrices involucradas poseen una gran cantidad de ceros (i.e. son [ralas](https://es.wikipedia.org/wiki/Matriz_dispersa) — _sparse_ en inglés—), trabajar con las matrices completas se volverá muy ineficiente a resoluciones altas. Para suplir esta falencia, Numpy (y muchas otras bibliotecas de técnicas numéricas) implementan subrutinas especiales para matrices ralas. En prácticas posteriores veremos como emplearlas eficientemente.

## Análisis de consistencia

Para estudiar la consistencia entre una ecuación diferencial y la correspondiente ecuación de diferencias queremos ver si, cuando disminuye el espaciamiento entre puntos, la ecuación de diferencias tiende a la ecuación diferencial. Naturalmente, la consistencia depende tanto de la ecuación diferencial bajo estudio como del esquema de diferencias propuesto.

Supongamos que nos interesa resolver la ecuación de advección 1D
\begin{equation}
    \frac{\partial f(x,t)}{\partial t} + c \frac{\partial f(x,t)}{\partial x} = 0,  \qquad 0 < t < T \quad \wedge \quad 0 < x < L.
\end{equation}
Proponemos sendas grillas de $N$ y $J$ puntos $t^n = n T/(N-1) = n \Delta t$ y $x_j = j L/(J-1) = j\Delta x$. Noten que ahora el subíndice denota puntos del espacio y el superíndice puntos en tiempo (no potenciación). Proponemos entonces la aproximar nuestra ecuación con el esquema
\begin{equation}
    \frac{f_j^{n+1}-f_j^{n-1}}{2\Delta t} + c \frac{f^n_{j+1} - f^n_{j-1}}{2\Delta x} = 0,
\end{equation}
donde, naturalmente, $f^{n}_{j} = f(x_j, t^n)$.

Sea ahora $F$ una solución analítica a la ecuación de advección. Reemplazando en el esquema de diferencias propuesto, obtenemos
\begin{equation}
    \frac{F(x_j, t^{n+1}) -F(x_j, t^{n-1})}{2\Delta t} + c \frac{F(x_{j+1}, t^{n+1}) - F(x_{j-1}, t^n)}{2\Delta x} = \delta F,
\end{equation}
donde $\delta F$ es el _**error de truncamiento**_ (a veces llamado _**error de consistencia**_) y es una medida de qué tan bien o mal la solución real satisface nuestra ecuación en diferencias. Si la aproximación es buena, la solución analítica $F$ debería satisfacerla exactamente para $\Delta t \to 0 \wedge \Delta x \to 0$, es decir, $\delta F \to 0$.

Podemos obtener un orden para $\delta F$ en función de la resolución de la discretización empleando desarrollos de Taylor en $t^n$ y $x_j$, es decir
\begin{equation}
\delta F = \frac{1}{2\Delta t} \left( \left[F + \frac{\partial F}{\partial t} \Delta t + \frac{1}{2}\frac{\partial^2 F}{\partial t^2} (\Delta t)^2 + \mathcal{O}(\Delta t)^3 \right]_{x_j, t^n} - \left[F - \frac{\partial F}{\partial t} \Delta t + \frac{1}{2} \frac{\partial^2 F}{\partial t^2} (\Delta t)^2 + \mathcal{O}(\Delta t)^3 \right]_{x_j, t^n} \right) + \frac{c}{2\Delta x} \left( \left[F + \frac{\partial F}{\partial x} \Delta x + \frac{1}{2}\frac{\partial^2 F}{\partial x^2} (\Delta x)^2 + \mathcal{O}(\Delta x)^3\right]_{x_j, t^n} - \left[F - \frac{\partial F}{\partial x} \Delta x + \frac{1}{2} \frac{\partial^2 F}{\partial x^2} (\Delta x)^2 + \mathcal{O}(\Delta x)^3 \right]_{x_j, t^n} \right).
\end{equation}

Reagrupando tenemos
\begin{equation}
\delta F = \left[ \frac{\partial F}{\partial t} + c \frac{\partial F}{\partial x} \right]_{x_j, t^n} + \mathcal{O}\left[(\Delta t)^2\right] + \mathcal{O}\left[(\Delta x)^2\right].
\end{equation}
El primer término del miembro derecho se anula ya que $F$ es solución a la ecuación de advección en todo punto, en particular, en $(x_j, t^n)$. Obtuvimos entonces
\begin{equation}
\delta F = \mathcal{O}\left[(\Delta t)^2\right] + \mathcal{O}\left[(\Delta x)^2\right],
\end{equation}
que tiende a $0$ para $\Delta t \to 0$ y $\Delta x \to 0$ y, entonces, el esquema sugerido es consistente.

Noten que $\delta F \to 0$ no alcanza para garantizar que $f$ converja a $F$ sobre la grilla discreta. Además de consistente es necesario que el esquema sea estable para garantizar convergencia. Veremos más sobre esto en clases posteriores.

---

# Instrucciones de entrega

## Formato
Deberán presentar sus respuestas en esta misma notebook, adjuntando sus respuestas en las celdas correspondientes a cada ejercicio que presentan el texto **Su resolución acá**. Noten que algunos problemas tienen celdas de texto y de código, ya que el ejercicio en cuestión así lo requiere.

En las celdas de texto, deberán generalmente escribir expresiones matemáticas. Para ello pueden, o bien escribir $\mathrm{\LaTeX}$, como mencionamos en el material [_Fundamentos de Python para uso científico_](https://colab.research.google.com/drive/1ATrAN8vDjD7fSIMUfbsmuqB3S1zDgX64?usp=sharing), o bien subir fotos (legibles, sin rotar, etc.) utilizando la opción resaltada en rojo en la siguiente imagen:

que aparece cuando editan la celda de texto correspondiente.

## Método de entrega
Primeramente, deberán descargar esta notebook en formato **IPython notebook** (`.ipynb`). Si tienen el documento abierto en Google Colab, esta opción está disponible en
```
Archivo -> Descargar -> Descargar .ipynb
```

Deberán luego subir su resolución a la correspondiente actividad [Entrega Práctica 1](https://campus.exactas.uba.ar/mod/assign/view.php?id=208709) en el aula virtual. En dicho link deberán adjuntar el archivo como `APELLIDOESTUDIANTE1_APELLIDOESTUDIANTE2.ipynb` (o solo `APELLIDO.ipynb` en caso que trabajen individualmente) y confirmar la entrega si la versión que subieron es definitiva.


# 更多关于多元高斯分布的知识

截至目前，我们已经在不少的应用中见到了多元高斯分布的身影，比如线性回归的概率解释、高斯判别分析、高斯混合模型聚类、以及因子分析模型。在这篇笔记中，我们将揭示关于多元高斯分布的（已经在因子分析模型中引入的）更多优良特性，并给我们这些特性的直观概念。

## 1. 定义

一个向量形式的随机变量$x\in\mathbb R^n$，以$\mu\in\mathbb R^n$为期望、$\varSigma\in\mathbb S_{++}^n$为协方差矩阵（在[线性代数](sn01.ipynb)笔记中$\mathbb S_{++}^n$为$n\times n$正定对称矩阵空间，具体定义为$\mathbb S_{++}^n=\left\{A\in\mathbb R^{n\times n}: A=A^T,\ \forall x\in\mathbb R^n\land x\neq0\to x^TAx\gt0\right\}$），如果随机变量的概率密度函数满足：

$$
p\left(x;\mu,\varSigma\right)=\frac{1}{\left(2\pi\right)^{n/2}\left\lvert\varSigma\right\rvert^{1/2}}\exp\left(-\frac{1}{2}\left(x-\mu\right)^T\varSigma^{-1}\left(x-\mu\right)\right)
$$

则称该随机变量服从**多元正太（高斯）分布（multivariate normal (or Gaussian) distribution）**，写作$x\sim\mathcal N(\mu,\varSigma)$。

## 2. 相关特性

多元高斯分布在各种场景中都很好用通常是因为以下几个特性：

1. 如果我们知道随机变量$x$的期望$\mu$和协方差$\varSigma$，就可以直接得到随机变量$x$的概率密度函数；
2. 以下关于多元高斯分布的积分均有明确的解析解：

    $$
    \begin{align}
    \int_{x\in\mathbb R^n}p\left(x;\mu,\varSigma\right)\mathrm dx &= \int_{-\infty}^\infty\cdots\int_{-\infty}^\infty p\left(x;\mu,\varSigma\right)\mathrm dx_1\cdots\mathrm dx_n=1\\
    \int_{x\in\mathbb R^n}x_ip\left(x;\mu,\sigma^2\right)\mathrm dx &= \mu_i\\
    \int_{x\in\mathbb R^n}\left(x_i-\mu_i\right)\left(x_j-\mu_j\right)p\left(x;\mu,\sigma^2\right)\mathrm dx &= \varSigma_{ij}
    \end{align}
    $$

3. 高斯分布具有下列闭包性：
    * 相互独立的高斯随机变量之和仍是高斯随机变量；
    * 联合高斯分布的边缘分布仍是高斯分布；
    * 联合高斯分布的条件分布仍是高斯分布；

这几个特性，尤其是前两个，都非常直观（或至少看上去正确）。可能唯一不是很明确的是，为什么这几个特性会如此强大，在这篇笔记中，我们将给出关于这几个特性的直观概念，并介绍在平常遇到的问题中如何使用多元高斯分布处理随机变量。

## 3. 闭包规则

在这一小节，我们将介绍前面提到的闭包性，并会使用特性1、2证明这些闭包规则，或给出这些规则为什么成立的直观解释。

后面会介绍的内容有：

$$
\begin{array}
{c|c c c}
&\textrm{求和}&\textrm{边缘分布}&\textrm{条件分布}\\
\hline
\textrm{为什么是高斯分布？}&\textrm{不介绍}&\textrm{介绍}&\textrm{介绍}\\
\textrm{相应的概率密度函数}&\textrm{介绍}&\textrm{介绍}&\textrm{介绍}
\end{array}
$$

### 3.1 相互独立的高斯随机变量之和仍是高斯随机变量

这条规则的正式描述应为：

* 设$y\sim\mathcal N(\mu,\varSigma),\ z\sim\mathcal N(\mu',\varSigma')$是服从高斯发布的相互独立的随机变量，其中$\mu,\mu'\in\mathbb R^n,\ \varSigma,\varSigma'\in\mathbb S_{++}^n$，则它们之和是高斯分布：

    $$
    y+z\sim\mathcal N(\mu+\mu',\varSigma+\varSigma')
    $$

在给出证明之前，先观察这个式子：

1. 首先要指出，独立性假设在这个命题中是非常重要的。设有随机变量$y\sim\mathcal N(\mu,\varSigma)$，其期望为$\mu$、协方差矩阵为$\varSigma$，再假设$z=-y$（不独立），则显然$z$也服从高斯分布$z\sim\mathcal N(-\mu,\varSigma)$，但此时$y+z$恒为零。
2. 然后是澄清一个疑问：如果我们将两个高斯模型（类似一个高维空间的“鼓包”）相加，难道不是得到了某种“双峰模型”（两个“鼓包”）吗？这里我们要理解，两个随机变量相加$y+z$的规则并不是将每个独立的随机变量简单的概率密度简单相加，随机变量$y+z$的概率密度是将$y$和$z$的概率密度做[卷积](https://zh.wikipedia.org/wiki/%E5%8D%B7%E7%A7%AF)（[Convolution](https://en.wikipedia.org/wiki/Convolution)）。

    证明两个高斯概率密度之和仍为高斯概率密度超出了本课程范围，这里仅给出卷积的例子，设$y,z$为单变量高斯分布$y\sim\mathcal N(\mu,\varSigma),\ z\sim\mathcal N\left(\mu',\varSigma'\right)$，则它们概率密度的卷积为：
    $$
    \begin{align}
    p\left(y+z;\mu,\mu',\varSigma^2,\varSigma'^2\right)&=\int_{-\infty}^\infty p\left(w;\mu,\sigma^2\right)p\left(y+z-w;\mu',\sigma'^2\right)\mathrm dw\\
    &=\int_{-\infty}^\infty\frac{1}{\sqrt{2\pi}\sigma}\exp\left(-\frac{1}{2\sigma^2}(w-\mu)^2\right)\cdot\frac{1}{\sqrt{2\pi}\sigma'}\exp\left(-\frac{1}{2\sigma'^2}\left(y+z-w-\mu'\right)^2\right)\mathrm dw
    \end{align}
    $$

这里我们由观测得到卷积运算给出了某种高斯分布，那么根据特性1，通过卷积运算能够得到$p(y+z;\mu,\varSigma)$。不过特性1也指出，如果仅求出期望向量与协方差矩阵，也可以得到这个高斯分布，对我们来说明显这样更简单：

根据期望的线性性质有：

$$
\mathrm E\left[y_i+z_i\right]=\mathrm E\left[y_i\right]+\mathrm E\left[z_i\right]=\mu_i+\mu_i'
$$

所以$y+z$的期望就是$\mu+\mu$。同样的，协方差矩阵的第$(i,j)$元素为：

$$
\begin{align}
\mathrm E&\left[(y_i+z_i)\left(y_j+z_j\right)\right]-\mathrm E\left[y_i+z_i\right]\mathrm E\left[y_j+z_j\right]\\
&=\mathrm E\left[y_iy_j+z_iy_j+y_iz_j+z_iz_j\right]-\left(\mathrm E\left[y_i\right]+\mathrm E\left[z_i\right]\right)\left(\mathrm E\left[y_j\right]+\mathrm E\left[z_j\right]\right)\\
&=\mathrm E\left[y_iy_j\right]+\mathrm E\left[z_iy_j\right]+\mathrm E\left[y_iz_j\right]+\mathrm E\left[z_iz_j\right]-\mathrm E\left[y_i\right]\mathrm E\left[y_j\right]-\mathrm E\left[z_i\right]\mathrm E\left[y_j\right]-\mathrm E\left[y_i\right]\mathrm E\left[z_j\right]-\mathrm E\left[z_i\right]\mathrm E\left[z_j\right]\\
&=\left(\mathrm E\left[y_iy_j\right]-\mathrm E\left[y_i\right]\mathrm E\left[y_j\right]\right)+\left(\mathrm E\left[z_iz_j\right]-\mathrm E\left[z_i\right]\mathrm E\left[z_j\right]\right)+\left(\mathrm E\left[z_iy_j\right]-\mathrm E\left[z_i\right]\mathrm E\left[y_j\right]\right)+\left(\mathrm E\left[y_iz_j\right]-\mathrm E\left[y_i\right]\mathrm E\left[z_j\right]\right)
\end{align}
$$

因为$y,z$相互独立，则有$\mathrm E\left[z_iy_j\right]=\mathrm E\left[z_i\right]\mathrm E\left[y_j\right]$、$\mathrm E\left[y_iz_j\right]=\mathrm E\left[y_i\right]\mathrm E\left[z_j\right]$，于是最后两项为零，剩下的项为：

$$
\begin{align}
\mathrm E&\left[(y_i+z_i)\left(y_j+z_j\right)\right]-\mathrm E\left[y_i+z_i\right]\mathrm E\left[y_j+z_j\right]\\
&=\left(\mathrm E\left[y_iy_j\right]-\mathrm E\left[y_i\right]\mathrm E\left[y_j\right]\right)+\left(\mathrm E\left[z_iz_j\right]-\mathrm E\left[z_i\right]\mathrm E\left[z_j\right]\right)\\
&=\varSigma_{ij}+\varSigma_{ij}'
\end{align}
$$

所以得到了$y+z$的协方差矩阵为$\varSigma+\varSigma'$。

现在回忆一下刚才的计算过程，我们使用了期望的性质和随机变量的独立假设，计算出随机变量求和后$y+z$的期望和协方差矩阵。那么根据特性1，我们就可以在不进行卷积运算的情况下求出$y+z$的概率密度了。（当然，我们提前“观察”得到了求和后的分布仍是一个高斯分布。）

### 3.2 联合高斯分布的边缘分布仍是高斯分布

这条规则的正是描述应为：

设有$
\begin{bmatrix}
x_A\\x_B
\end{bmatrix}
\sim
\mathcal N
\left(
\begin{bmatrix}
\mu_A\\\mu_B
\end{bmatrix}
,
\begin{bmatrix}
\varSigma_{AA}&\varSigma_{AB}\\
\varSigma_{BA}&\varSigma_{BB}
\end{bmatrix}
\right)
$，其中的随机变量为$x_A\in\mathbb R^m,x_B\in\mathbb R^n$，式中的期望向量及协方差矩阵都是满足这两个随机变量维度要求的。则边缘分布：
$$
\begin{align}
p\left(x_A\right)&=\int_{x_B\in\mathbb R^n}p\left(x_A,x_B;\mu,\varSigma\right)\mathrm dx_B\\
p\left(x_B\right)&=\int_{x_A\in\mathbb R^m}p\left(x_A,x_B;\mu,\varSigma\right)\mathrm dx_A
\end{align}
$$
均为高斯分布：
$$
\begin{align}
x_A&\sim\mathcal N\left(\mu_A,\varSigma_{AA}\right)\\
x_B&\sim\mathcal N\left(\mu_B,\varSigma_{BB}\right)\\
\end{align}
$$

下面我们来证明这个规则，关注随机变量$x_A$的边缘分布（对于服从高斯分布的向量型随机变量$x$，我们总是可以转置随机变量$x$，就像我们转置其期望向量和协方差矩阵的行列一样。所以，只要证明规则在随机变量$x_A$上成立，就可以将结论推广到随机变量$x_B$）：

容易看出，计算边缘分布的期望和协方差矩阵是比较容易的：只需要取联合分布的期望、协方差矩阵的相应分块矩阵即可——为了证明这个结论，我们来看$x_{A,i},x_{A,j}$这两个元素（及随机变量$x_A$的第$i$个和第$j$个元素）。同时，$x_{A,i},x_{A,j}$也是$\begin{bmatrix}x_A\\x_B\end{bmatrix}$的第$i,j$个元素（因为$x_A$位于这个向量的上半部分）。要找到对应的协方差，我们可以观察协方差矩阵$\begin{bmatrix}\varSigma_{AA}&\varSigma_{AB}\\\varSigma_{BA}&\varSigma_{BB}\end{bmatrix}$的第$(i,j)$元素。这个元素位于协方差分块矩阵$\varSigma_{AA}$中，准确的说就是$\varSigma_{AA,ij}$。依照这个论点，接下来只需要查看所有的$i,j\in\{1,\cdots,m\}$就可以发现，$x_A$的协方差矩阵就是$\varSigma_{AA}$。类似的论点可以用于计算$x_A$的均值，就是$\mu_A$。因此，上面这论点告诉我们，如果我们知道了关于$x_A$的边缘分布是一个高斯分布，则我们可以立刻通过联合分布的期望向量和协方差矩阵的恰当的分块矩阵写出$x_A$的密度函数。

不过，上面的这个论点并不那么令人满意，因为我们并不能直接看出$x_A$一定服从多元高斯分布。这个论点的证明比较冗长，总体分为四个步骤：

1. 严格的用积分形式表达出边缘分布；
2. 将协方差矩阵的逆写成分块矩阵的形式；
3. 使用配方法（completion of squares）计算关于$x_B$的积分；
4. 证明得到的结果服从高斯分布。

现在来看如何实现这四步：

#### 3.2.1 边缘分布概率密度的积分形式

假设我们想要直接计算$x_A$的概率密度函数，则需要计算下面这个积分：

$$
\begin{align}
p\left(x_A\right)&=\int_{x_B\in\mathbb R^n}p\left(x_A,x_B;\mu,\varSigma\right)\mathrm dx_B\\
&=\frac{1}{(2\pi)^{\frac{m+n}{2}}\ \begin{vmatrix}\varSigma_{AA}&\varSigma_{AB}\\\varSigma_{BA}&\varSigma_{BB}\end{vmatrix}^{1/2}}
\int_{x_B\in\mathbb R^n}\exp\left(-\frac{1}{2}\begin{bmatrix}x_A-\mu_A\\x_B-\mu_B\end{bmatrix}^T\begin{bmatrix}\varSigma_{AA}&\varSigma_{AB}\\\varSigma_{BA}&\varSigma_{BB}\end{bmatrix}^{-1}\begin{bmatrix}x_A-\mu_A\\x_B-\mu_B\end{bmatrix}\right)\mathrm dx_B
\end{align}
$$

#### 3.2.2 协方差矩阵分块

我们还需要将指数部分的矩阵乘法写成另一种形式。定义矩阵$V\in\mathbb R^{(m+n)\times(m+n)}$为$V=\begin{bmatrix}V_{AA}&V_{AB}\\V_{BA}&V_{BB}\end{bmatrix}=\varSigma^{-1}$。

可以将这个矩阵视为$V=\begin{bmatrix}V_{AA}&V_{AB}\\V_{BA}&V_{BB}\end{bmatrix}=\begin{bmatrix}\varSigma_{AA}&\varSigma_{AB}\\\varSigma_{BA}&\varSigma_{BB}\end{bmatrix}^{-1}"="\begin{bmatrix}\varSigma_{AA}^{-1}&\varSigma_{AB}^{-1}\\\varSigma_{BA}^{-1}&\varSigma_{BB}^{-1}\end{bmatrix}$。要注意最右边（带引号的等号）并不成立，后面的步骤会回来继续讨论这个问题。现在只看$V$的定义就像了，不需要去在意分块矩阵究竟是什么。

根据$V$的定义，积分可以写作：

$$
\begin{align}
p\left(x_A\right)=\frac{1}{Z}\int_{x_B\in\mathbb R^n}\exp\Bigg(-\bigg[
&\frac{1}{2}\left(x_A-\mu_A\right)^TV_{AA}\left(x_A-\mu_A\right)+\frac{1}{2}\left(x_A-\mu_A\right)^TV_{AB}\left(x_B-\mu_B\right)\\
&+\frac{1}{2}\left(x_B-\mu_B\right)^TV_{BA}\left(x_A-\mu_A\right)+\frac{1}{2}\left(x_B-\mu_B\right)^TV_{BB}\left(x_B-\mu_B\right)
\bigg]\Bigg)\mathrm dx_B
\end{align}
$$

其中$Z$是某个不依靠$x_A,x_B$的常数，我们可以先暂时忽略这个值。如果以前没有做过分块矩阵乘法，可以用一个$2\times2$矩阵自己试一下，最终会得到：

$$
x^TAx=\sum_i\sum_jA_{ij}x_ix_j=x_1A_{11}x_1+x_1A_{12}x_2+x_2A_{21}x_1+x_2A_{22}x_2
$$

#### 3.2.3 使用配方法计算关于$x_B$的积分

为了计算这个积分，我们需要先求出$x_B$的积分。不过，高斯分布的积分通常很难直接求解，我们可以使用特性2求出某些特定高斯分布的积分。这一小节的主要思路，就是将上一小节得到的积分通过某种“变形”写为特性2中的某个积分，以化简求解。

这一步的关键就是配方法。考虑计算二次函数$z^TAz+b^z+c$，其中$A$是一个非奇异对称矩阵，则可以证明：

$$
\frac{1}{2}z^TAz+b^Tz+c=\frac{1}{2}\left(z+A^{-1}b\right)^TA\left(z+A^{-1}b\right)+c-\frac{1}{2}b^TA^{-1}b
$$

这是多变量泛化的配方法，在单变量代数中为：

$$
\frac{1}{2}az^2+bz+c=\frac{1}{2}a\left(z+\frac{b}{a}\right)^2+c-\frac{b^2}{2a}
$$

为了使用配方法，我们令：

$$
\begin{align}
z&=x_B-\mu_B\\
A&=V_{BB}\\
b&=V_{BA}\left(x_A-\mu_A\right)\\
c&=\frac{1}{2}\left(x_A-\mu_A\right)^TV_{AA}\left(x_A-\mu_A\right)
\end{align}
$$

则通过多变量配方法公式可得：

$$
\begin{align}
p(x_A)=\frac{1}{Z}\int_{x_B\in\mathbb R^n}\exp\Bigg(-\bigg[
&\frac{1}{2}\left(x_B-\mu_B+V_{BB}^{-1}V_{BA}\left(x_A-\mu_A\right)\right)^TV_{BB}\left(x_B-\mu_B+V_{BB}^{-1}V_{BA}\left(x_A-\mu_A\right)\right)\\
&+\frac{1}{2}\left(x_A-\mu_A\right)^TV_{AA}\left(x_A-\mu_A\right)-\frac{1}{2}\left(\left(x_A-\mu_A\right)^TV_{AB}V_{BB}^{-1}V_{BA}\left(x_A-\mu_A\right)\right)
\bigg]\Bigg)\mathrm dx_B
\end{align}
$$

我们可以将不含$x_B$的项提出来：

$$
\begin{align}
p(x_A)=\exp&\left(-\frac{1}{2}\left(x_A-\mu_A\right)^TV_{AA}\left(x_A-\mu_A\right)+\frac{1}{2}\left(\left(x_A-\mu_A\right)^TV_{AB}V_{BB}^{-1}V_{BA}\left(x_A-\mu_A\right)\right)\right)\\
&\cdot\frac{1}{Z}\int_{x_B\in\mathbb R^n}\exp\left(\frac{1}{2}\left[\left(x_B-\mu_B+V_{BB}^{-1}V_{BA}\left(x_A-\mu_A\right)\right)^TV_{BB}\left(x_B-\mu_B+V_{BB}^{-1}V_{BA}\left(x_A-\mu_A\right)\right)\right]\right)\mathrm dx_B
\end{align}
$$

现在，就可以使用特性2中的公式了，我们知道对于期望为$x$和协方差矩阵为$\varSigma$的多元高斯分布的随机变量$x$，其概率密度函数标准化后为：

$$
\frac{1}{\left(2\pi\right)^{n/2}\left\lvert\varSigma\right\rvert^{1/2}}\int_{\mathbb R^n}\exp\left(-\frac{1}{2}\left(x-\mu\right)^T\varSigma^{-1}\left(x-\mu\right)\right)=1
$$

等价的有：

$$
\int_{\mathbb R^n}\exp\left(-\frac{1}{2}\left(x-\mu\right)^T\varSigma^{-1}\left(x-\mu\right)\right)=\left(2\pi\right)^{n/2}\left\lvert\varSigma\right\rvert^{1/2}
$$

于是最终得到（改变期望或方差并不会改变概率密度函数下方的面积，全概率始终应是$1$）：

$$
p(x_A)=\frac{1}{Z}\cdot\left(2\pi\right)^{n/2}\left\lvert\varSigma\right\rvert^{1/2}\cdot\exp\left(\frac{1}{2}\left(\left(x_A-\mu_A\right)^T\left(V_{AA}-V_{AB}V_{BB}^{-1}V_{BA}\right)\left(x_A-\mu_A\right)\right)\right)
$$

#### 3.2.4 证明得到的结果是一个高斯分布

到这一步基本就结束了，忽略前面的标准化系数，可以看出$x_A$的概率密度是一个指数上带有$x_A$二次型的函数。同时，我们很容易看出是一个以$\mu_A$为期望、$V_{AA}-V_{AB}V_{BB}^{-1}V_{BA}$为协方差矩阵的高斯分布。尽管这个协方差矩阵有一些复杂，我们依旧可以得到——$x_A$的边缘分布仍是一个高斯分布。根据前面的推理，可以猜测到这个复杂的协方差矩阵可以通过某种途径化简为$\varSigma_{AA}$。

这个推导需要用到一个分块矩阵的公式：

$$
\begin{bmatrix}
A&B\\C&D
\end{bmatrix}^{-1}=
\begin{bmatrix}
M^{-1}&-M^{-1}BD^{-1}\\
-D^{-1}CM^{-1}&D^{-1}+D^{-1}CM^{-1}BD^{-1}
\end{bmatrix}
,\ M=A-BD^{-1}C$$

这个公式可以看做是$2\times2$矩阵求逆（利用$A^{-1}=\frac{1}{\det A}\mathrm{adj}A$）的多变量泛化形式：

$$
\begin{bmatrix}
a&b\\c&d
\end{bmatrix}^{-1}=
\frac{1}{ad-bc}
\begin{bmatrix}
d&-b\\-c&a
\end{bmatrix}
$$

使用这个公式，可以得到：

$$
\begin{align}
\begin{bmatrix}
\varSigma_{AA}&\varSigma_{AB}\\
\varSigma_{BA}&\varSigma_{BB}
\end{bmatrix}&=
\begin{bmatrix}
V_{AA}&V_{AB}\\
V_{BA}&V_{BB}
\end{bmatrix}^{-1}\\
&=\begin{bmatrix}
\left(V_{AA}-V_{AB}V_{BB}^{-1}V_{BA}\right)^{-1}&-\left(V_{AA}-V_{AB}V_{BB}^{-1}V_{BA}\right)^{-1}V_{AB}V_{BB}^{-1}\\
-V_{BB}^{-1}V_{BA}\left(V_{AA}-V_{AB}V_{BB}^{-1}V_{BA}\right)^{-1}&\left(V_{BB}-V_{BA}V_{AA}^{-1}V_{AB}\right)^{-1}
\end{bmatrix}
\end{align}
$$

我们可以很容易的发现$\left(V_{AA}-V_{AB}V_{BB}^{-1}V_{BA}\right)^{-1}=\varSigma_{AA}$，得证。

### 3.3 联合高斯分布的条件分布仍是高斯分布

这条规则的正是描述应为：

设有$
\begin{bmatrix}
x_A\\x_B
\end{bmatrix}
\sim
\mathcal N
\left(
\begin{bmatrix}
\mu_A\\\mu_B
\end{bmatrix}
,
\begin{bmatrix}
\varSigma_{AA}&\varSigma_{AB}\\
\varSigma_{BA}&\varSigma_{BB}
\end{bmatrix}
\right)
$，其中的随机变量为$x_A\in\mathbb R^m,x_B\in\mathbb R^n$，式中的期望向量及协方差矩阵都是满足这两个随机变量维度要求的。则条件分布为：
$$
\begin{align}
p\left(x_A\mid x_B\right)&=\frac{p\left(x_A,x_B;\mu,\varSigma\right)}{\int_{x_B\in\mathbb R^n}p\left(x_A,x_B;\mu,\varSigma\right)\mathrm dx_B}\\
p\left(x_B\mid x_A\right)&=\frac{p\left(x_A,x_B;\mu,\varSigma\right)}{\int_{x_A\in\mathbb R^m}p\left(x_A,x_B;\mu,\varSigma\right)\mathrm dx_A}
\end{align}
$$
均为高斯分布：
$$
\begin{align}
x_A\mid x_B&\sim\mathcal N\left(\mu_A+\varSigma_{AB}\varSigma_{BB}^{-1}(x_B-\mu_B),\ \varSigma_{AA}-\varSigma_{AB}\varSigma_{BB}^{-1}\varSigma_{BA}\right)\\
x_B\mid x_A&\sim\mathcal N\left(\mu_B+\varSigma_{BA}\varSigma_{AA}^{-1}(x_A-\mu_A),\ \varSigma_{BB}-\varSigma_{BA}\varSigma_{AA}^{-1}\varSigma_{AB}\right)
\end{align}
$$

与上一个证明类似，我们只需要证明$x_B\mid x_A$成立，另一个证明也会因为对称性而成立。这个证明也分为四个步骤：

1. 严格的用积分形式表达出边缘分布；
2. 将协方差矩阵的逆写成分块矩阵的形式；
3. 使用配方法；
4. 证明得到的结果服从高斯分布。

具体证明如下：

#### 3.3.1 条件分布概率密度的积分形式

假设我们想要计算在条件$x_A$下$x_B$的概率，则应该计算：

$$
\begin{align}
p\left(x_B\mid x_A\right)&=\frac{p\left(x_A,x_B;\mu,\varSigma\right)}{\int_{x_B\in\mathbb R^n}p\left(x_A,x_B;\mu,\varSigma\right)\mathrm dx_B}\\
&=\frac{1}{Z'}\exp\left(-\frac{1}{2}\begin{bmatrix}x_A-\mu_A\\x_B-\mu_B\end{bmatrix}^T\begin{bmatrix}\varSigma_{AA}&\varSigma_{AB}\\\varSigma_{BA}&\varSigma_{BB}\end{bmatrix}^{-1}\begin{bmatrix}x_A-\mu_A\\x_B-\mu_B\end{bmatrix}\right)
\end{align}
$$

其中的$Z'$依然是标准化常量，我们用它来存放那些不依靠$x_B$的项。注意到在这次的证明中，我们甚至不需要计算积分，因为分母上的积分不依靠$x_B$，所以分母上的积分都可以放进$Z'$（视为常量）。

#### 3.2.2 协方差矩阵分块

与上一个证明一样，使用矩阵$V$做参数：

$$
\begin{eqnarray}
p\left(x_B\mid x_A\right)&=\frac{1}{Z'}\exp\Bigg(-\frac{1}{2}&\begin{bmatrix}x_A-\mu_A\\x_B-\mu_B\end{bmatrix}^T\begin{bmatrix}\varSigma_{AA}&\varSigma_{AB}\\\varSigma_{BA}&\varSigma_{BB}\end{bmatrix}^{-1}\begin{bmatrix}x_A-\mu_A\\x_B-\mu_B\end{bmatrix}\Bigg)\\
p\left(x_A\right)&=\frac{1}{Z'}\exp\Bigg(-\bigg[
&\frac{1}{2}\left(x_A-\mu_A\right)^TV_{AA}\left(x_A-\mu_A\right)+\frac{1}{2}\left(x_A-\mu_A\right)^TV_{AB}\left(x_B-\mu_B\right)\\
&&+\frac{1}{2}\left(x_B-\mu_B\right)^TV_{BA}\left(x_A-\mu_A\right)+\frac{1}{2}\left(x_B-\mu_B\right)^TV_{BB}\left(x_B-\mu_B\right)
\bigg]\Bigg)
\end{eqnarray}
$$

#### 3.3.3 使用配方法

再上一个证明中，我们提到多变量情形中的配方法：

$$
\frac{1}{2}z^TAz+b^Tz+c=\frac{1}{2}\left(z+A^{-1}b\right)^TA\left(z+A^{-1}b\right)+c-\frac{1}{2}b^TA^{-1}b
$$

其中$A$是对称非奇异矩阵，类似上一个证明，令：

$$
\begin{align}
z&=x_B-\mu_B\\
A&=V_{BB}\\
b&=V_{BA}\left(x_A-\mu_A\right)\\
c&=\frac{1}{2}\left(x_A-\mu_A\right)^TV_{AA}\left(x_A-\mu_A\right)
\end{align}
$$

则可以将$p\left(x_B\mid x_A\right)$重写为：

$$
\begin{align}
p\left(x_B\mid x_A\right)=\frac{1}{Z'}\exp\Bigg(-\bigg[
&\frac{1}{2}\left(x_B-\mu_B+V_{BB}^{-1}V_{BA}\left(x_A-\mu_A\right)\right)^TV_{BB}\left(x_B-\mu_B+V_{BB}^{-1}V_{BA}\left(x_A-\mu_A\right)\right)\\
&+\frac{1}{2}\left(x_A-\mu_A\right)^TV_{AA}\left(x_A-\mu_A\right)-\frac{1}{2}\left(\left(x_A-\mu_A\right)^TV_{AB}V_{BB}^{-1}V_{BA}\left(x_A-\mu_A\right)\right)
\bigg]\Bigg)
\end{align}
$$

将那些不依靠$x_B$的项提出来放在标准化常量中，得到：

$$
p\left(x_B\mid x_A\right)=\frac{1}{Z''}\exp\left(\frac{1}{2}\left[\left(x_B-\mu_B+V_{BB}^{-1}V_{BA}\left(x_A-\mu_A\right)\right)^TV_{BB}\left(x_B-\mu_B+V_{BB}^{-1}V_{BA}\left(x_A-\mu_A\right)\right)\right]\right)
$$

#### 3.3.4 证明得到的结果是一个高斯分布

观察上一小节的最后，$p\left(x_B\mid x_A\right)$形为期望是$\mu_B+V_{BB}^{-1}V_{BA}\left(x_A-\mu_A\right)$协方差是$V_{BB}^{-1}$的高斯分布，像上一个证明中做的那样：

$$
\begin{align}
\begin{bmatrix}
\varSigma_{AA}&\varSigma_{AB}\\
\varSigma_{BA}&\varSigma_{BB}
\end{bmatrix}&=
\begin{bmatrix}
V_{AA}&V_{AB}\\
V_{BA}&V_{BB}
\end{bmatrix}^{-1}\\
&=\begin{bmatrix}
\left(V_{AA}-V_{AB}V_{BB}^{-1}V_{BA}\right)^{-1}&-\left(V_{AA}-V_{AB}V_{BB}^{-1}V_{BA}\right)^{-1}V_{AB}V_{BB}^{-1}\\
-V_{BB}^{-1}V_{BA}\left(V_{AA}-V_{AB}V_{BB}^{-1}V_{BA}\right)^{-1}&\left(V_{BB}-V_{BA}V_{AA}^{-1}V_{AB}\right)^{-1}
\end{bmatrix}
\end{align}
$$

于是可以得出：

$$
\mu_{B\mid A}=\mu_B-V_{BB}^{-1}V_{BA}\left(x_A-\mu_A\right)=\mu_B+\varSigma_{BA}\varSigma_{AA}^{-1}\left(x_A-\mu_A\right)
$$

反过来，我们也可以使用$\varSigma$矩阵表达$V$矩阵：

$$
\begin{bmatrix}
V_{AA}&V_{AB}\\
V_{BA}&V_{BB}
\end{bmatrix}=
\begin{bmatrix}
\left(\varSigma_{AA}-\varSigma_{AB}\varSigma_{BB}^{-1}\varSigma_{BA}\right)^{-1}&
-\left(\varSigma_{AA}-\varSigma_{AB}\varSigma_{BB}^{-1}\varSigma_{BA}\right)\varSigma_{AB}\varSigma_{BB}^{-1}\\
-\varSigma_{BB}^{-1}\varSigma_{BA}\left(\varSigma_{AA}-\varSigma_{AB}\varSigma_{BB}^{-1}\varSigma_{BA}\right)&
\left(\varSigma_{BB}-\varSigma_{BA}\varSigma_{AA}^{-1}\varSigma_{AB}\right)^{-1}
\end{bmatrix}
$$

于是得到：

$$
\varSigma_{B\mid A}=V_{BB}^{-1}=\varSigma_{BB}-\varSigma_{BA}\varSigma_{AA}^{-1}\varSigma_{AB}
$$

证毕。

## 4. 总结

在这篇笔记中，我们使用了多元高斯分布的几个简单特性（以及一些矩阵计算技巧）证明了多元高斯分布的几个闭包特性。多元高斯分布是一个非常有用的概率分布，部分因为这些闭包特性可以使得我们对多元高斯分布的大部分操作有解析解。从解析角度讲，在实际当中，涉及多元高斯分布的积分通常比较容易处理，因为我们可以利用已知的关于高斯分布的积分的结论，从而避免自己计算这些高斯分布的积分。

## 5. 练习

令$A\in\mathbb R^{n\times n}$为对称非奇异矩阵，$b\in\mathbb R^n$，证明：

$$
\int_{x\in\mathbb R^n}\exp\left(-\frac{1}{2}x^TAx-x^Tb-c\right)\mathrm dx=\frac{(2\pi)^{n/2}}{\lvert A\rvert^{1/2}\exp\left(c-b^TA^{-1}b\right)}
$$

（使用配方法易证。）