확률 프로그래밍 (Probabilistic Programming)
=====
그리고 베이지안 기법을 해커답게 알아보자! (원제 : Bayesian Methods for Hackers)
========

##### Version 0.1
Welcome to *Bayesian Methods for Hackers*. The full Github repository is available at [github/Probabilistic-Programming-and-Bayesian-Methods-for-Hackers](https://github.com/CamDavidsonPilon/Probabilistic-Programming-and-Bayesian-Methods-for-Hackers). The other chapters can be found on the project's [homepage](https://camdavidsonpilon.github.io/Probabilistic-Programming-and-Bayesian-Methods-for-Hackers/). We hope you enjoy the book, and we encourage any contributions!

Chapter 1
======
***

베이지안 추론 (Bayesian Inference)에 내재된 철학
------

> 여러분은 훌륭한 프로그래머입니다. 하지만 그래도 작성한 코드엔 버그가 있죠. 특히 알고리즘을 작성하는 것과 같은 어려운 작업을 하고나면, 대충 몇몇 상황에 대해서 코드를 테스트해봅니다. 패스했습니다. 좀 더 어려운 상황을 가지고 돌려보죠. 오 이것도 패스했습니다. *진짜 더 어려운 테스트*도 패스했어요! 조금씩 당신은 작성한 코드에 아마도 버그가 없는 것은 아닌가라고 생각하게 되는 겁니다...

만약 여러분이 위와 같이 조심스럽다면, 이미 당신은 베이지안처럼 생각하고 있는 것입니다! 몇몇 테스트 통과했다고 바로 환호성을 지르지 않았다면 말이에요. 베이지안 추론은 그냥 여러분이 상황에 대해 가진 믿음 (belief) 을 발견한 정보들에 따라 조금씩 수정해나가는 것을 말합니다. 베이지안은 결과에 대해 확신하지 않습니다. 대신, 좀 더 자신감을 가질 수 있겠지요. 바로 위 예제와 같이, 우리는 코드에 버그가 없다고 100% 확신하지 않습니다. 모든 가능한 상황에 대해서 테스트하지 않는 이상은요. 그런 일은 실제론 있을 수 없지요. 그러니 그 대신에 우리는 최대한 많은 상황에 대고 이를 테스트해보고, 만약 그게 잘 작동한다면 우리는 좀 더 *자신감*을 느끼는 겁니다. 확신은 하지 않구요. 베이지안 추론이 바로 이런 식으로 동작합니다: 우리는 우리가 생각하고 있는 것을 조금씩 수정하고, 절대 확신하지 않습니다. 혹시라도 다른 모든 상황들이 다 제거되지 않는다면 또 모르겠지만요.

### 베이지안의 마음가짐 (The Bayesian state of mind)

베이지안 추론은 *결과의 불확실성 (uncertainty)* 을 유지한다는 점에서 전통적인 통계 기법과는 다릅니다. 이건 잘못된 통계기법처럼 간주될 수 있어요. 통계학이란 멋대로인 것처럼 보이는 것에서 *확실성*을 구하는 것 아니었나요? 기왕 이렇게 된 김에, 우리 한번 베이지안처럼 생각해보기로 합시다.

베이지안 (Bayesian) 의 세계관에서 확률이란 *발생한 사건에 대한 신뢰성* (believability in an event) 을 구하는 것입니다. 즉, 우리가 발생한 event 에 대해 얼마나 자신감있게 알고 있었냐는거죠. 곧 이게 확률을 해석하는 자연스러운 방식이라는 것을 알게 될 것입니다.

보다 확실히 해보죠. 우리는 확률을 해석하는 좀 다른 방법을 고려하고 있습니다. *빈도주의자 (Frequentist)* 혹은 *전통적인 (classical)* 방법을 사용하는 통계학자들은 확률이란 오랜 시간 동안 발생하는 사건들의 횟수라고 해석합니다 (그래서 빈도주의자라 불리게 된 거죠). 예로서, 빈도주의자 철학에게 *비행기 사고*의 확률은 *아주 오랜 기간동안 비행기 사고 횟수*인거죠. 매우 많은 상황에서 이건 말이 됩니다. 하지만 아주 오랜 기간이 뭐냐는 질문에 답하기는 힘들죠. 생각해보세요: 대통령 선거 결과가 어떻게 나올지 확률 계산을 해보고자 합니다. 문제는 선거는 딱 한 번만 있어요! 빈도주의자들은 비슷한 상황들을 죄다 끌여들여 와서 이 문제를 해결해보려고 합니다. 발생의 횟수가 확률을 결정하니까요.

반면에 베이지안들은 보다 더 직관적으로 접근합니다. 베이지안은 확률을 주어진 사건이 발생하는 것에 대한 자신감 (confidence) 이라고 생각하거나 얼마나 그 사건을 믿는지에 대한 *믿음 (belief)*을 수치화한 것이라 생각합니다. 확률이란 단순히 의견을 간략화한 것이라 말할 수 있겠습니다. 어떤 이는 자신감을 0으로 수치화시킬 수 있겠지요. 만약 주어진 event 가 발생할거라는 확신이 전혀 없다면요. 반대로 confidence 를 1로 정한다는 것은 그 사람이 해당 사건이 발생할 것이라는 것에 아주 확신한다는 이야기입니다. 해당 사건이 발생할 것이라는 확실성은 0부터 1까지의 숫자입니다. 이러한 정의는 비행기 사고 예제에도 잘 들어맞습니다. 빈도주의자들의 결과를 그대로 가져와서 개개인들이 자신의 관점으로 삼을 수 있습니다. 마찬가지로, 확률에 대한 그러한 정의도 관점에 들어맞습니다. 그렇다면 대통령 선거에 대한 확률 (혹은 관점) 에 대해서 이야기할 때는 어떨까요? 후보 '1번' 이 당선될 확률에 대해 얼마나 확실하다고 생각하십니까?

위의 단락을 다시 살펴보신다면, 저는 관점 (확률) 은 *개인*들이 측정하는 것이라고 이야기하였습니다. 이건 굉장히 흥미로운 접근입니다. 왜냐하면 개별 개인들은 서로 다른 믿음을 가지게 되거든요. 다시 말하자면, 이건 자연스러운 것이기도 합니다: 서로 다른 개인들이 특정 사건의 발생 여부에 대한 서로 다른 믿음을 가지고 있는 거에요. 왜냐하면 그들은 세계에 대한 서로 다른 *정보*들을 가지고 있으니까요. 서로 다른 정보들의 존재란 그 누구가 틀렸다는 이야기가 아니에요. 개인이 가진 믿음과 그 확률에 대한 관계를 설명하는 예제를 소개하도록 하겠습니다.

- 동전 던지기를 할 때 어떤 결과가 나올지 예측하기. 물론 이상한 동전을 쓰진 않기로 합니다. 앞면이 나올 확률은 1/2 가 되겠죠. 그리고 제가 한번 동전을 봅니다. 그 결과 저는 앞면 혹은 뒷면이 나올 확률을 모두 1.0 으로 잡게 되었어요. 하지만 그게 당신도 반드시 그렇게 생각한다는 의미는 아니죠. *당신*은 앞면이 나올 확률을 얼마라고 믿고 있나요? 뭣보다도 제가 뭐라 믿건 간에 동전이 실제로 어떤 면을 보여줄지는 아무도 몰라요. 그러므로 우리는 사건에 대해 서로 다른 확률을 가지고 있게 되는 겁니다.

-  코드는 버그가 있을 수도 있고 없을 수도 있어요. 하지만 우리는 뭐가 사실인지 아무도 모르죠. 그래도 우리는 버그가 얼마나 될지에 대해서 각각 얼마라도 믿고 있는 수치가 있습니다.

-  어떤 환자가 $x$, $y$ 그리고 $z$ 라는 증상을 보여요. 굉장히 많은 수의 질병들이 그런 증상을 동반하죠. 전부 다 일 수도 있겠지만, 딱 하나의 질병만이 존재한다고 칩시다. 의사라면 어떤 질병이 제일 그럴듯한지에 대해 각 질병들마다 수치를 매길 수 있어요. 그런데 어떤 의사는 그 정도가 약간 다를 수도 있지요.

자신의 믿음을 중시하는 이러한 철학은 사람들에게 자연스럽습니다. 세상을 살아가면서 우리는 이런 방식으로 살아갑니다. 오직 부분적인 진실만을 볼 수 있지만, 그게 우리가 각각이 세상을 바라보는 관점을 가지기에 부족하다는 말은 아니에요. 이거와 달리 우리는 빈도주의자들처럼 생각하는 법을 *배워야만* 합니다.

전통적인 확률 용어로 풀이해보겠습니다. 사건 $A$에 대한 우리의 관점을 $P(A)$ 라 표기합니다. 그리고 이를 사건을 보기 전의 자신의 관점 혹은 *사전확률 (prior probability)* 이라 말합니다.

존 메이날드 케네스 (John Maynard Keynes)라는 대단한 경제학자는
" 난 사실들이 바뀌면 내 마음도 바꾼다. " 라고 합니다. 이 격언은 베이지안이 증거들을 보면서 자신의 관점을 계속 바꿔나가는 것과 흡사한 방식을 보여주지요. 심지어 — 특히 — 자신의 초창기 관점과 상반되는 증거들이 나타날 때, 변경되는 쪽은 자신의 관점이지 증거가 아닙니다. 이렇게 변경된 관점을 $P(A |X )$ 라고 합니다. $X$라는 증거를 보고 바뀐 확률 $A$ 라고 설명할 수 있겠네요. 우리는 이를 사건이 발생한 이후의 자신의 관점 혹은 *사후 확률 (posterior probability)* 이라 합니다. Prior 와 대비되는 개념이지요. 위 예제들에선 사전 확률만 소개했는데 증거 $X$를 보고 난 이후의 사후 확률의 예는 아래와 같습니다.

1\. $P(A): \;\;$ 동전의 앞면이 나올 확률은 50 퍼센트라는 게 내 관점이다. $P(A | X):\;\;$ 동전을 던져보니 앞면이 나왔다. 난 이 증거 $X$ 에 굉장히 호들갑을 떨었고 그에 따라 앞면에 1.0 그리고 뒷면에 0.0 이라고 관점을 바꾸었다.

2\.   $P(A): \;\;$  복잡스럽기 그지 없는 코드는 버그가 있을 법하다는 게 내 관점이다. $P(A | X): \;\;$ 코드는 $X$ 테스트 케이스를 통과하였다; 버그가 있을 수는 있겠지만 의외로 없는 것은 아닐까라고 관점을 바꾸었다.

3\.  $P(A):\;\;$ 환자는 어떤 종류건 얼마나 되건 간에 질병을 가지고 있을거라는 게 내 관점이다. $P(A | X):\;\;$ 혈액 검사를 해보니 증거 $X$가 나왔다. 그에 따라서 어떤 질병들은 가지고 있지 않을 거라는게 내 관점이다.

위의 예에서 우리는 증거 $X$ 하나를 봤다고 해서 사전 확률을 아예 폐기하지는 않았습니다. 하지만 *Prior 를 바꾸어서* 좀 더 증거에 맞아들어 갈 수 있도록 했지요.

사건에 대한 사전 확률의 불확실성에 대해서 소개함으로서, 우리는 우리의 관점이 매우 틀릴 수도 있다는 것을 인정하는 것입니다. 데이터를 좀 더 얻은 다음 우리는 관점을 바꿔나갈 수 있고 결국 *좀 덜 틀릴* 수 있습니다. 이러한 관점은 일반적인 예측들이 *보다 더 맞도록* 접근하는 것과는 다른 방식이라 할 수 있습니다.

### 베이지안 추론을 실제로 해보자

 만약 빈도주의 추론과 베이지안 추론이 프로그램의 함수이고 통계문제를 인풋값으로 넣는다면 각각의 함수들은 서로 다른 값을 리턴할 것입니다. 빈도주의 추론 함수는 숫자를 리턴할거구요 베이지안 추론 함수는 *확률*을 리턴할 거에요.

테스팅 문제로 예를 들자면, "내 코드가 $X$ 테스트를 통과했는데 그럼 내 코드는 문제 없는 거임?" 이라는 인풋값으로 빈도주의 추론 함수를 돌린다고 생각해볼께요. 그거 리턴값이 $YES$가 나온다고 치구요. 그런데 그걸 베이지안 추론 함수로 돌리면 굉장히 다른 값이 나올거에요. 가령:

>    *YES*, with probability 0.8; *NO*, with probability 0.2

이건 빈도주의 추론이 리턴하는 값과는 완전히 달라요. 또한 베이지안 추론의 인풋값은 다음과 같은 걸 하나 더 추가할 수 있어요. *"Often my code has bugs"*. 이런 인풋값을 *prior* 라 합니다. 이를 받아들임으로서 우리는 베이지안 추론 함수에다가 해당 상황에 대한 우리의 관점을 알려줄 수 있어요. 엄밀히 말하자면 이런 인풋값은 반드시 넣어야 하는 값은 아니지만, 그걸 빼버리면 다른 결과가 나오게 되버리는거죠.

#### 증거들을 집어넣기

증거들을 가져오면 올 수록, 우리의 prior belief 는 새로운 증거들에 의해 *씻겨나가 버립니다*. 놀랄 일은 아니죠. 가령, 우리의 prior belief 가 좀 이상했을 수도 있어요. 가령 "난 내일 태양이 폭발한다고 생각해" 뭐 이런거처럼요. 매일 우리가 틀렸다는 것을 깨닫게 됩니다. 최소한 우리의 belief 를 좀 더 낫도록 만들어줘요.
베이지안 추론은 이런 belief 를 수정해줍니다.

$N$ 이 우리가 수집했던 데이터들의 수라고 합시다. *무한한* 수의 데이터들을 모으면서 $N \rightarrow \infty$ 되게 되지요. 그러면서 베이지안 결과는 (종종) Frequentist들의 결과들이랑 비슷하게 됩니다. 많은 수의 $N$이 있다면야 통계적 추론 결과는 객관적이 되기 마련입니다. 그런데 $N$의 수가 적다면, 뭐든간에 의미있는 값이 *나오지 않게 됩니다*: Frequentist 식의 결과는 보다 더 큰 분산을 가지게 되고, 보다 더 큰 confidence intervals 를 가지게 되지요. 베이지안 분석은 보다 더 낫습니다. Prior 와 그 결과값들을 통해서 보다 더 작은 $N$ 데이터들에 대해 우리는 *불확실성을 보전하면서* 결과들을 낼 수 있게 되기 때문이지요.

큰 수 $N$ 에 대해서 두 기법은 아무런 차이가 없다고 생각할지도 모르겠습니다. 비슷한 결과가 나오긴 해요. 그리고 그들은 좀 더 계산이 편한 빈도주의 이론을 좀 더 선호할 지도 모릅니다. 그렇게 생각하는 사람은 그런 결정을 내리기 전에 앤드류 겔만 [1] 의 아래와 같은 말을 생각하는 것도 좋습니다.

> 샘플의 크기가 커지는 경우란 절대 없다. 만약 $N$ 이 충분한 결정을 내리기 너무 작으면, 더 많은 데이터를 모아야 한다 (혹은 더 많은 assumptions). 하지만 $N$ 이 한번 "엄청 많아지면", 당신은 해당 데이터를 나누게 된다. 왜냐하면 더 배우기를 원하거든. 가령 의견투표 같은데에는 충분할 정도로 많이 모은다. 그리고 남녀별, 지역별, 나이별 등으로 또 나누게 되는 것이다. 그러므로 $N$은 절대 충분하지 않으며 만약 충분하다고 해도 나눌 것이기 때문에 충분하다고 할 수 없다.

### 그럼 빈도주의 이론이 정확하지 않다는 이야기인가요?

**아뇨.**

빈도주의 이론은 쓸만하고 굉장히 많은 곳에서 state-of-art 입니다. 도구들은 보통 least squares linear regression, LASSO regression, and expectation-maximization algorithms 입니다. 다들 엄청 파워풀하고 빠르죠. 베이즈 기법은 그걸 보완합니다. 그들이 하지 못하는 일을 하거나 보다 더 유연한 모델링을 할 수 있게 도와주지요.

#### *빅데이터* 에 대해서
빅데이더으로 예측 작업을 하면 꽤 단순한 알고리즘으로 수행합니다 [2][4]. 빅데이터를 가지고 예측을 했을때 어렵다는 이야기는 알고리즘이 어려워서가 아니에요, 계산 작업 자체가 어려운거죠. 빅데이터의 크기랑 그거 실행하는거 자체가 힘들거든요. (겔만이 말했던 데로 "진짜 빅데이터가 필요하긴 한가?"라고 자문해봅시다,)

더 어려운 분석 작업은 *보통 크기 정도의 데이터*로 수행되며, 뭣보다 빡친 것은 *진짜 작은 크기의 데이터*를 사용하는거에요. 겔만이 말했던 것처럼, 만약 우리가 하는 작업이 바로 풀기에 *거대하다면*, 우리는 *딱히 크다고는 할 수 없는* 데이터셋에 좀 더 집중해야 겠죠.

### 우리가 사용할 베이지안 프레임워크

belief 는 베이지안에 의해 확률로 해석될 수 있고 우리는 그것에 관심이 있지요. 우리는 사건 $A$ 에 대해서 *prior* belief 를 가집니다. 기존에 알고 있었던 정보들을 가지고 형성하는 것이죠, 가령 테스트 돌리기 전의 버그에 대한 우리의 prior belief 같은거요.

그리고, 우리가 evidence 를 관찰합니다. 버그 코드 예제의 경우에는: 만약 $X$ 테스트를 통과했다면, 우리의 belief 를 이 결과에 맞출거에요. 이렇게 수정된 belief 를 *posterior* 확률이라고 해요. 아래와 같은 수식으로 belief 를 바꿀 수 있어요. 이를 베이즈 이론이라고 합니다. 이 이론을 발견한 토마스 베이즈를 따서 붙인 이름이에요:

\begin{align}
 P( A | X ) = & \frac{ P(X | A) P(A) } {P(X) } \\\\[5pt]
& \propto P(X | A) P(A)\;\; (\propto \text{is proportional to } )
\end{align}

이 수식은 베이지안 추론에만 쓰이는 것이 아닙니다: 베이지안 추론 말고도 여러 군데에서 사용되는 수식이에요. 베이지안 추론이 prior probabilities $P(A)$ 를 posterior probabilities $P(A | X )$ 로 바꾸기 위해 이걸 바로 사용하는 것은 드문 일이에요. 수식을 바로 사용하는 건 드문 일이에요.

##### Example: 필수 교양으로서의 동전 던지기 예제

통계학 교재라면 동전 던지기 예제가 있어야 제 맛이죠. 까짓것 여기서 한번 해봅시다. 동전을 던질때 앞면이 나올 확률을 당신이 모른다고 칩시다 (답은 50%에요!). 아직은 모르지만 true underlying ratio 가 있다는 것은 알고 있습니다. 이걸 $p$ 라고 불러요. 그런데 prior 를 뭘로 정해야 할지도 모릅니다.

그럼 동전을 직접 던지는 걸로 한번 시작해봅시다. 그리고 결과들을 기록합니다: $H$ or $T$ 겠죠. 결과들을 알게 될 때마다 어떻게 우리의 추론이 변해갈까요? 좀 더 자세히 설명하자면, posterior probabilities 는 우리가 데이터를 조금가지고 있을 때랑 많이 가지고 있을 때 어떤 차이가 날까요?

Below we plot a sequence of updating posterior probabilities as we observe increasing amounts of data (coin flips).

```python
"""
The book uses a custom matplotlibrc file, which provides the unique styles for
matplotlib plots. If executing this book, and you wish to use the book's
styling, provided are two options:
    1. Overwrite your own matplotlibrc file with the rc-file provided in the
       book's styles/ dir. See http://matplotlib.org/users/customizing.html
    2. Also in the styles is  bmh_matplotlibrc.json file. This can be used to
       update the styles in only this notebook. Try running the following code:

        import json, matplotlib
        s = json.load( open("../styles/bmh_matplotlibrc.json") )
        matplotlib.rcParams.update(s)

"""

# The code below can be passed over, as it is currently not important, plus it
# uses advanced topics we have not covered yet. LOOK AT PICTURE, MICHAEL!
%matplotlib inline
from IPython.core.pylabtools import figsize
import numpy as np
from matplotlib import pyplot as plt
figsize(11, 9)

import scipy.stats as stats

dist = stats.beta # beta-distribution is flexible
n_trials = [0, 1, 2, 3, 4, 5, 8, 15, 50, 500]
data = stats.bernoulli.rvs(0.5, size=n_trials[-1])
x = np.linspace(0, 1, 100)

# For the already prepared, I'm using Binomial's conj. prior.
for k, N in enumerate(n_trials):
    sx = plt.subplot(len(n_trials) / 2, 2, k + 1)
    plt.xlabel("$p$, probability of heads") \
        if k in [0, len(n_trials) - 1] else None
    plt.setp(sx.get_yticklabels(), visible=False) # y-axis
    heads = data[:N].sum()
    y = dist.pdf(x, 1 + heads, 1 + N - heads) #
    plt.plot(x, y, label="observe %d tosses,\n %d heads" % (N, heads))
    plt.fill_between(x, 0, y, color="#348ABD", alpha=0.4)
    plt.vlines(0.5, 0, 4, color="k", linestyles="--", lw=1)

    leg = plt.legend()
    leg.get_frame().set_alpha(0.4)
    plt.autoscale(tight=True)

plt.suptitle("Bayesian updating of posterior probabilities",
             y=1.02,
             fontsize=14)

plt.tight_layout()
```

위의 커브들은 The posterior probabilities 를 나타냅니다. 해당 커브의 너비는 추측 결과의 불확실성을 나타내지요. 위 플롯들이 보여주듯이, 데이터를 적용할 수록 posterior probabilities 도 움직이기 시작합니다. 시작이 지날 수록 우리는 보다 더 많은 데이터를 관찰하게 되고, 실제 결과인 $p=0.5$ 에 가깝게되죠. (marked by a dashed line).

플롯들이 언제나 $0.5$ 를 최대값으로 가리키고 있지 않다는 걸 주의하세요. 확률계산 프로그래밍에서는 그럴 필요도 없어고, 애초에 prior 에 아무런 값을 설정하지도 않았기에 모든 값을 동등한 확률을 설정했거든요. 데이터가 몇 개 없다면 굉장히 사실에서 먼 값을 가리키게 될 거에요. (아무 것도 모르는 상태에서 과연 얼마나 이걸 확신할 수 있겠어요.) 데이터를 좀 더 모을 수록, $p=0.5$ 에 가까운 값이 되지요.

다음 예제는 베이지안 추론의 단순한 예를 수식을 사용하여 보도록 하겠습니다.

##### 예제: 버그인가 피쳐인가?

$A$ 는 우리의 코드에 **아무런 버그도 없을** 확률이다. $X$ 는 해당 코드가 모든 테스트를 통과했다는 이벤트이다. prior 를 해당 코드에 아무런 버그도 없을 확률이다.  i.e. $P(A) = p$.

우리는 $P(A|X)$ 에 관심이 있다, i.e. 디버깅 테스트 $X$ 가 있을 때 버그가 없을 확률. 위 수식을 이용해서 다른 값들도 구할 수 있다.

$P(X | A)$ 란 뭘까?, i.e., 아무런 버그가 없다고 *쳤을때* 테스트 케이스 $X$ 를 다 통과할 가능성. 그건 만약 다 통과한다면 1 이라고 해도 될 것이다.

$P(X)$ 는 조금 더 깨닫기 어렵다: 이벤트 $X$ 는 두 상황으로 나눌 수 있다. 버그가 *있는데도* $X$ 다 통과하는 상황 (denoted $\sim A\;$, spoken *not $A$*) 이랑, 버그가 *없기 때문에* $X$ 다 통과하는 상황 말이다. $P(X)$ 는 다음과 같이 표현된다:

\begin{align}
P(X ) & = P(X \text{ and } A) + P(X \text{ and } \sim A) \\\\[5pt]
 & = P(X|A)P(A) + P(X | \sim A)P(\sim A)\\\\[5pt]
& = P(X|A)p + P(X | \sim A)(1-p)
\end{align}

We have already computed $P(X|A)$ above. On the other hand, $P(X | \sim A)$ is subjective: our code can pass tests but still have a bug in it, though the probability there is a bug present is reduced. Note this is dependent on the number of tests performed, the degree of complication in the tests, etc. Let's be conservative and assign $P(X|\sim A) = 0.5$. Then

\begin{align}
P(A | X) & = \frac{1\cdot p}{ 1\cdot p +0.5 (1-p) } \\\\
& = \frac{ 2 p}{1+p}
\end{align}
This is the posterior probability. What does it look like as a function of our prior, $p \in [0,1]$?

```python
figsize(12.5, 4)
p = np.linspace(0, 1, 50)
plt.plot(p, 2 * p / (1 + p), color="#348ABD", lw=3)
# plt.fill_between(p, 2*p/(1+p), alpha=.5, facecolor=["#A60628"])
plt.scatter(0.2, 2 * (0.2) / 1.2, s=140, c="#348ABD")
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.xlabel("Prior, $P(A) = p$")
plt.ylabel("Posterior, $P(A|X)$, with $P(A) = p$")
plt.title("Are there bugs in my code?")
```

We can see the biggest gains if we observe the $X$ tests passed when the prior probability, $p$, is low. Let's settle on a specific value for the prior. I'm a strong programmer (I think), so I'm going to give myself a realistic prior of 0.20, that is, there is a 20% chance that I write code bug-free. To be more realistic, this prior should be a function of how complicated and large the code is, but let's pin it at 0.20. Then my updated belief that my code is bug-free is 0.33.

Recall that the prior is a probability: $p$ is the prior probability that there *are no bugs*, so $1-p$ is the prior probability that there *are bugs*.

Similarly, our posterior is also a probability, with $P(A | X)$ the probability there is no bug *given we saw all tests pass*, hence $1-P(A|X)$ is the probability there is a bug *given all tests passed*. What does our posterior probability look like? Below is a chart of both the prior and the posterior probabilities.

```python
figsize(12.5, 4)
colours = ["#348ABD", "#A60628"]

prior = [0.20, 0.80]
posterior = [1. / 3, 2. / 3]
plt.bar([0, .7], prior, alpha=0.70, width=0.25,
        color=colours[0], label="prior distribution",
        lw="3", edgecolor=colours[0])

plt.bar([0 + 0.25, .7 + 0.25], posterior, alpha=0.7,
        width=0.25, color=colours[1],
        label="posterior distribution",
        lw="3", edgecolor=colours[1])

plt.xticks([0.20, .95], ["Bugs Absent", "Bugs Present"])
plt.title("Prior and Posterior probability of bugs present")
plt.ylabel("Probability")
plt.legend(loc="upper left");
```

Notice that after we observed $X$ occur, the probability of bugs being absent increased. By increasing the number of tests, we can approach confidence (probability 1) that there are no bugs present.

This was a very simple example of Bayesian inference and Bayes rule. Unfortunately, the mathematics necessary to perform more complicated Bayesian inference only becomes more difficult, except for artificially constructed cases. We will later see that this type of mathematical analysis is actually unnecessary. First we must broaden our modeling tools. The next section deals with *probability distributions*. If you are already familiar, feel free to skip (or at least skim), but for the less familiar the next section is essential.

_______

Probability Distributions
------

**Let's quickly recall what a probability distribution is:** Let $Z$ be some random variable. 해당 변수 $Z$ 와 *probability distribution function* 이 연관된다. pdf 는 확률에다가 서로 다른 크기인 outcome $Z$ 를 할당해준다. 생긴 모양은 커브인데, outcome 은 커브의 크기와 proportional 하다. 해당 챕터의 첫 번째 피겨를 보라.

확률 변수를 세 가지의 서로 다른 분류가 있다:

-   **$Z$ is discrete**: 이건 결과들을 하나의 특정 배열로 생각한다. 가령 인구라거나, 영화 평점이라거나 아니면 투표라거나 뭐 이런게 discrete random variables 이다. Discrete random variables become more clear when we contrast them with...

-   **$Z$ is continuous**: 이건 임의적인 변수로 생각한다. 그 와 동시에 하나의 점이기도 하다. 가령 온도라거나 속도라거나 시간 색깔 뭐 이런거이다. 왜냐하면 점차적으로 증감하고 훨씬 더 정확한 수이기 떄문이다.

- **$Z$ is mixed**: Mixed random variables assign probabilities to both discrete and continuous random variables, i.e. it is a combination of the above two categories.

### Discrete Case
만약 $Z$ 가 이산이면, 그 분포는 *probability mass function* 라 부른다, which measures the probability $Z$ takes on the value $k$, denoted $P(Z=k)$. Note that the probability mass function completely describes the random variable $Z$, that is, if we know the mass function, we know how $Z$ should behave. 유명한 probability mass functions 들이 있는데 꽤 자주 보이는 놈들이다: 필요할 때 마다 그들에 대해서 소개할텐데, 일단 진짜 많이 쓰이면서도 유용한 probability mass function 놈부터 먼저 소개하겠다. We say $Z$ is *Poisson*-distributed if:

$$P(Z = k) =\frac{ \lambda^k e^{-\lambda} }{k!}, \; \; k=0,1,2, \dots $$

$\lambda$ 는 분포의 parameter 이다, 그리고 이 것이 분포의 모양을 결정한다. For the Poisson distribution, $\lambda$ can be any positive number. By increasing $\lambda$, we add more probability to larger values, and conversely by decreasing $\lambda$ we add more probability to smaller values. One can describe $\lambda$ as the *intensity* of the Poisson distribution.

$\lambda$ 와는 다르게, which can be any positive number, 위 식의 숫자 $k$ 는 반드시 non-negative integer 여야 한다., i.e., $k$ must take on values 0,1,2, and so on. This is very important, because if you wanted to model a population you could not make sense of populations with 4.25 or 5.612 members.

If a random variable $Z$ has a Poisson mass distribution, we denote this by writing

$$Z \sim \text{Poi}(\lambda) $$

One useful property of the Poisson distribution is that its expected value is equal to its parameter, i.e.:

$$E\large[ \;Z\; | \; \lambda \;\large] = \lambda $$

We will use this property often, so it's useful to remember. Below, we plot the probability mass distribution for different $\lambda$ values. The first thing to notice is that by increasing $\lambda$, we add more probability of larger values occurring. Second, notice that although the graph ends at 15, the distributions do not. They assign positive probability to every non-negative integer.

```python
figsize(12.5, 4)

import scipy.stats as stats
a = np.arange(16)
poi = stats.poisson
lambda_ = [1.5, 4.25]
colours = ["#348ABD", "#A60628"]

plt.bar(a, poi.pmf(a, lambda_[0]), color=colours[0],
        label="$\lambda = %.1f$" % lambda_[0], alpha=0.60,
        edgecolor=colours[0], lw="3")

plt.bar(a, poi.pmf(a, lambda_[1]), color=colours[1],
        label="$\lambda = %.1f$" % lambda_[1], alpha=0.60,
        edgecolor=colours[1], lw="3")

plt.xticks(a + 0.4, a)
plt.legend()
plt.ylabel("probability of $k$")
plt.xlabel("$k$")
plt.title("Probability mass function of a Poisson random variable; differing \
$\lambda$ values")
```

###연속 상황에서
확률 mass 함수 대신, 연속 확률 변수는 *확률 density 함수* 를 가지고 있습니다. 불필요한 것처럼 들릴 수도 있겠지만, 서로는 굉장히 다르게 동작합니다. 가령 *exponential density*를 가지는 연속 확률 변수의 경우처럼요. 이 경우, exponential random variable를 위한 density function은 다음과 같습니다:

$$f_Z(z | \lambda) = \lambda e^{-\lambda z }, \;\; z\ge 0$$

푸아송 확률변수와 같이, exponential random variable 는 non-negative value 들만 가집니다. 하지만 Poisson variable 과는 다르게, the exponential 은 그 *어떤* non-negative values 도 가질 수 있습니다, non-integral values such as 4.25 or 5.612401 와 같은 것도 말이죠. 이러한 성질 때문에 이 함수는 a poor choice for count data, which must be an integer, but a great choice for time data, temperature data (measured in Kelvins, of course), or any other precise *and positive* variable. The graph below shows two probability density functions with different $\lambda$ values.

When a random variable $Z$ has an exponential distribution with parameter $\lambda$, we say *$Z$ is exponential* and write

$$Z \sim \text{Exp}(\lambda)$$

Given a specific $\lambda$, the expected value of an exponential random variable is equal to the inverse of $\lambda$, that is:

$$E[\; Z \;|\; \lambda \;] = \frac{1}{\lambda}$$

```python
a = np.linspace(0, 4, 100)
expo = stats.expon
lambda_ = [0.5, 1]

for l, c in zip(lambda_, colours):
    plt.plot(a, expo.pdf(a, scale=1. / l), lw=3,
             color=c, label="$\lambda = %.1f$" % l)
    plt.fill_between(a, expo.pdf(a, scale=1. / l), color=c, alpha=.33)

plt.legend()
plt.ylabel("PDF at $z$")
plt.xlabel("$z$")
plt.ylim(0, 1.2)
plt.title("Probability density function of an Exponential random variable;\
 differing $\lambda$");
```

### But what is $\lambda \;$?

**This question is what motivates statistics**. In the real world, $\lambda$ is hidden from us. We see only $Z$, and must go backwards to try and determine $\lambda$. The problem is difficult because there is no one-to-one mapping from $Z$ to $\lambda$. Many different methods have been created to solve the problem of estimating $\lambda$, but since $\lambda$ is never actually observed, no one can say for certain which method is best!

Bayesian inference is concerned with *beliefs* about what $\lambda$ might be. Rather than try to guess $\lambda$ exactly, we can only talk about what $\lambda$ is likely to be by assigning a probability distribution to $\lambda$.

This might seem odd at first. After all, $\lambda$ is fixed; it is not (necessarily) random! How can we assign probabilities to values of a non-random variable? Ah, we have fallen for our old, frequentist way of thinking. Recall that under Bayesian philosophy, we *can* assign probabilities if we interpret them as beliefs. And it is entirely acceptable to have *beliefs* about the parameter $\lambda$.

##### 예제: 폰 문자 데이터로 사용자 행동 추론하기

좀 더 재밌는 걸 모델로 만들어보자. 사용자가 얼마나 폰으로 문자를 보내고 받는지 말이다.:

>  You are given a series of daily text-message counts from a user of your system. The data, plotted over time, appears in the chart below. You are curious to know if the user's text-messaging habits have changed over time, either gradually or suddenly. How can you model this? (This is in fact my own text-message data. Judge my popularity as you wish.)

```python
figsize(12.5, 3.5)
count_data = np.loadtxt("data/txtdata.csv")
n_count_data = len(count_data)
plt.bar(np.arange(n_count_data), count_data, color="#348ABD")
plt.xlabel("Time (days)")
plt.ylabel("count of text-msgs received")
plt.title("Did the user's texting habits change over time?")
plt.xlim(0, n_count_data);
```

모델링을 본격적으로 해보기 전에, 지금까지 봤던 chart 중에서 뭘 발견할 수 있었는지 보라. 해당 기간 동안 행동 양식이 변했다고 말할 수 있는가?

그럼 어떻게 이걸 모델링 할 수 있겠는가? 글쎄, 이미 우리가 봤듯이, a Poisson random variable 은 굉장히 적합한 모델이다. 특히 이렇게 데이터를 *세는 데* 있어서 말이다. 날짜 $i$에 발생된 text-message count 를 $C_i$ 라고 하자,

$$ C_i \sim \text{Poisson}(\lambda)  $$

우리는 $\lambda$ parameter 를 뭘로 정해야 할지 전혀 모른다. 위의 chart 를 보면, the rate 는 observation 기간 중에 좀 더 높아진다. 이건 해당 기간 동안 $\lambda$ 가 올라갔다는 소리이기도 하다. (Recall that a higher value of $\lambda$ assigns more probability to larger outcomes. That is, there is a higher probability of many text messages having been sent on a given day.)

이러한 observation 를 어떻게 수식으로 표현할 수 있을까? observation 기간 중 어떤 특별한 날이 있고 (그걸 $\tau$ 라고 부르기로 하자), 그 날을 기점으로 $\lambda$ 가 갑자기 치솟았다고 하자. 그럼 우리는 두 개의 $\lambda$ 를 가지게 된 셈이 된다: 하나는 $\tau$ 전이고, 다른 하나는 그 이후 모든 observation 기간. 이러한 갑작스러운 변화를 *switchpoint* 라고 한다:

$$
\lambda =
\begin{cases}
\lambda_1  & \text{if } t \lt \tau \cr
\lambda_2 & \text{if } t \ge \tau
\end{cases}
$$

만약에 실제로 그런 변화 따윈 없었다면 $\lambda_1 = \lambda_2$ 이 된다. 그러면 $\lambda$s posterior distributions 도 동일해 보여야 한다.

미지의 $\lambda$s 를 추론해내는 것이 우리가 구하고자 하는 것이다. Bayesian inference 를 사용하기 위해서는, 우리는 prior probabilities 를 정하고 시작해야 한다. 그러면 $\lambda_1$ and $\lambda_2$ 에는 뭘 각각 정하는게 좋을까? $\lambda$ 는 그 어떤 positive 숫자도 된다는 걸 기억하라. 이전에 봤듯이 *exponential* distribution 은 양수에 대한 continuous density function 를 제공한다. 그러니 이걸 사용하면 $\lambda_i$ 를 모델링할 수 있다. 하지만 exponential distribution 도 그냥 동작하는게 아니라 우리가 parameter 를 설정해줘야 한다. 그러므로 뭔가 정하긴 정해야 하고 이 파라메터를 $\alpha$ 라고 하자.

\begin{align}
&\lambda_1 \sim \text{Exp}( \alpha ) \\\
&\lambda_2 \sim \text{Exp}( \alpha )
\end{align}

$\alpha$ 는 *hyper-parameter* or *parent variable* 라고 한다. 이는 다른 parameters 에게 영향을 주는 parameter 를 말한다. $\alpha$ 자체는 모델에 그렇게 심대한 영향을 끼치진 않는다, 그러므로 좀 편하게 정해도 된다.

$\alpha$ is called a *hyper-parameter* or *parent variable*. In literal terms, it is a parameter that influences other parameters. Our initial guess at $\alpha$ does not influence the model too strongly, so we have some flexibility in our choice.  A good rule of thumb is to set the exponential parameter equal to the inverse of the average of the count data. Since we're modeling $\lambda$ using an exponential distribution, we can use the expected value identity shown earlier to get:

$$\frac{1}{N}\sum_{i=0}^N \;C_i \approx E[\; \lambda \; |\; \alpha ] = \frac{1}{\alpha}$$

An alternative, and something I encourage the reader to try, would be to have two priors: one for each $\lambda_i$. Creating two exponential distributions with different $\alpha$ values reflects our prior belief that the rate changed at some point during the observations.

그렇다면 $\tau$ 는 언제일까? 데이터에 들어있는 노이즈들 때문에, 언제가 $\tau$ 일지 prior 를 구하기는 쉽지 않다. 대신에, 우리는 모든 날에 대해서 *uniform prior belief* 를 설정할 수 있다. This is equivalent to saying

\begin{align}
& \tau \sim \text{DiscreteUniform(1,70) }\\\\
& \Rightarrow P( \tau = k ) = \frac{1}{70}
\end{align}

So after all this, what does our overall prior distribution for the unknown variables look like? Frankly, *it doesn't matter*. What we should understand is that it's an ugly, complicated mess involving symbols only a mathematician could love. And things will only get uglier the more complicated our models become. Regardless, all we really care about is the posterior distribution.

We next turn to PyMC, a Python library for performing Bayesian analysis that is undaunted by the mathematical monster we have created.

Introducing our first hammer: PyMC
-----

PyMC is a Python library for programming Bayesian analysis [3]. It is a fast, well-maintained library. The only unfortunate part is that its documentation is lacking in certain areas, especially those that bridge the gap between beginner and hacker. One of this book's main goals is to solve that problem, and also to demonstrate why PyMC is so cool.

We will model the problem above using PyMC. This type of programming is called *probabilistic programming*, an unfortunate misnomer that invokes ideas of randomly-generated code and has likely confused and frightened users away from this field. The code is not random; it is probabilistic in the sense that we create probability models using programming variables as the model's components. Model components are first-class primitives within the PyMC framework.

B. Cronin [5] has a very motivating description of probabilistic programming:

>   Another way of thinking about this: unlike a traditional program, which only runs in the forward directions, a probabilistic program is run in both the forward and backward direction. It runs forward to compute the consequences of the assumptions it contains about the world (i.e., the model space it represents), but it also runs backward from the data to constrain the possible explanations. In practice, many probabilistic programming systems will cleverly interleave these forward and backward operations to efficiently home in on the best explanations.

Because of the confusion engendered by the term *probabilistic programming*, I'll refrain from using it. Instead, I'll simply say *programming*, since that's what it really is.

PyMC code is easy to read. The only novel thing should be the syntax, and I will interrupt the code to explain individual sections. Simply remember that we are representing the model's components ($\tau, \lambda_1, \lambda_2$ ) as variables:

```python
import pymc as pm

alpha = 1.0 / count_data.mean()  # Recall count_data is the
                               # variable that holds our txt counts
lambda_1 = pm.Exponential("lambda_1", alpha)
lambda_2 = pm.Exponential("lambda_2", alpha)

tau = pm.DiscreteUniform("tau", lower=0, upper=n_count_data)
```

In the code above, we create the PyMC variables corresponding to $\lambda_1$ and $\lambda_2$. We assign them to PyMC's *stochastic variables*, so-called because they are treated by the back end as random number generators. We can demonstrate this fact by calling their built-in `random()` methods.

```python
print "Random output:", tau.random(), tau.random(), tau.random()
```

    Random output: 39 10 32

```python
@pm.deterministic
def lambda_(tau=tau, lambda_1=lambda_1, lambda_2=lambda_2):
    out = np.zeros(n_count_data)
    out[:tau] = lambda_1  # lambda before tau is lambda1
    out[tau:] = lambda_2  # lambda after (and including) tau is lambda2
    return out
```

새 function `lambda_` 의 코드이다. 하지만 이건 이미 random variable 이기도 하다: the random variable $\lambda$ from above. Note that because `lambda_1`, `lambda_2` and `tau` are random, `lambda_` will be random. We are **not** fixing any variables yet.

`@pm.deterministic` is a decorator that tells PyMC this is a deterministic function. That is, if the arguments were deterministic (which they are not), the output would be deterministic as well. Deterministic functions will be covered in Chapter 2.

```python
observation = pm.Poisson("obs", lambda_, value=count_data, observed=True)

model = pm.Model([observation, lambda_1, lambda_2, tau])
```

The variable `observation` combines our data, `count_data`, with our proposed data-generation scheme, given by the variable `lambda_`, through the `value` keyword. We also set `observed = True` to tell PyMC that this should stay fixed in our analysis. Finally, PyMC wants us to collect all the variables of interest and create a `Model` instance out of them. This makes our life easier when we retrieve the results.

아래의 코드는 Chapter 3 에서 좀 더 자세히 설명할 것이나 일단 여기에서 간략히 설명하도록 한다. One can think of it as a *learning* step. The machinery being employed is called *Markov Chain Monte Carlo* (MCMC), which I also delay explaining until Chapter 3. 이 기법은 the posterior distributions of $\lambda_1, \lambda_2$ and $\tau$ 으로부터 수천개의 random variables 를 생성하는 것이다. random variables 의 히스토그램을 출력하면 the posterior distributions 가 어떻게 생겼는지도 확인가능하다. Below, we collect the samples (called *traces* in the MCMC literature) into histograms.

```python
# Mysterious code to be explained in Chapter 3.
mcmc = pm.MCMC(model)
mcmc.sample(40000, 10000, 1)
```

     [-----------------100%-----------------] 40000 of 40000 complete in 8.0 sec

```python
lambda_1_samples = mcmc.trace('lambda_1')[:]
lambda_2_samples = mcmc.trace('lambda_2')[:]
tau_samples = mcmc.trace('tau')[:]
```

```python
figsize(12.5, 10)
# histogram of the samples:

ax = plt.subplot(311)
ax.set_autoscaley_on(False)

plt.hist(lambda_1_samples, histtype='stepfilled', bins=30, alpha=0.85,
         label="posterior of $\lambda_1$", color="#A60628", normed=True)
plt.legend(loc="upper left")
plt.title(r"""Posterior distributions of the variables
    $\lambda_1,\;\lambda_2,\;\tau$""")
plt.xlim([15, 30])
plt.xlabel("$\lambda_1$ value")

ax = plt.subplot(312)
ax.set_autoscaley_on(False)
plt.hist(lambda_2_samples, histtype='stepfilled', bins=30, alpha=0.85,
         label="posterior of $\lambda_2$", color="#7A68A6", normed=True)
plt.legend(loc="upper left")
plt.xlim([15, 30])
plt.xlabel("$\lambda_2$ value")

plt.subplot(313)
w = 1.0 / tau_samples.shape[0] * np.ones_like(tau_samples)
plt.hist(tau_samples, bins=n_count_data, alpha=1,
         label=r"posterior of $\tau$",
         color="#467821", weights=w, rwidth=2.)
plt.xticks(np.arange(n_count_data))

plt.legend(loc="upper left")
plt.ylim([0, .75])
plt.xlim([35, len(count_data) - 20])
plt.xlabel(r"$\tau$ (in days)")
plt.ylabel("probability");
```

### 결과 해석

베이지안 방법론은 *분포* 를 리턴한다는 걸 떠올려보자. 그러니 우리는 미지의 값인 $\lambda$s and $\tau$를 기술할 수 있다. 뭘 얻을 수 있는가? 일단 우리는 우리 에측의 불확실성을 알 수 있다: 분포가 넓을 수록, 우리는 결과를 잘 모른다는 이야기가 된다. 우린 또 얼마나 해당 파라메터들이 그럴듯한지도 알 수 있다. 이 경우에 파라메터는 $\lambda_1$ 이랑 $\lambda_2 인데, 각각 18과 23이라는 값을 가진다. 각각의 posterior distributions 은 충분히 구별된다. 무슨 말이냐면 확실히 사용자 행위에 변화가 있다는 것이다.

다른 관측도 할 수 있을까요? 원본 데이터를 다시 한번 본다면, 지금 우리가 얻은 결과가 그럴듯해 보일지 궁금하다.

the posterior distributions for the $\lambda$s 들이 exponential distributions 같지 않다는 것에 주목해라. 우리 prior 가 exponential 인데도 말이다. 실은 posterior distributions 은 우리가 알 수 있는 그 어떤 형태도 아니다. 여기선 그냥 컴퓨터 계산의 혜택을 받기 위해서이다. 만약 수학적으로 이걸 해보려고 한다면, 우리는 어떤 형태의 분포를 선택해야 할지 고민하느라 시간을 많이 보내게 될 것입니다 (그리고 빡친다). 현재 이 구현에서 한 방식은 수학적인 구현 가능성과 딱히 다르지 않다.

우리 방식은 또한 $\tau$ 에 대한 분포도 리턴한다. 이 posterior distribution 은 다른 두 posterior distribution 와는 좀 달라보인다. 왜냐면 이건 discrete random variable 이기 때문이다. 그래서 이건 intervals 에 확률값을 가지지 않는다.

Our analysis also returned a distribution for $\tau$. Its posterior distribution looks a little different from the other two because it is a discrete random variable, so it doesn't assign probabilities to intervals. We can see that near day 45, there was a 50% chance that the user's behaviour changed. Had no change occurred, or had the change been gradual over time, the posterior distribution of $\tau$ would have been more spread out, reflecting that many days were plausible candidates for $\tau$. By contrast, in the actual results we see that only three or four days make any sense as potential transition points.

###그나저나 posterior로부터 샘플값들을 가져서 어디다 쓰는거죠?

이 문제에 대해서는 뒷 부분에 걸쳐 계속 설명하겠습니다. 이건 아주 놀라운 결과를 가져다주거든요. 일단은 이 챕터는 여기서 끝내기로 하죠. 딱 한 예제만 더 하구요.

posterior 샘플들을 사용하면 다음과 같은 질문에 답할 수 있습니다: what is the expected number of texts at day $t, \; 0 \le t \le 70$ ? Recall that the expected value of a Poisson variable is equal to its parameter $\lambda$. Therefore, the question is equivalent to *what is the expected value of $\lambda$ at time $t$*?

아래의 코드에서, let $i$ index samples from the posterior distributions. Given a day $t$, we average over all possible $\lambda_i$ for that day $t$, using $\lambda_i = \lambda_{1,i}$ if $t \lt \tau_i$ (that is, if the behaviour change has not yet occurred), else we use $\lambda_i = \lambda_{2,i}$.

```python
figsize(12.5, 5)
# tau_samples, lambda_1_samples, lambda_2_samples contain
# N samples from the corresponding posterior distribution
N = tau_samples.shape[0]
expected_texts_per_day = np.zeros(n_count_data)
for day in range(0, n_count_data):
    # ix is a bool index of all tau samples corresponding to
    # the switchpoint occurring prior to value of 'day'
    ix = day < tau_samples
    # Each posterior sample corresponds to a value for tau.
    # for each day, that value of tau indicates whether we're "before"
    # (in the lambda1 "regime") or
    #  "after" (in the lambda2 "regime") the switchpoint.
    # by taking the posterior sample of lambda1/2 accordingly, we can average
    # over all samples to get an expected value for lambda on that day.
    # As explained, the "message count" random variable is Poisson distributed,
    # and therefore lambda (the poisson parameter) is the expected value of
    # "message count".
    expected_texts_per_day[day] = (lambda_1_samples[ix].sum()
                                   + lambda_2_samples[~ix].sum()) / N

plt.plot(range(n_count_data), expected_texts_per_day, lw=4, color="#E24A33",
         label="expected number of text-messages received")
plt.xlim(0, n_count_data)
plt.xlabel("Day")
plt.ylabel("Expected # text-messages")
plt.title("Expected number of text-messages received")
plt.ylim(0, 60)
plt.bar(np.arange(len(count_data)), count_data, color="#348ABD", alpha=0.65,
        label="observed texts per day")

plt.legend(loc="upper left");
```

Our analysis shows strong support for believing the user's behavior did change ($\lambda_1$ would have been close in value to $\lambda_2$ had this not been true), and that the change was sudden rather than gradual (as demonstrated by $\tau$'s strongly peaked posterior distribution). We can speculate what might have caused this: a cheaper text-message rate, a recent weather-to-text subscription, or perhaps a new relationship. (In fact, the 45th day corresponds to Christmas, and I moved away to Toronto the next month, leaving a girlfriend behind.)

##### Exercises

1\.  Using `lambda_1_samples` and `lambda_2_samples`, what is the mean of the posterior distributions of $\lambda_1$ and $\lambda_2$?

```python
# type your code here.
```

2\.  What is the expected percentage increase in text-message rates? `hint:` compute the mean of `lambda_1_samples/lambda_2_samples`. Note that this quantity is very different from `lambda_1_samples.mean()/lambda_2_samples.mean()`.

```python
# type your code here.
```

3\. What is the mean of $\lambda_1$ **given** that we know $\tau$ is less than 45.  That is, suppose we have been given new information that the change in behaviour occurred prior to day 45. What is the expected value of $\lambda_1$ now? (You do not need to redo the PyMC part. Just consider all instances where `tau_samples < 45`.)

```python
# type your code here.
```

### References

-  [1] Gelman, Andrew. N.p.. Web. 22 Jan 2013. [N is never large enough](http://andrewgelman.com/2005/07/31/n_is_never_larg/).
-  [2] Norvig, Peter. 2009. [The Unreasonable Effectiveness of Data](http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/35179.pdf).
- [3] Patil, A., D. Huard and C.J. Fonnesbeck. 2010.
PyMC: Bayesian Stochastic Modelling in Python. Journal of Statistical
Software, 35(4), pp. 1-81.
- [4] Jimmy Lin and Alek Kolcz. Large-Scale Machine Learning at Twitter. Proceedings of the 2012 ACM SIGMOD International Conference on Management of Data (SIGMOD 2012), pages 793-804, May 2012, Scottsdale, Arizona.
- [5] Cronin, Beau. "Why Probabilistic Programming Matters." 24 Mar 2013. Google, Online Posting to Google . Web. 24 Mar. 2013. <https://plus.google.com/u/0/107971134877020469960/posts/KpeRdJKR6Z1>.

```python
from IPython.core.display import HTML

def css_styling():
    styles = open("../styles/custom.css", "r").read()
    return HTML(styles)
css_styling()
```

<style>
    @font-face {
        font-family: "Computer Modern";
        src: url('http://9dbb143991406a7c655e-aa5fcb0a5a4ec34cff238a2d56ca4144.r56.cf5.rackcdn.com/cmunss.otf');
    }
    @font-face {
        font-family: "Computer Modern";
        font-weight: bold;
        src: url('http://9dbb143991406a7c655e-aa5fcb0a5a4ec34cff238a2d56ca4144.r56.cf5.rackcdn.com/cmunsx.otf');
    }
    @font-face {
        font-family: "Computer Modern";
        font-style: oblique;
        src: url('http://9dbb143991406a7c655e-aa5fcb0a5a4ec34cff238a2d56ca4144.r56.cf5.rackcdn.com/cmunsi.otf');
    }
    @font-face {
        font-family: "Computer Modern";
        font-weight: bold;
        font-style: oblique;
        src: url('http://9dbb143991406a7c655e-aa5fcb0a5a4ec34cff238a2d56ca4144.r56.cf5.rackcdn.com/cmunso.otf');
    }
    div.cell{
        width:800px;
        margin-left:16% !important;
        margin-right:auto;
    }
    h1 {
        font-family: Helvetica, serif;
    }
    h4{
        margin-top:12px;
        margin-bottom: 3px;
       }
    div.text_cell_render{
        font-family: Computer Modern, "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
        line-height: 145%;
        font-size: 130%;
        width:800px;
        margin-left:auto;
        margin-right:auto;
    }
    .CodeMirror{
            font-family: "Source Code Pro", source-code-pro,Consolas, monospace;
    }
    .prompt{
        display: None;
    }
    .text_cell_render h5 {
        font-weight: 300;
        font-size: 22pt;
        color: #4057A1;
        font-style: italic;
        margin-bottom: .5em;
        margin-top: 0.5em;
        display: block;
    }

    .warning{
        color: rgb( 240, 20, 20 )
        }
</style>

```python

```


```
!pip install pint pip yapf scipy numpy matplotlib pandas seaborn sympy beautifulsoup4 lxml html5lib modsimpy tables
```

    Collecting pint
      Downloading https://files.pythonhosted.org/packages/9d/db/7a2204b03c22069839958df5723eb2718d50c33052e0da84c9a83de14ea4/Pint-0.11-py2.py3-none-any.whl (186kB)
         |████████████████████████████████| 194kB 3.4MB/s
    Requirement already satisfied: pip in /usr/local/lib/python3.6/dist-packages (19.3.1)
    Collecting yapf
      Downloading https://files.pythonhosted.org/packages/c1/5d/d84677fe852bc5e091739acda444a9b6700ffc6b11a21b00dd244c8caef0/yapf-0.30.0-py2.py3-none-any.whl (190kB)
         |████████████████████████████████| 194kB 9.8MB/s
    Requirement already satisfied: scipy in /usr/local/lib/python3.6/dist-packages (1.4.1)
    Requirement already satisfied: numpy in /usr/local/lib/python3.6/dist-packages (1.18.3)
    Requirement already satisfied: matplotlib in /usr/local/lib/python3.6/dist-packages (3.2.1)
    Requirement already satisfied: pandas in /usr/local/lib/python3.6/dist-packages (1.0.3)
    Requirement already satisfied: seaborn in /usr/local/lib/python3.6/dist-packages (0.10.1)
    Requirement already satisfied: sympy in /usr/local/lib/python3.6/dist-packages (1.1.1)
    Requirement already satisfied: beautifulsoup4 in /usr/local/lib/python3.6/dist-packages (4.6.3)
    Requirement already satisfied: lxml in /usr/local/lib/python3.6/dist-packages (4.2.6)
    Requirement already satisfied: html5lib in /usr/local/lib/python3.6/dist-packages (1.0.1)
    Collecting modsimpy
      Downloading https://files.pythonhosted.org/packages/ee/ef/c170df0ee5311cda90a10fc71d1610b9a1cb2241fadbf04bf0f57ff161e9/modsimpy-1.1.3.tar.gz
    Requirement already satisfied: tables in /usr/local/lib/python3.6/dist-packages (3.4.4)
    Requirement already satisfied: setuptools in /usr/local/lib/python3.6/dist-packages (from pint) (46.1.3)
    Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.6/dist-packages (from matplotlib) (2.4.7)
    Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.6/dist-packages (from matplotlib) (1.2.0)
    Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.6/dist-packages (from matplotlib) (0.10.0)
    Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.6/dist-packages (from matplotlib) (2.8.1)
    Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.6/dist-packages (from pandas) (2018.9)
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.6/dist-packages (from sympy) (1.1.0)
    Requirement already satisfied: six>=1.9 in /usr/local/lib/python3.6/dist-packages (from html5lib) (1.12.0)
    Requirement already satisfied: webencodings in /usr/local/lib/python3.6/dist-packages (from html5lib) (0.5.1)
    Requirement already satisfied: numexpr>=2.5.2 in /usr/local/lib/python3.6/dist-packages (from tables) (2.7.1)
    Building wheels for collected packages: modsimpy
      Building wheel for modsimpy (setup.py) ... done
      Created wheel for modsimpy: filename=modsimpy-1.1.3-cp36-none-any.whl size=20356 sha256=0c3a9a5145a45f4b3ea8828531e415b1b79f74c88b8be1306ad1f7618b426abd
      Stored in directory: /root/.cache/pip/wheels/59/34/ac/e82f103fbfb5d961b1a4d2b8e89bb6629a4c0b242e5373cbfa
    Successfully built modsimpy
    Installing collected packages: pint, yapf, modsimpy
    Successfully installed modsimpy-1.1.3 pint-0.11 yapf-0.30.0

```
# import functions from the modsim library
from modsim import *

# set the random number generator. so our data remains the same.
np.random.seed(49)
```

    /usr/local/lib/python3.6/dist-packages/statsmodels/tools/_testing.py:19: FutureWarning: pandas.util.testing is deprecated. Use the functions in the public API at pandas.testing instead.
      import pandas.util.testing as tm

We're simulating a bikeshare system from two locations. 'dap' and 'padilla' which are based in the Ortigas CBD.

**Functions**

```
# simulate probability of arrival.
def step(state, p1, p2):

    # state.clock += 1

    if flip(p1):
        bike_to_padilla(state)
        # print('Moving to Padilla')

    if flip(p2):
        bike_to_dap(state)
        # print('Moving to Dap')

# move bike from dap to padilla. value of empty is the
# number of unhappy customers in a location.
def bike_to_padilla(state):

    if state.dap == 0:
        state.dap_empty +=1
        return
    state.dap -=1
    state.padilla +=1

# move bike from padilla to dap.
def bike_to_dap(state):

    if state.padilla == 0:
        state.padilla_empty +=1
        return
    state.padilla -=1
    state.dap +=1

def decorate_bikeshare():

    decorate(title='Dap-Padilla Bikeshare',
         xlabel='Time Step (minutes)',
         ylabel='Number of Bikes')

# number of time steps -> num_steps. we remove state in the argument on the final parts of the code.
def run_simulation(p1, p2, num_steps):

    # dap = TimeSeries()
    # padilla = TimeSeries()

    state = State(dap=10, padilla=2,
                  dap_empty=0, padilla_empty=0)

    for i in range(num_steps):
        step(state, p1, p2)
        # dap[i] = state.dap
        # padilla[i] = state.padilla

    return state

    # plot(dap, label='Dap')
    # plot(padilla, label='Padilla')

```

**Function Tests**

Initial state that indicate the number of bikes on each location as well as two different states.

```
bikeshare_01 = State(dap=7, padilla=3)
bikeshare_02 = State(dap=3, padilla=7)
```

Which state to work on?

```
bike_to_dap(bikeshare_01)
bike_to_padilla(bikeshare_02)
```

```
bikeshare_01
```

      values

      dap
      8

      padilla
      2

```
bikeshare_02
```

      values

      dap
      2

      padilla
      8

**Metrics for running simulation**

Time to evaluate if the metrics for this design is good or not.

```
new = State(dap=8, padilla=4, dap_empty=0, padilla_empty=0)
```

```
run_simulation(new, 0.4, 0.2, 60)
decorate_bikeshare()
```

More bikes available for padilla. Less bikes available for Dap which leads to...

...our count of unhappy customers at each location.

```
new.dap_empty
```

    2

```
new.padilla_empty
```

    0

Suppose we want to add a clock to find out how many steps have elapsed.

```
simulation = State(dap=10, padilla=2,
                  dap_empty=0, padilla_empty=0,
                  clock=0)
```

```
run_simulation(simulation, 0.4, 0.2, 60)
decorate_bikeshare()
```

Less number of bikes available for Dap.

```
simulation
```

      values

      dap
      1

      padilla
      11

      dap_empty
      5

      padilla_empty
      0

      clock
      60

Another way of doing this would be to add state within function, remove state from argument and hide clock within function. We get this result...

```
 state = run_simulation(0.4, 0.2, 60)
 state
```

      values

      dap
      0

      padilla
      12

      dap_empty
      3

      padilla_empty
      0

```
state.dap_empty
```

    3

```
state = run_simulation(0.2, 0.2, 60)
state
```

      values

      dap
      7

      padilla
      5

      dap_empty
      0

      padilla_empty
      0

```
state = run_simulation(0.6, 0.2, 60)
state
```

      values

      dap
      1

      padilla
      11

      dap_empty
      9

      padilla_empty
      0

So we know that if we assign higher probability for dap, the more we have unhappy customers waiting. The lower probability dap has, the more available bikes there are.

```
p2 = 0.2
num_steps = 60
p1_array = linspace(0, 1, 11)
```

```
for p1 in p1_array:
    state = run_simulation(p1, p2, num_steps)
    print(p1, state.dap_empty)
```

    0.0 0
    0.1 0
    0.2 0
    0.30000000000000004 0
    0.4 0
    0.5 8
    0.6000000000000001 26
    0.7000000000000001 20
    0.8 26
    0.9 32
    1.0 37

So what we are seeing here are the number of unhappy customers in the dap station based on the p1 array.

```
sweep = SweepSeries()

for p1 in p1_array:
    state = run_simulation(p1, p2, num_steps)
    sweep[p1] = state.dap_empty
```

```
plot(sweep, label='Dap')

decorate(title='Dap-Padilla Bikeshare',
         xlabel='Arrival rate at Dap (p1 in customers/min)',
         ylabel='Number of unhappy customers')
```

So we used Sweep instead of print and plot the results. As we can see, there's a lot of unhappy customers when the arrival rate is high.

**Optimizing Sweep**

```
# to optimize sweep, we turn this into a function
def sweep_p1(p1_array):
    p2 = 0.2
    num_steps = 60
    sweep = SweepSeries()

    for p1 in p1_array:
        state = run_simulation(p1, p2, num_steps)
        sweep[p1] = state.dap_empty

    return sweep
```

```
p1_array = linspace(0, 1, 101)
sweep = sweep_p1(p1_array)
plot(sweep, 'bo', label='Dap')
decorate(title='Padilla-Dap Bikeshare',
         xlabel='Arrival rate at Dap (p1 in customers/min)',
         ylabel='Number of unhappy customers')
```

What about arrival to Padilla?

```
def sweep_p2(p2_array):
    p1 = 0.5
    num_steps = 60
    sweep = SweepSeries()

    for p2 in p2_array:
        state = run_simulation(p1, p2, num_steps)
        sweep[p2] = state.dap_empty

    return sweep
```

```
p2_array = linspace(0, 1, 101)
sweep = sweep_p2(p2_array)
plot(sweep, 'bo', label='Padilla', color='orange')

decorate(title='Dap-Padilla Bikeshare',
         xlabel='Arrival rate at Padilla (p2 in customers/min)',
         ylabel='Number of unhappy customers')
```

Longer we go, less number of unhappy customers.

Simulations tend to be random and the results vary from one to another. To get a clearer picture, we need to run multiple simulations.

```
def run_multiple_simulations(p1, p2, num_steps, num_runs):

    results = TimeSeries()

    for i in range(num_runs):
        state = run_simulation(p1, p2, num_steps)
        results[i] = state.dap_empty + state.padilla_empty

    return results
```

We test our function with sample parameters.

```
p1 = 0.3
p2 = 0.3
num_steps = 60
num_runs = 10
run_multiple_simulations(p1, p2, num_steps, num_runs)
```

      values

      0
      0

      1
      6

      2
      3

      3
      0

      4
      0

      5
      4

      6
      9

      7
      0

      8
      0

      9
      2

These are the total unhappy customers found at both locations.

```
# will need to reset this at sweep p1 array to see Dap's arrival rate!
plot(sweep, label='total', color='green')

decorate(title='Dap-Padilla Bikeshare',
         xlabel='Arrival rate at Padilla (p2 in customers/min)',
         ylabel='Average total unhappy customers')
```

On route to Padilla, we can see here that higher arrival rates occur within 0.0 - 0.4 customers/min.

```

```


# Equations of General Relativistic Force-Free Electrodynamics (GRFFE)

## Authors: Patrick Nelson, Zach Etienne, and Leo Werneck

## This notebook documents and constructs a number of quantities useful for building symbolic (SymPy) expressions for the equations of general relativistic force-free electrodynamics (GRFFE), using the same (Valencia) formalism as `IllinoisGRMHD`

**Notebook Status:**  Self-Validated

**Validation Notes:** This tutorial notebook has been confirmed to be self-consistent with its corresponding NRPy+ module, as documented [below](#code_validation). **Additional validation tests may have been performed, but are as yet, undocumented. (TODO)**

## Introduction

We write the equations of general relativistic hydrodynamics in conservative form as follows (adapted from Eqs. 41-44 of [Duez et al](https://arxiv.org/pdf/astro-ph/0503420.pdf)):

\begin{eqnarray}
\partial_t \tilde{S}_i &+& \partial_j \left(\alpha \sqrt{\gamma} T^j_{{\rm EM}i} \right) = \frac{1}{2} \alpha\sqrt{\gamma} T^{\mu\nu}_{\rm EM} g_{\mu\nu,i},
\end{eqnarray}
where we assume $T^{\mu\nu}_{\rm EM}$ is the electromagnetic stress-energy tensor:
$$
T^{\mu\nu}_{\rm EM} = b^2 u^{\mu} u^{\nu} + \frac{b^2}{2} g^{\mu\nu} - b^\mu b^\nu,
$$
and
$$
v^j = \frac{u^j}{u^0} \\
$$

Some quantities can be defined in precisely the same way they are defined in the GRHD equations. ***Therefore, we will not define special functions for generating these quantities, and instead refer the user to the appropriate functions in the [GRHD module](../edit/GRHD/equations.py)*** Namely,

* The GRFFE conservative variables:
    * $\tilde{S}_i  = \alpha \sqrt{\gamma} T^0{}_i$, via `GRHD.compute_S_tildeD(alpha, sqrtgammaDET, T4UD)`
* The GRFFE fluxes:
    * $\tilde{S}_i$ flux: $\left(\alpha \sqrt{\gamma} T^j{}_i \right)$, via `GRHD.compute_S_tilde_fluxUD(alpha, sqrtgammaDET, T4UD)`
* GRFFE source terms:
    * $\tilde{S}_i$ source term: $\frac{1}{2} \alpha\sqrt{\gamma} T^{\mu\nu} g_{\mu\nu,i}$, via `GRHD.compute_S_tilde_source_termD(alpha, sqrtgammaDET,g4DD_zerotimederiv_dD, T4UU)`

Also we will write the 4-metric in terms of the ADM 3-metric, lapse, and shift using standard equations.

Thus the full set of input variables include:
* Spacetime quantities:
    * ADM quantities $\alpha$, $\beta^i$, $\gamma_{ij}$
* Hydrodynamical quantities:
    * 4-velocity $u^\mu$
* Electrodynamical quantities
    * Magnetic field $B^i$

### A Note on Notation

As is standard in NRPy+,

* Greek indices refer to four-dimensional quantities where the zeroth component indicates temporal (time) component.
* Latin indices refer to three-dimensional quantities. This is somewhat counterintuitive since Python always indexes its lists starting from 0. As a result, the zeroth component of three-dimensional quantities will necessarily indicate the first *spatial* direction.

For instance, in calculating the first term of $b^2 u^\mu u^\nu$, we use Greek indices:

```python
T4EMUU = ixp.zerorank2(DIM=4)
for mu in range(4):
    for nu in range(4):
        # Term 1: b^2 u^{\mu} u^{\nu}
        T4EMUU[mu][nu] = smallb2*u4U[mu]*u4U[nu]
```

When we calculate $\beta_i = \gamma_{ij} \beta^j$, we use Latin indices:
```python
betaD = ixp.zerorank1(DIM=3)
for i in range(3):
    for j in range(3):
        betaD[i] += gammaDD[i][j] * betaU[j]
```

As a corollary, any expressions involving mixed Greek and Latin indices will need to offset one set of indices by one: A Latin index in a four-vector will be incremented and a Greek index in a three-vector will be decremented (however, the latter case does not occur in this tutorial notebook). This can be seen when we handle $\frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu}$:
```python
# \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu} / 2
for i in range(3):
    for mu in range(4):
        for nu in range(4):
            S_tilde_rhsD[i] += alpsqrtgam * T4EMUU[mu][nu] * g4DD_zerotimederiv_dD[mu][nu][i+1] / 2
```

# Table of Contents
$$\label{toc}$$

Each family of quantities is constructed within a given function (**boldfaced** below). This notebook is organized as follows

1. [Step 1](#importmodules): Import needed NRPy+ & Python modules
1. [Step 2](#u0bu): Define needed quantities for $T^{\mu\nu}_{\rm EM}$, the EM part of the stress-energy tensor
1. [Step 3](#stressenergy): **compute_TEM4UU()**, **compute_TEM4UD()**: Define the stress-energy tensor $T^{\mu\nu}_{\rm EM}$ and $T^\mu_{{\rm EM}\nu}$
1. [Step 4](#declarevarsconstructgrffeeqs): Declare ADM and hydrodynamical input variables, and construct GRFFE equations
1. [Step 5](#code_validation): Code Validation against `GRFFE.equations` NRPy+ module for GRMHD
1. [Step 6](#code_validation_2): Code Validation against `GRFFE.equations` NRPy+ module for GRFFE
1. [Step 7](#latex_pdf_output): Output this notebook to $\LaTeX$-formatted PDF file

# Step 1: Import needed NRPy+ & Python modules \[Back to [top](#toc)\]
$$\label{importmodules}$$

```python
# Step 1: Import needed core NRPy+ modules
from outputC import *            # NRPy+: Core C code output module
import indexedexp as ixp         # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support
```

# Step 2: Define needed quantities for $T^{\mu\nu}_{\rm EM}$, the EM part of the stress-energy tensor \[Back to [top](#toc)\]
$$\label{u0bu}$$

We are given $B^i$, the magnetic field as measured by a *normal* observer, yet $T^{\mu\nu}_{\rm EM}$ depends on $b^{\mu}$, the magnetic field as measured by an observer comoving with the plasma $B^{\mu}_{\rm (u)}$, divided by $\sqrt{4\pi}$.

In the ideal MHD limit, $B^{\mu}_{\rm (u)}$ is orthogonal to the plasma 4-velocity $u^\mu$, which sets the $\mu=0$ component.

$B^{\mu}_{\rm (u)}$ is related to the magnetic field as measured by a *normal* observer $B^i$ via a simple projection (Eq 21 in [Duez *et al* (2005)](https://arxiv.org/pdf/astro-ph/0503420.pdf)), which results in the expressions (Eqs 23 and 24 in [Duez *et al* (2005)](https://arxiv.org/pdf/astro-ph/0503420.pdf)):

\begin{align}
\sqrt{4\pi} b^0 = B^0_{\rm (u)} &= \frac{u_j B^j}{\alpha} \\
\sqrt{4\pi} b^i = B^i_{\rm (u)} &= \frac{B^i + (u_j B^j) u^i}{\alpha u^0}\\
\end{align}

Further, $B^i$ is related to the actual magnetic field evaluated in IllinoisGRMHD, $\tilde{B}^i$ via

$$B^i = \frac{\tilde{B}^i}{\gamma},$$

where $\gamma$ is the determinant of the spatial 3-metric:

```python
# Step 2.a: Define B^i = Btilde^i / sqrt(gamma)
def compute_B_notildeU(sqrtgammaDET, B_tildeU):
    global B_notildeU
    B_notildeU = ixp.zerorank1(DIM=3)
    for i in range(3):
        B_notildeU[i] = B_tildeU[i]/sqrtgammaDET
```

Next we compute Eqs 23 and 24 in [Duez *et al* (2005)](https://arxiv.org/pdf/astro-ph/0503420.pdf):

\begin{align}
\sqrt{4\pi} b^0 = B^0_{\rm (u)} &= \frac{u_j B^j}{\alpha} \\
\sqrt{4\pi} b^i = B^i_{\rm (u)} &= \frac{B^i + (u_j B^j) u^i}{\alpha u^0}.
\end{align}

In doing so, we will store the scalar $u_j B^j$ to `u4_dot_B_notilde`:

```python
# Step 2.b.i: Define b^mu.
def compute_smallb4U(gammaDD,betaU,alpha, u4U,B_notildeU, sqrt4pi):
    global smallb4U
    import BSSN.ADMBSSN_tofrom_4metric as AB4m
    AB4m.g4DD_ito_BSSN_or_ADM("ADM",gammaDD,betaU,alpha)

    u4D = ixp.zerorank1(DIM=4)
    for mu in range(4):
        for nu in range(4):
            u4D[mu] += AB4m.g4DD[mu][nu]*u4U[nu]
    smallb4U = ixp.zerorank1(DIM=4)
    u4_dot_B_notilde = sp.sympify(0)
    for i in range(3):
        u4_dot_B_notilde += u4D[i+1]*B_notildeU[i]

    # b^0 = (u_j B^j)/[alpha * sqrt(4 pi)]
    smallb4U[0] = u4_dot_B_notilde / (alpha*sqrt4pi)
    # b^i = [B^i + (u_j B^j) u^i]/[alpha * u^0 * sqrt(4 pi)]
    for i in range(3):
        smallb4U[i+1] = (B_notildeU[i] + u4_dot_B_notilde*u4U[i+1]) / (alpha*u4U[0]*sqrt4pi)
```

However, in the case of pure GRFFE (that is, if we assume the pressure and density are zero), we can make an additional simplifying assumption. When deriving the equations of GRFFE, one has a choice as to how to define the velocity. Following the example set by [McKinney (2006)](https://arxiv.org/pdf/astro-ph/0601410.pdf), section 2.1, and [Paschalidis (2013)](https://arxiv.org/pdf/1310.3274v2.pdf), Appendix A, we choose a referencing frame comoving with the plasma that fulfills the ideal MHD condition (i.e., the electric field is zero). However, in GRFFE, this choice yields a one-parameter family of timelike vectors. By choosing the drift velocity $v^i = u^i/u^0$ that minimizes the Lorentz factor, we find that the four velocity is orthogonal to the magnetic field, or $u_j B^j = 0$. With this assumption,

\begin{align}
\sqrt{4\pi} b^0 &= 0 \\
\sqrt{4\pi} b^i &= \frac{B^i}{\alpha u^0}.
\end{align}

This simplification also gives the inversion from $\tilde{S}_i$ to the drift velocity $v^i$ a closed form, greatly simplifying the conservative-to-primitive solver and removing the need to

```python
# Step 2.b.ii: Define b^mu when u4 and B are orthogonal
def compute_smallb4U_with_driftvU_for_FFE(gammaDD,betaU,alpha, u4U,B_notildeU, sqrt4pi):
    global smallb4_with_driftv_for_FFE_U
    import BSSN.ADMBSSN_tofrom_4metric as AB4m
    AB4m.g4DD_ito_BSSN_or_ADM("ADM",gammaDD,betaU,alpha)

    u4D = ixp.zerorank1(DIM=4)
    for mu in range(4):
        for nu in range(4):
            u4D[mu] += AB4m.g4DD[mu][nu]*u4U[nu]
    smallb4_with_driftv_for_FFE_U = ixp.zerorank1(DIM=4)

    # b^0 = 0
    smallb4_with_driftv_for_FFE_U[0] = 0
    # b^i = B^i / [alpha * u^0 * sqrt(4 pi)]
    for i in range(3):
        smallb4_with_driftv_for_FFE_U[i+1] = B_notildeU[i] / (alpha*u4U[0]*sqrt4pi)
```

Finally we compute `smallbsquared`=$b^2 = b_{\mu} b^{\mu} = g_{\mu \nu} b^{\nu}b^{\mu}$:

```python
# Step 2.c: Define b^2.
def compute_smallbsquared(gammaDD,betaU,alpha, smallb4U):
    global smallbsquared
    import BSSN.ADMBSSN_tofrom_4metric as AB4m
    AB4m.g4DD_ito_BSSN_or_ADM("ADM",gammaDD,betaU,alpha)

    smallbsquared = sp.sympify(0)
    for mu in range(4):
        for nu in range(4):
            smallbsquared += AB4m.g4DD[mu][nu]*smallb4U[mu]*smallb4U[nu]
```

# Step 3: Define the electromagnetic stress-energy tensor $T^{\mu\nu}_{\rm EM}$ and $T^\mu_{{\rm EM}\nu}$ \[Back to [top](#toc)\]
$$\label{stressenergy}$$

Recall from above that

$$
T^{\mu\nu}_{\rm EM} = b^2 u^{\mu} u^{\nu} + \frac{1}{2} b^2 g^{\mu\nu} - b^\mu b^\nu.
$$
Also

$$
T^\mu_{{\rm EM}\nu} = T^{\mu\delta}_{\rm EM} g_{\delta \nu}
$$

```python
# Step 3.a: Define T_{EM}^{mu nu} (a 4-dimensional tensor)
def compute_TEM4UU(gammaDD,betaU,alpha, smallb4U, smallbsquared,u4U):
    global TEM4UU

    # Then define g^{mu nu} in terms of the ADM quantities:
    import BSSN.ADMBSSN_tofrom_4metric as AB4m
    AB4m.g4UU_ito_BSSN_or_ADM("ADM",gammaDD,betaU,alpha)

    # Finally compute T^{mu nu}
    TEM4UU = ixp.zerorank2(DIM=4)
    for mu in range(4):
        for nu in range(4):
            TEM4UU[mu][nu] = smallbsquared*u4U[mu]*u4U[nu] \
                             + sp.Rational(1,2)*smallbsquared*AB4m.g4UU[mu][nu] \
                             - smallb4U[mu]*smallb4U[nu]

# Step 3.b: Define T^{mu}_{nu} (a 4-dimensional tensor)
def compute_TEM4UD(gammaDD,betaU,alpha, TEM4UU):
    global TEM4UD
    # Next compute T^mu_nu = T^{mu delta} g_{delta nu}, needed for S_tilde flux.
    # First we'll need g_{alpha nu} in terms of ADM quantities:
    import BSSN.ADMBSSN_tofrom_4metric as AB4m
    AB4m.g4DD_ito_BSSN_or_ADM("ADM",gammaDD,betaU,alpha)
    TEM4UD = ixp.zerorank2(DIM=4)
    for mu in range(4):
        for nu in range(4):
            for delta in range(4):
                TEM4UD[mu][nu] += TEM4UU[mu][delta]*AB4m.g4DD[delta][nu]
```

# Step 4: Declare ADM and hydrodynamical input variables, and construct GRFFE equations \[Back to [top](#toc)\]
$$\label{declarevarsconstructgrffeeqs}$$

The GRFFE equations are given by the induction equation (handled later) and the evolution equation for $\tilde{S}_i$, the Poynting one-form:

\begin{eqnarray}
\partial_t \tilde{S}_i &+& \partial_j \left(\alpha \sqrt{\gamma} T^j_{{\rm EM}i} \right) = \frac{1}{2} \alpha\sqrt{\gamma} T^{\mu\nu}_{\rm EM} g_{\mu\nu,i}.
\end{eqnarray}

Notice that all terms in this equation ($\tilde{S}_i$, $\left(\alpha \sqrt{\gamma} T^j_{{\rm EM}i} \right)$, and the source term) are *identical* to those in the evolution equation for $\tilde{S}_i$ in [general relativistic hydrodynamics (GRHD)](Tutorial-GRHD_Equations-Cartesian.ipynb); one need only replace $T^{\mu\nu}$ of GRHD with the $T^{\mu\nu}_{\rm EM}$ defined above.

Thus we will reuse expressions from the [general relativistic hydrodynamics (GRHD)](Tutorial-GRHD_Equations-Cartesian.ipynb) module:

```python
# First define hydrodynamical quantities
u4U = ixp.declarerank1("u4U", DIM=4)
B_tildeU = ixp.declarerank1("B_tildeU", DIM=3)

# Then ADM quantities
gammaDD = ixp.declarerank2("gammaDD","sym01",DIM=3)
betaU   = ixp.declarerank1("betaU", DIM=3)
alpha   = sp.symbols('alpha', real=True)

# Then numerical constant
sqrt4pi = sp.symbols('sqrt4pi', real=True)

# First compute stress-energy tensor T4UU and T4UD:
import GRHD.equations as GRHD
GRHD.compute_sqrtgammaDET(gammaDD)
compute_B_notildeU(GRHD.sqrtgammaDET, B_tildeU)
compute_smallb4U(gammaDD,betaU,alpha, u4U, B_notildeU, sqrt4pi)
compute_smallbsquared(gammaDD,betaU,alpha, smallb4U)

compute_TEM4UU(gammaDD,betaU,alpha, smallb4U, smallbsquared,u4U)
compute_TEM4UD(gammaDD,betaU,alpha, TEM4UU)

# Compute conservative variables in terms of primitive variables
GRHD.compute_S_tildeD( alpha, GRHD.sqrtgammaDET, TEM4UD)
S_tildeD = GRHD.S_tildeD

# Next compute fluxes of conservative variables
GRHD.compute_S_tilde_fluxUD(alpha, GRHD.sqrtgammaDET,    TEM4UD)
S_tilde_fluxUD = GRHD.S_tilde_fluxUD

# Then declare derivatives & compute g4DDdD
gammaDD_dD = ixp.declarerank3("gammaDD_dD","sym01",DIM=3)
betaU_dD   = ixp.declarerank2("betaU_dD"  ,"nosym",DIM=3)
alpha_dD   = ixp.declarerank1("alpha_dD"          ,DIM=3)
GRHD.compute_g4DD_zerotimederiv_dD(gammaDD,betaU,alpha, gammaDD_dD,betaU_dD,alpha_dD)

# Finally compute source terms on tau_tilde and S_tilde equations
GRHD.compute_S_tilde_source_termD(alpha, GRHD.sqrtgammaDET, GRHD.g4DD_zerotimederiv_dD, TEM4UU)
S_tilde_source_termD = GRHD.S_tilde_source_termD
```

# Step 5: Code Validation against `GRFFE.equations` NRPy+ module for GRMHD \[Back to [top](#toc)\]
$$\label{code_validation}$$

As a code validation check, we verify agreement in the SymPy expressions for the GRFFE equations generated in
1. this tutorial notebook versus
2. the NRPy+ [GRFFE.equations](../edit/GRFFE/equations.py) module.

```python
import GRFFE.equations as GRFFE

# First compute B^i from Btilde^i:
GRFFE.compute_B_notildeU(GRHD.sqrtgammaDET, B_tildeU)

# Then compute b^mu and b^2:
GRFFE.compute_smallb4U(gammaDD, betaU, alpha, u4U, GRFFE.B_notildeU, sqrt4pi)
GRFFE.compute_smallbsquared(gammaDD, betaU, alpha, GRFFE.smallb4U)

# Next construct stress-energy tensor T4UU and T4UD:
GRFFE.compute_TEM4UU(gammaDD,betaU,alpha, GRFFE.smallb4U, GRFFE.smallbsquared,u4U)
GRFFE.compute_TEM4UD(gammaDD,betaU,alpha, GRFFE.TEM4UU)

# Compute conservative variables in terms of primitive variables
GRHD.compute_S_tildeD(alpha, GRHD.sqrtgammaDET, GRFFE.TEM4UD)
Ge_S_tildeD = GRHD.S_tildeD

# Next compute fluxes of conservative variables
GRHD.compute_S_tilde_fluxUD(alpha, GRHD.sqrtgammaDET, GRFFE.TEM4UD)
Ge_S_tilde_fluxUD = GRHD.S_tilde_fluxUD

# Then declare derivatives & compute g4DDdD
# gammaDD_dD = ixp.declarerank3("gammaDD_dD","sym01",DIM=3)
# betaU_dD   = ixp.declarerank2("betaU_dD"  ,"nosym",DIM=3)
# alpha_dD   = ixp.declarerank1("alpha_dD"          ,DIM=3)
GRHD.compute_g4DD_zerotimederiv_dD(gammaDD,betaU,alpha, gammaDD_dD,betaU_dD,alpha_dD)

# Finally compute source terms on S_tilde equations
GRHD.compute_S_tilde_source_termD(alpha, GRHD.sqrtgammaDET,GRHD.g4DD_zerotimederiv_dD,GRFFE.TEM4UU)
Ge_S_tilde_source_termD = GRHD.S_tilde_source_termD
```

```python
all_passed=True
def comp_func(expr1,expr2,basename,prefixname2="GRFFE."):
    if str(expr1-expr2)!="0":
        print(basename+" - "+prefixname2+basename+" = "+ str(expr1-expr2))
        all_passed=False

def gfnm(basename,idx1,idx2=None,idx3=None):
    if idx2==None:
        return basename+"["+str(idx1)+"]"
    if idx3==None:
        return basename+"["+str(idx1)+"]["+str(idx2)+"]"
    return basename+"["+str(idx1)+"]["+str(idx2)+"]["+str(idx3)+"]"

expr_list = []
exprcheck_list = []
namecheck_list = []

for mu in range(4):
    for nu in range(4):
        namecheck_list.extend([gfnm("TEM4UU",mu,nu),gfnm("TEM4UD",mu,nu)])
        exprcheck_list.extend([GRFFE.TEM4UU[mu][nu],GRFFE.TEM4UD[mu][nu]])
        expr_list.extend([TEM4UU[mu][nu],TEM4UD[mu][nu]])

for i in range(3):
    namecheck_list.extend([gfnm("S_tildeD",i),gfnm("S_tilde_source_termD",i)])
    exprcheck_list.extend([Ge_S_tildeD[i],Ge_S_tilde_source_termD[i]])
    expr_list.extend([S_tildeD[i],S_tilde_source_termD[i]])
    for j in range(3):
        namecheck_list.extend([gfnm("S_tilde_fluxUD",i,j)])
        exprcheck_list.extend([Ge_S_tilde_fluxUD[i][j]])
        expr_list.extend([S_tilde_fluxUD[i][j]])

for i in range(len(expr_list)):
    comp_func(expr_list[i],exprcheck_list[i],namecheck_list[i])

import sys
if all_passed:
    print("ALL TESTS PASSED!")
else:
    print("ERROR: AT LEAST ONE TEST DID NOT PASS")
    sys.exit(1)
```

    ALL TESTS PASSED!

# Step 6: Code Validation against `GRFFE.equations` NRPy+ module for GRFFE \[Back to [top](#toc)\]
$$\label{code_validation_2}$$

Additionally, we verify agreement in the SymPy expressions for the GRFFE equations generated in
1. this tutorial notebook versus
2. the NRPy+ [GRFFE.equations](../edit/GRFFE/equations.py) module
for the case $u_j B^j = 0$.

We will only recompute $b^\mu$ and the expressions that depend on it.

```python
# Generate the expressions within the tutorial, starting with:
compute_smallb4U_with_driftvU_for_FFE(gammaDD,betaU,alpha, u4U, B_notildeU, sqrt4pi)
compute_smallbsquared(gammaDD,betaU,alpha, smallb4_with_driftv_for_FFE_U)

compute_TEM4UU(gammaDD,betaU,alpha, smallb4_with_driftv_for_FFE_U, smallbsquared,u4U)
compute_TEM4UD(gammaDD,betaU,alpha, TEM4UU)

# Compute conservative variables in terms of primitive variables
GRHD.compute_S_tildeD( alpha, GRHD.sqrtgammaDET, TEM4UD)
S_tildeD = GRHD.S_tildeD

# Next compute fluxes of conservative variables
GRHD.compute_S_tilde_fluxUD(alpha, GRHD.sqrtgammaDET,    TEM4UD)
S_tilde_fluxUD = GRHD.S_tilde_fluxUD

# Then declare derivatives & compute g4DDdD
gammaDD_dD = ixp.declarerank3("gammaDD_dD","sym01",DIM=3)
betaU_dD   = ixp.declarerank2("betaU_dD"  ,"nosym",DIM=3)
alpha_dD   = ixp.declarerank1("alpha_dD"          ,DIM=3)
GRHD.compute_g4DD_zerotimederiv_dD(gammaDD,betaU,alpha, gammaDD_dD,betaU_dD,alpha_dD)

# Finally compute source terms on tau_tilde and S_tilde equations
GRHD.compute_S_tilde_source_termD(alpha, GRHD.sqrtgammaDET, GRHD.g4DD_zerotimederiv_dD, TEM4UU)
S_tilde_source_termD = GRHD.S_tilde_source_termD

# Now compute the expressions from the module
# Then compute b^mu and b^2:
GRFFE.compute_smallb4U_with_driftvU_for_FFE(gammaDD, betaU, alpha, u4U, GRFFE.B_notildeU, sqrt4pi)
GRFFE.compute_smallbsquared(gammaDD, betaU, alpha, GRFFE.smallb4_with_driftv_for_FFE_U)

# Next construct stress-energy tensor T4UU and T4UD:
GRFFE.compute_TEM4UU(gammaDD,betaU,alpha, GRFFE.smallb4_with_driftv_for_FFE_U, GRFFE.smallbsquared,u4U)
GRFFE.compute_TEM4UD(gammaDD,betaU,alpha, GRFFE.TEM4UU)

# Compute conservative variables in terms of primitive variables
GRHD.compute_S_tildeD(alpha, GRHD.sqrtgammaDET, GRFFE.TEM4UD)
Ge_S_tildeD = GRHD.S_tildeD

# Next compute fluxes of conservative variables
GRHD.compute_S_tilde_fluxUD(alpha, GRHD.sqrtgammaDET, GRFFE.TEM4UD)
Ge_S_tilde_fluxUD = GRHD.S_tilde_fluxUD

# Then declare derivatives & compute g4DDdD
# gammaDD_dD = ixp.declarerank3("gammaDD_dD","sym01",DIM=3)
# betaU_dD   = ixp.declarerank2("betaU_dD"  ,"nosym",DIM=3)
# alpha_dD   = ixp.declarerank1("alpha_dD"          ,DIM=3)
GRHD.compute_g4DD_zerotimederiv_dD(gammaDD,betaU,alpha, gammaDD_dD,betaU_dD,alpha_dD)

# Finally compute source terms on S_tilde equations
GRHD.compute_S_tilde_source_termD(alpha, GRHD.sqrtgammaDET,GRHD.g4DD_zerotimederiv_dD,GRFFE.TEM4UU)
Ge_S_tilde_source_termD = GRHD.S_tilde_source_termD
```

```python
all_passed=True

expr_list = []
exprcheck_list = []
namecheck_list = []

for mu in range(4):
    for nu in range(4):
        namecheck_list.extend([gfnm("TEM4UU",mu,nu),gfnm("TEM4UD",mu,nu)])
        exprcheck_list.extend([GRFFE.TEM4UU[mu][nu],GRFFE.TEM4UD[mu][nu]])
        expr_list.extend([TEM4UU[mu][nu],TEM4UD[mu][nu]])

for i in range(3):
    namecheck_list.extend([gfnm("S_tildeD",i),gfnm("S_tilde_source_termD",i)])
    exprcheck_list.extend([Ge_S_tildeD[i],Ge_S_tilde_source_termD[i]])
    expr_list.extend([S_tildeD[i],S_tilde_source_termD[i]])
    for j in range(3):
        namecheck_list.extend([gfnm("S_tilde_fluxUD",i,j)])
        exprcheck_list.extend([Ge_S_tilde_fluxUD[i][j]])
        expr_list.extend([S_tilde_fluxUD[i][j]])

for i in range(len(expr_list)):
    comp_func(expr_list[i],exprcheck_list[i],namecheck_list[i])

import sys
if all_passed:
    print("ALL TESTS PASSED!")
else:
    print("ERROR: AT LEAST ONE TEST DID NOT PASS")
    sys.exit(1)
```

    ALL TESTS PASSED!

# Step 7: Output this notebook to $\LaTeX$-formatted PDF file \[Back to [top](#toc)\]
$$\label{latex_pdf_output}$$

The following code cell converts this Jupyter notebook into a proper, clickable $\LaTeX$-formatted PDF file. After the cell is successfully run, the generated PDF may be found in the root NRPy+ tutorial directory, with filename
[Tutorial-GRFFE_Equations-Cartesian.pdf](Tutorial-GRFFE_Equations-Cartesian.pdf) (Note that clicking on this link may not work; you may need to open the PDF file through another means.)

```python
!jupyter nbconvert --to latex --template latex_nrpy_style.tplx --log-level='WARN' Tutorial-GRFFE_Equations-Cartesian.ipynb
!pdflatex -interaction=batchmode Tutorial-GRFFE_Equations-Cartesian.tex
!pdflatex -interaction=batchmode Tutorial-GRFFE_Equations-Cartesian.tex
!pdflatex -interaction=batchmode Tutorial-GRFFE_Equations-Cartesian.tex
!rm -f Tut*.out Tut*.aux Tut*.log
```

    This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
     restricted \write18 enabled.
    entering extended mode
    This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
     restricted \write18 enabled.
    entering extended mode
    This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
     restricted \write18 enabled.
    entering extended mode