# Frequentist Inference Case Study - Part A

## 1. Learning objectives

Welcome to part A of the Frequentist inference case study! The purpose of this case study is to help you apply the concepts associated with Frequentist inference in Python. Frequentist inference is the process of deriving conclusions about an underlying distribution via the observation of data. In particular, you'll practice writing Python code to apply the following statistical concepts:
* the _z_-statistic
* the _t_-statistic
* the difference and relationship between the two
* the Central Limit Theorem, including its assumptions and consequences
* how to estimate the population mean and standard deviation from a sample
* the concept of a sampling distribution of a test statistic, particularly for the mean
* how to combine these concepts to calculate a confidence interval

## Prerequisites

To be able to complete this notebook, you are expected to have a basic understanding of:
* what a random variable is (p.400 of Professor Spiegelhalter's *The Art of Statistics, hereinafter AoS*)
* what a population, and a population distribution, are (p. 397 of *AoS*)
* a high-level sense of what the normal distribution is (p. 394 of *AoS*)
* what the t-statistic is (p. 275 of *AoS*)

Happily, these should all be concepts with which you are reasonably familiar after having read ten chapters of Professor Spiegelhalter's book, *The Art of Statistics*.

We'll try to relate the concepts in this case study back to page numbers in *The Art of Statistics* so that you can focus on the Python aspects of this case study. The second part (part B) of this case study will involve another, more real-world application of these tools.

For this notebook, we will use data sampled from a known normal distribution. This allows us to compare our results with theoretical expectations.

## 2. An introduction to sampling from the normal distribution

First, let's explore the ways we can generate the normal distribution. While there's a fair amount of interest in [sklearn](https://scikit-learn.org/stable/) within the machine learning community, you're likely to have heard of [scipy](https://docs.scipy.org/doc/scipy-0.15.1/reference/index.html) if you're coming from the sciences. For this assignment, you'll use [scipy.stats](https://docs.scipy.org/doc/scipy-0.15.1/reference/tutorial/stats.html) to complete your work.

This assignment will require some digging around and getting your hands dirty (your learning is maximized that way)! You should have the research skills and the tenacity to do these tasks independently, but if you struggle, reach out to your immediate community and your mentor for help.

```python
from scipy.stats import norm
from scipy.stats import t
import numpy as np
from numpy.random import seed
import matplotlib.pyplot as plt
```

__Q1:__ Call up the documentation for the `norm` function imported above. (Hint: that documentation is [here](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.norm.html)). What is the second listed method?

__A:__  Probability density function.
pdf(x, loc=0, scale=1)

__Q2:__ Use the method that generates random variates to draw five samples from the standard normal distribution.

__A:__ Random variates.
rvs(loc=0, scale=1, size=1, random_state=None)

```python
seed(47)
# draw five samples here
samples = norm.rvs(size=5)
print(samples)
```

    [-0.84800948  1.30590636  0.92420797  0.6404118  -1.05473698]

__Q3:__ What is the mean of this sample? Is it exactly equal to the value you expected? Hint: the sample was drawn from the standard normal distribution. If you want a reminder of the properties of this distribution, check out p. 85 of *AoS*.

__A:__ About 0.19.
This is what I expected because the normal distribution should be spread around 1. Then 1 is divided by the size (5 samples).

```python
# Calculate and print the mean here, hint: use np.mean()
np.mean(samples)
```

    0.19355593334131074

__Q4:__ What is the standard deviation of these numbers? Calculate this manually here as $\sqrt{\frac{\sum_i(x_i - \bar{x})^2}{n}}$ (This is just the definition of **standard deviation** given by Professor Spiegelhalter on p.403 of *AoS*). Hint: np.sqrt() and np.sum() will be useful here and remember that numPy supports [broadcasting](https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html).

__A:__ Approximately 0.96.

```python
np.sqrt(np.sum((samples - samples.mean()) ** 2) / len(samples))
```

    0.9606195639478641

Here we have calculated the actual standard deviation of a small data set (of size 5). But in this case, this small data set is actually a sample from our larger (infinite) population. In this case, the population is infinite because we could keep drawing our normal random variates until our computers die!

In general, the sample mean we calculate will not be equal to the population mean (as we saw above). A consequence of this is that the sum of squares of the deviations from the _population_ mean will be bigger than the sum of squares of the deviations from the _sample_ mean. In other words, the sum of squares of the deviations from the _sample_ mean is too small to give an unbiased estimate of the _population_ variance. An example of this effect is given [here](https://en.wikipedia.org/wiki/Bessel%27s_correction#Source_of_bias). Scaling our estimate of the variance by the factor $n/(n-1)$ gives an unbiased estimator of the population variance. This factor is known as [Bessel's correction](https://en.wikipedia.org/wiki/Bessel%27s_correction). The consequence of this is that the $n$ in the denominator is replaced by $n-1$.

You can see Bessel's correction reflected in Professor Spiegelhalter's definition of **variance** on p. 405 of *AoS*.

__Q5:__ If all we had to go on was our five samples, what would be our best estimate of the population standard deviation? Use Bessel's correction ($n-1$ in the denominator), thus $\sqrt{\frac{\sum_i(x_i - \bar{x})^2}{n-1}}$.

__A:__ Approximately 1.07.

```python
np.sqrt(np.sum((samples - samples.mean()) ** 2) / (len(samples) - 1))
```

    1.0740053227518152

__Q6:__ Now use numpy's std function to calculate the standard deviation of our random samples. Which of the above standard deviations did it return?

__A:__ The first (regular) standard deviation calculation.

```python
np.std(samples)
```

    0.9606195639478641

__Q7:__ Consult the documentation for np.std() to see how to apply the correction for estimating the population parameter and verify this produces the expected result.

__A:__ The ddof parameter of 1 returns the same result of about 1.07 as above.

```python
# np.std? only returns the help in the Jupyter console.
help(np.std)
```

    Help on function std in module numpy:

    std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=<no value>)
        Compute the standard deviation along the specified axis.

        Returns the standard deviation, a measure of the spread of a distribution,
        of the array elements. The standard deviation is computed for the
        flattened array by default, otherwise over the specified axis.

        Parameters
        ----------
        a : array_like
            Calculate the standard deviation of these values.
        axis : None or int or tuple of ints, optional
            Axis or axes along which the standard deviation is computed. The
            default is to compute the standard deviation of the flattened array.

            .. versionadded:: 1.7.0

            If this is a tuple of ints, a standard deviation is performed over
            multiple axes, instead of a single axis or all the axes as before.
        dtype : dtype, optional
            Type to use in computing the standard deviation. For arrays of
            integer type the default is float64, for arrays of float types it is
            the same as the array type.
        out : ndarray, optional
            Alternative output array in which to place the result. It must have
            the same shape as the expected output but the type (of the calculated
            values) will be cast if necessary.
        ddof : int, optional
            Means Delta Degrees of Freedom.  The divisor used in calculations
            is ``N - ddof``, where ``N`` represents the number of elements.
            By default `ddof` is zero.
        keepdims : bool, optional
            If this is set to True, the axes which are reduced are left
            in the result as dimensions with size one. With this option,
            the result will broadcast correctly against the input array.

            If the default value is passed, then `keepdims` will not be
            passed through to the `std` method of sub-classes of
            `ndarray`, however any non-default value will be.  If the
            sub-class' method does not implement `keepdims` any
            exceptions will be raised.

        Returns
        -------
        standard_deviation : ndarray, see dtype parameter above.
            If `out` is None, return a new array containing the standard deviation,
            otherwise return a reference to the output array.

        See Also
        --------
        var, mean, nanmean, nanstd, nanvar
        ufuncs-output-type

        Notes
        -----
        The standard deviation is the square root of the average of the squared
        deviations from the mean, i.e., ``std = sqrt(mean(abs(x - x.mean())**2))``.

        The average squared deviation is normally calculated as
        ``x.sum() / N``, where ``N = len(x)``.  If, however, `ddof` is specified,
        the divisor ``N - ddof`` is used instead. In standard statistical
        practice, ``ddof=1`` provides an unbiased estimator of the variance
        of the infinite population. ``ddof=0`` provides a maximum likelihood
        estimate of the variance for normally distributed variables. The
        standard deviation computed in this function is the square root of
        the estimated variance, so even with ``ddof=1``, it will not be an
        unbiased estimate of the standard deviation per se.

        Note that, for complex numbers, `std` takes the absolute
        value before squaring, so that the result is always real and nonnegative.

        For floating-point input, the *std* is computed using the same
        precision the input has. Depending on the input data, this can cause
        the results to be inaccurate, especially for float32 (see example below).
        Specifying a higher-accuracy accumulator using the `dtype` keyword can
        alleviate this issue.

        Examples
        --------
        >>> a = np.array([[1, 2], [3, 4]])
        >>> np.std(a)
        1.1180339887498949 # may vary
        >>> np.std(a, axis=0)
        array([1.,  1.])
        >>> np.std(a, axis=1)
        array([0.5,  0.5])

        In single precision, std() can be inaccurate:

        >>> a = np.zeros((2, 512*512), dtype=np.float32)
        >>> a[0, :] = 1.0
        >>> a[1, :] = 0.1
        >>> np.std(a)
        0.45000005

        Computing the standard deviation in float64 is more accurate:

        >>> np.std(a, dtype=np.float64)
        0.44999999925494177 # may vary

```python
np.std(samples, ddof=1)
```

    1.0740053227518152

### Summary of section

In this section, you've been introduced to the scipy.stats package and used it to draw a small sample from the standard normal distribution. You've calculated the average (the mean) of this sample and seen that this is not exactly equal to the expected population parameter (which we know because we're generating the random variates from a specific, known distribution). You've been introduced to two ways of calculating the standard deviation; one uses $n$ in the denominator and the other uses $n-1$ (Bessel's correction). You've also seen which of these calculations np.std() performs by default and how to get it to generate the other.

You use $n$ as the denominator if you want to calculate the standard deviation of a sequence of numbers. You use $n-1$ if you are using this sequence of numbers to estimate the population parameter. This brings us to some terminology that can be a little confusing.

The population parameter is traditionally written as $\sigma$ and the sample statistic as $s$. Rather unhelpfully, $s$ is also called the sample standard deviation (using $n-1$) whereas the standard deviation of the sample uses $n$. That's right, we have the sample standard deviation and the standard deviation of the sample and they're not the same thing!

The sample standard deviation
\begin{equation}
s = \sqrt{\frac{\sum_i(x_i - \bar{x})^2}{n-1}} \approx \sigma,
\end{equation}
is our best (unbiased) estimate of the population parameter ($\sigma$).

If your dataset _is_ your entire population, you simply want to calculate the population parameter, $\sigma$, via
\begin{equation}
\sigma = \sqrt{\frac{\sum_i(x_i - \bar{x})^2}{n}}
\end{equation}
as you have complete, full knowledge of your population. In other words, your sample _is_ your population. It's worth noting that we're dealing with what Professor Spiegehalter describes on p. 92 of *AoS* as a **metaphorical population**: we have all the data, and we act as if the data-point is taken from a population at random. We can think of this population as an imaginary space of possibilities.

If, however, you have sampled _from_ your population, you only have partial knowledge of the state of your population. In this case, the standard deviation of your sample is not an unbiased estimate of the standard deviation of the population, in which case you seek to estimate that population parameter via the sample standard deviation, which uses the $n-1$ denominator.

Great work so far! Now let's dive deeper.

## 3. Sampling distributions

So far we've been dealing with the concept of taking a sample from a population to infer the population parameters. One statistic we calculated for a sample was the mean. As our samples will be expected to vary from one draw to another, so will our sample statistics. If we were to perform repeat draws of size $n$ and calculate the mean of each, we would expect to obtain a distribution of values. This is the sampling distribution of the mean. **The Central Limit Theorem (CLT)** tells us that such a distribution will approach a normal distribution as $n$ increases (the intuitions behind the CLT are covered in full on p. 236 of *AoS*). For the sampling distribution of the mean, the standard deviation of this distribution is given by

\begin{equation}
\sigma_{mean} = \frac{\sigma}{\sqrt n}
\end{equation}

where $\sigma_{mean}$ is the standard deviation of the sampling distribution of the mean and $\sigma$ is the standard deviation of the population (the population parameter).

This is important because typically we are dealing with samples from populations and all we know about the population is what we see in the sample. From this sample, we want to make inferences about the population. We may do this, for example, by looking at the histogram of the values and by calculating the mean and standard deviation (as estimates of the population parameters), and so we are intrinsically interested in how these quantities vary across samples.

In other words, now that we've taken one sample of size $n$ and made some claims about the general population, what if we were to take another sample of size $n$? Would we get the same result? Would we make the same claims about the general population? This brings us to a fundamental question: _when we make some inference about a population based on our sample, how confident can we be that we've got it 'right'?_

We need to think about **estimates and confidence intervals**: those concepts covered in Chapter 7, p. 189, of *AoS*.

Now, the standard normal distribution (with its variance equal to its standard deviation of one) would not be a great illustration of a key point. Instead, let's imagine we live in a town of 50,000 people and we know the height of everyone in this town. We will have 50,000 numbers that tell us everything about our population. We'll simulate these numbers now and put ourselves in one particular town, called 'town 47', where the population mean height is 172 cm and population standard deviation is 5 cm.

```python
seed(47)
pop_heights = norm.rvs(172, 5, size=50000)
```

```python
_ = plt.hist(pop_heights, bins=30)
_ = plt.xlabel('height (cm)')
_ = plt.ylabel('number of people')
_ = plt.title('Distribution of heights in entire town population')
_ = plt.axvline(172, color='r')
_ = plt.axvline(172+5, color='r', linestyle='--')
_ = plt.axvline(172-5, color='r', linestyle='--')
_ = plt.axvline(172+10, color='r', linestyle='-.')
_ = plt.axvline(172-10, color='r', linestyle='-.')
```

Now, 50,000 people is rather a lot to chase after with a tape measure. If all you want to know is the average height of the townsfolk, then can you just go out and measure a sample to get a pretty good estimate of the average height?

```python
def townsfolk_sampler(n):
    return np.random.choice(pop_heights, n)
```

Let's say you go out one day and randomly sample 10 people to measure.

```python
seed(47)
daily_sample1 = townsfolk_sampler(10)
```

```python
_ = plt.hist(daily_sample1, bins=10)
_ = plt.xlabel('height (cm)')
_ = plt.ylabel('number of people')
_ = plt.title('Distribution of heights in sample size 10')
```

The sample distribution doesn't resemble what we take the population distribution to be. What do we get for the mean?

```python
np.mean(daily_sample1)
```

    173.47911444163503

And if we went out and repeated this experiment?

```python
daily_sample2 = townsfolk_sampler(10)
```

```python
np.mean(daily_sample2)
```

    173.7317666636263

__Q8:__ Simulate performing this random trial every day for a year, calculating the mean of each daily sample of 10, and plot the resultant sampling distribution of the mean.

__A:__

```python
seed(47)
# take your samples here
for day in range(365):
    print(f"Sample for day {day + 1} was {np.mean(townsfolk_sampler(10))}")
```

    Sample for day 1 was 173.00937310417513
    Sample for day 2 was 170.2661643961573
    Sample for day 3 was 174.34598844844118
    Sample for day 4 was 170.785406800034
    Sample for day 5 was 173.31770470569631
    Sample for day 6 was 173.10858686641774
    Sample for day 7 was 171.40439332248283
    Sample for day 8 was 170.9704617042305
    Sample for day 9 was 172.61510636545793
    Sample for day 10 was 172.2913740885055
    Sample for day 11 was 170.50358941424687
    Sample for day 12 was 172.22018481582614
    Sample for day 13 was 172.85834358816803
    Sample for day 14 was 171.56620891479838
    Sample for day 15 was 171.58204113512346
    Sample for day 16 was 171.07473473402555
    Sample for day 17 was 175.2047218243162
    Sample for day 18 was 172.20101905509054
    Sample for day 19 was 175.8140325675064
    Sample for day 20 was 171.42567364667013
    Sample for day 21 was 171.54879166928384
    Sample for day 22 was 173.37962048578632
    Sample for day 23 was 170.84926519404007
    Sample for day 24 was 174.59322186598968
    Sample for day 25 was 171.54718475118278
    Sample for day 26 was 171.6096336712505
    Sample for day 27 was 171.53767794655576
    Sample for day 28 was 172.9149498945323
    Sample for day 29 was 172.2981516718446
    Sample for day 30 was 165.39551194077626
    Sample for day 31 was 169.9597376836921
    Sample for day 32 was 173.9465941840398
    Sample for day 33 was 172.1342306069537
    Sample for day 34 was 171.3984656489666
    Sample for day 35 was 171.11161431266052
    Sample for day 36 was 173.6267218608726
    Sample for day 37 was 169.11050233231748
    Sample for day 38 was 169.69609920441803
    Sample for day 39 was 172.4816825903941
    Sample for day 40 was 172.35465226352488
    Sample for day 41 was 170.4018730294428
    Sample for day 42 was 172.6410928824817
    Sample for day 43 was 171.34876456725738
    Sample for day 44 was 172.84629108546204
    Sample for day 45 was 175.26564169319403
    Sample for day 46 was 168.68677877662915
    Sample for day 47 was 173.01832873955627
    Sample for day 48 was 169.56393772762289
    Sample for day 49 was 172.99035886037572
    Sample for day 50 was 175.3707428706809
    Sample for day 51 was 171.68166141416253
    Sample for day 52 was 172.21351476973385
    Sample for day 53 was 173.57719464559077
    Sample for day 54 was 172.23443258433025
    Sample for day 55 was 171.49321124063263
    Sample for day 56 was 175.0569955524844
    Sample for day 57 was 169.7489045337734
    Sample for day 58 was 170.2576081367393
    Sample for day 59 was 173.00527760461273
    Sample for day 60 was 169.41958867850704
    Sample for day 61 was 171.09210131077157
    Sample for day 62 was 174.09652244869528
    Sample for day 63 was 173.97372431777853
    Sample for day 64 was 170.76960029551344
    Sample for day 65 was 173.91299863576833
    Sample for day 66 was 172.77281552568883
    Sample for day 67 was 171.17248840522046
    Sample for day 68 was 172.6754158361887
    Sample for day 69 was 174.95950548649049
    Sample for day 70 was 174.5280861190028
    Sample for day 71 was 169.3587222486768
    Sample for day 72 was 172.19628668598872
    Sample for day 73 was 173.47675542556266
    Sample for day 74 was 171.8867463490586
    Sample for day 75 was 171.64766944047537
    Sample for day 76 was 172.03472701707668
    Sample for day 77 was 171.8514968514924
    Sample for day 78 was 173.3504076956295
    Sample for day 79 was 175.835465920465
    Sample for day 80 was 173.01619729265536
    Sample for day 81 was 172.87431639983677
    Sample for day 82 was 171.27137361530023
    Sample for day 83 was 169.08324493645043
    Sample for day 84 was 173.68116250421124
    Sample for day 85 was 170.11358709792825
    Sample for day 86 was 171.1893750210999
    Sample for day 87 was 169.25468169001886
    Sample for day 88 was 169.2150994830036
    Sample for day 89 was 171.596721889334
    Sample for day 90 was 173.77774156427014
    Sample for day 91 was 173.03004628460803
    Sample for day 92 was 172.34242485010785
    Sample for day 93 was 172.9855405060567
    Sample for day 94 was 169.43469092853624
    Sample for day 95 was 171.77975348011097
    Sample for day 96 was 172.64844848584667
    Sample for day 97 was 171.56408093054327
    Sample for day 98 was 169.95379792250952
    Sample for day 99 was 171.12137486338096
    Sample for day 100 was 171.73249633181402
    Sample for day 101 was 172.0630011932523
    Sample for day 102 was 172.834180845258
    Sample for day 103 was 172.38388837514353
    Sample for day 104 was 170.54584084764085
    Sample for day 105 was 171.99296673596194
    Sample for day 106 was 173.42344336887487
    Sample for day 107 was 170.69610500534776
    Sample for day 108 was 173.36937790530678
    Sample for day 109 was 174.7423134134954
    Sample for day 110 was 171.57490485555303
    Sample for day 111 was 171.25352997756042
    Sample for day 112 was 173.72474189207932
    Sample for day 113 was 172.441773023841
    Sample for day 114 was 173.80765705457642
    Sample for day 115 was 170.96908036245844
    Sample for day 116 was 170.5646444001596
    Sample for day 117 was 171.16932302392036
    Sample for day 118 was 171.7865759467937
    Sample for day 119 was 174.05858516618719
    Sample for day 120 was 171.6143448222104
    Sample for day 121 was 174.26791917555542
    Sample for day 122 was 172.75750544038792
    Sample for day 123 was 169.4482347475428
    Sample for day 124 was 172.88259602544014
    Sample for day 125 was 173.3483004197289
    Sample for day 126 was 169.76084247981902
    Sample for day 127 was 169.14347393153977
    Sample for day 128 was 171.4217504513107
    Sample for day 129 was 173.89834379492194
    Sample for day 130 was 170.3495147245446
    Sample for day 131 was 172.17203741079754
    Sample for day 132 was 172.79214318105068
    Sample for day 133 was 175.10499281941355
    Sample for day 134 was 173.18876387302893
    Sample for day 135 was 174.81414282425817
    Sample for day 136 was 173.51971821349957
    Sample for day 137 was 169.1832903415072
    Sample for day 138 was 172.44643036845486
    Sample for day 139 was 170.37438239142895
    Sample for day 140 was 170.5128178545061
    Sample for day 141 was 172.7603336967199
    Sample for day 142 was 173.43295705827208
    Sample for day 143 was 172.44986889632654
    Sample for day 144 was 168.54115045199467
    Sample for day 145 was 171.47237444495545
    Sample for day 146 was 172.28422187204686
    Sample for day 147 was 169.31812658254867
    Sample for day 148 was 171.9843089839522
    Sample for day 149 was 172.5937581563948
    Sample for day 150 was 173.23557646642925
    Sample for day 151 was 172.45242838151756
    Sample for day 152 was 172.95373798288568
    Sample for day 153 was 169.31196279581437
    Sample for day 154 was 169.68424533261566
    Sample for day 155 was 173.09559250773697
    Sample for day 156 was 170.5045876184657
    Sample for day 157 was 170.77385661410713
    Sample for day 158 was 173.1993206004434
    Sample for day 159 was 169.41827455268532
    Sample for day 160 was 172.69828423736095
    Sample for day 161 was 171.80810764017772
    Sample for day 162 was 171.83850781893216
    Sample for day 163 was 173.5753616187581
    Sample for day 164 was 170.78294074321053
    Sample for day 165 was 167.7625596819958
    Sample for day 166 was 173.63599353895043
    Sample for day 167 was 172.35793394439912
    Sample for day 168 was 172.42914238209988
    Sample for day 169 was 170.71751228569852
    Sample for day 170 was 171.54000560683969
    Sample for day 171 was 173.188789929698
    Sample for day 172 was 172.62322681495678
    Sample for day 173 was 172.6508432608691
    Sample for day 174 was 171.42006139790251
    Sample for day 175 was 172.4712247126185
    Sample for day 176 was 170.3786488645244
    Sample for day 177 was 172.73731268204696
    Sample for day 178 was 172.3000446236936
    Sample for day 179 was 170.7649928023232
    Sample for day 180 was 169.9141005121299
    Sample for day 181 was 172.42333039503097
    Sample for day 182 was 171.61277999714807
    Sample for day 183 was 170.60637508298126
    Sample for day 184 was 171.76476298366762
    Sample for day 185 was 170.41303623484504
    Sample for day 186 was 172.47393077457045
    Sample for day 187 was 171.3194342008746
    Sample for day 188 was 169.5841940850787
    Sample for day 189 was 170.52305891287497
    Sample for day 190 was 174.13981403506384
    Sample for day 191 was 171.57249535993967
    Sample for day 192 was 172.92969865919665
    Sample for day 193 was 170.71069014088408
    Sample for day 194 was 172.70087709251987
    Sample for day 195 was 171.02564174035243
    Sample for day 196 was 174.5655176759607
    Sample for day 197 was 173.2373307135623
    Sample for day 198 was 169.72435883757208
    Sample for day 199 was 171.4080637212518
    Sample for day 200 was 172.82472795827337
    Sample for day 201 was 172.68087344401215
    Sample for day 202 was 170.73816995930957
    Sample for day 203 was 173.27623446278108
    Sample for day 204 was 174.00762188244605
    Sample for day 205 was 173.13361473414275
    Sample for day 206 was 170.84245444649585
    Sample for day 207 was 173.38610121883
    Sample for day 208 was 171.0638349843619
    Sample for day 209 was 171.126280719832
    Sample for day 210 was 172.73680722414176
    Sample for day 211 was 170.48813262391832
    Sample for day 212 was 173.8065513385304
    Sample for day 213 was 174.987975821513
    Sample for day 214 was 170.03229177775182
    Sample for day 215 was 175.02529474715647
    Sample for day 216 was 173.40098890648693
    Sample for day 217 was 171.44694390778417
    Sample for day 218 was 174.3025151813375
    Sample for day 219 was 173.4280196820072
    Sample for day 220 was 171.33423913799567
    Sample for day 221 was 171.62893394353907
    Sample for day 222 was 174.71937083523463
    Sample for day 223 was 173.6777821451332
    Sample for day 224 was 173.29205813062757
    Sample for day 225 was 171.48099822052652
    Sample for day 226 was 174.7643867716951
    Sample for day 227 was 174.21143537234744
    Sample for day 228 was 171.77420202846264
    Sample for day 229 was 171.37841143093172
    Sample for day 230 was 172.18616002136272
    Sample for day 231 was 172.3111613339467
    Sample for day 232 was 171.77236918473153
    Sample for day 233 was 169.4252121074236
    Sample for day 234 was 171.16984338312017
    Sample for day 235 was 171.98592378485796
    Sample for day 236 was 170.66765933964413
    Sample for day 237 was 173.07633301699337
    Sample for day 238 was 172.55483298565144
    Sample for day 239 was 170.02605126977423
    Sample for day 240 was 171.4680428484353
    Sample for day 241 was 171.98907654608053
    Sample for day 242 was 175.02655281778826
    Sample for day 243 was 171.07855120204874
    Sample for day 244 was 170.51520740788092
    Sample for day 245 was 172.48598843478018
    Sample for day 246 was 172.1474353242007
    Sample for day 247 was 169.2709521164695
    Sample for day 248 was 172.5087810017655
    Sample for day 249 was 172.95952188635115
    Sample for day 250 was 170.5105096194364
    Sample for day 251 was 173.80365699123186
    Sample for day 252 was 173.20783401436017
    Sample for day 253 was 172.30853501437937
    Sample for day 254 was 171.3292027460107
    Sample for day 255 was 170.1284541620547
    Sample for day 256 was 170.53153661961474
    Sample for day 257 was 169.99233807038905
    Sample for day 258 was 172.2060568309715
    Sample for day 259 was 172.59375266931607
    Sample for day 260 was 173.13187918050644
    Sample for day 261 was 173.84225403798737
    Sample for day 262 was 172.16900966778172
    Sample for day 263 was 171.2740795246999
    Sample for day 264 was 172.06848748155048
    Sample for day 265 was 172.70806798793316
    Sample for day 266 was 169.52191788351348
    Sample for day 267 was 173.13995943698018
    Sample for day 268 was 171.31446586385138
    Sample for day 269 was 174.45944054257342
    Sample for day 270 was 172.33779383789957
    Sample for day 271 was 170.04050400074735
    Sample for day 272 was 170.5897937787512
    Sample for day 273 was 172.381119795683
    Sample for day 274 was 171.2191777049789
    Sample for day 275 was 174.13679937916376
    Sample for day 276 was 171.58968685112407
    Sample for day 277 was 172.14155987323056
    Sample for day 278 was 170.14580076222987
    Sample for day 279 was 173.8575126095746
    Sample for day 280 was 171.22280004171273
    Sample for day 281 was 174.50071744849237
    Sample for day 282 was 172.88891068451716
    Sample for day 283 was 169.31889881116254
    Sample for day 284 was 170.69600548765348
    Sample for day 285 was 171.42981400026548
    Sample for day 286 was 172.50472870805683
    Sample for day 287 was 171.51334191192277
    Sample for day 288 was 170.08549988158256
    Sample for day 289 was 172.5517746579218
    Sample for day 290 was 170.35377108926656
    Sample for day 291 was 173.3479274356198
    Sample for day 292 was 168.98144965130814
    Sample for day 293 was 174.43697752031915
    Sample for day 294 was 174.24488590135522
    Sample for day 295 was 171.75499841402396
    Sample for day 296 was 172.2505806984
    Sample for day 297 was 172.13537084694025
    Sample for day 298 was 168.91730244778347
    Sample for day 299 was 171.85383633190443
    Sample for day 300 was 171.44332622752884
    Sample for day 301 was 171.98065353587435
    Sample for day 302 was 174.67545641644853
    Sample for day 303 was 169.27456293913542
    Sample for day 304 was 171.98544346762102
    Sample for day 305 was 171.71523803475168
    Sample for day 306 was 171.66213269382746
    Sample for day 307 was 171.112298762341
    Sample for day 308 was 170.77343371955163
    Sample for day 309 was 172.20311106521876
    Sample for day 310 was 169.99680356458154
    Sample for day 311 was 172.95196752111943
    Sample for day 312 was 176.75728819085288
    Sample for day 313 was 171.8196727050369
    Sample for day 314 was 170.71102865921227
    Sample for day 315 was 168.0443984080638
    Sample for day 316 was 172.71396733459656
    Sample for day 317 was 168.70848675599822
    Sample for day 318 was 171.76101124195003
    Sample for day 319 was 173.73259618312758
    Sample for day 320 was 172.39938678401919
    Sample for day 321 was 172.4348027054093
    Sample for day 322 was 172.4558659621563
    Sample for day 323 was 170.7107801353672
    Sample for day 324 was 172.51742285335624
    Sample for day 325 was 172.24819759923054
    Sample for day 326 was 174.39776477155866
    Sample for day 327 was 172.01380734487162
    Sample for day 328 was 172.8420396499487
    Sample for day 329 was 172.34460031959003
    Sample for day 330 was 170.78349067379074
    Sample for day 331 was 173.0535374392026
    Sample for day 332 was 172.81474736800695
    Sample for day 333 was 170.59751924018238
    Sample for day 334 was 171.81195373983633
    Sample for day 335 was 173.14301505727445
    Sample for day 336 was 173.0867067005878
    Sample for day 337 was 172.49120180031275
    Sample for day 338 was 172.79245354383073
    Sample for day 339 was 168.77864347190467
    Sample for day 340 was 172.85250539601859
    Sample for day 341 was 171.51847211854056
    Sample for day 342 was 168.79667318837573
    Sample for day 343 was 171.98321088302234
    Sample for day 344 was 171.93952803545892
    Sample for day 345 was 173.98082900081164
    Sample for day 346 was 170.42434934870033
    Sample for day 347 was 172.80981621822798
    Sample for day 348 was 171.43800984364023
    Sample for day 349 was 169.13060750544295
    Sample for day 350 was 170.68510962199605
    Sample for day 351 was 171.6445361884467
    Sample for day 352 was 173.21043754817526
    Sample for day 353 was 169.11260696642978
    Sample for day 354 was 170.3325375027076
    Sample for day 355 was 171.78168029566487
    Sample for day 356 was 172.3123996044116
    Sample for day 357 was 170.1283454398062
    Sample for day 358 was 174.0205474832662
    Sample for day 359 was 170.3304690943122
    Sample for day 360 was 171.93335633113756
    Sample for day 361 was 170.89875086405297
    Sample for day 362 was 175.5202730928333
    Sample for day 363 was 171.85429378017477
    Sample for day 364 was 171.2142851564963
    Sample for day 365 was 172.26925019233641

```python
seed(47)
# Or the Pythonic way
daily_sample_means = np.array([np.mean(townsfolk_sampler(10)) for i in range(365)])
```

```python
_ = plt.hist(daily_sample_means, bins=10)
_ = plt.xlabel('height (cm)')
_ = plt.ylabel('number of people')
_ = plt.title('Distribution of heights in sample size 10')
```

The above is the distribution of the means of samples of size 10 taken from our population. The Central Limit Theorem tells us the expected mean of this distribution will be equal to the population mean, and standard deviation will be $\sigma / \sqrt n$, which, in this case, should be approximately 1.58.

__Q9:__ Verify the above results from the CLT.

__A:__ This is approximately 1.58.

```python
np.std(daily_sample_means, ddof=1)
```

    1.5778333114768472

Remember, in this instance, we knew our population parameters, that the average height really is 172 cm and the standard deviation is 5 cm, and we see some of our daily estimates of the population mean were as low as around 168 and some as high as 176.

__Q10:__ Repeat the above year's worth of samples but for a sample size of 50 (perhaps you had a bigger budget for conducting surveys that year)! Would you expect your distribution of sample means to be wider (more variable) or narrower (more consistent)? Compare your resultant summary statistics to those predicted by the CLT.

__A:__ The larger sample size of 50 is more normally distributed with a narrower range. This is expected as the sample size becomes more representative of the population.

```python
seed(47)
# calculate daily means from the larger sample size here
daily_sample_means_50 = np.array([np.mean(townsfolk_sampler(50)) for i in range(365)])
```

```python
_ = plt.hist(daily_sample_means_50, bins=10)
_ = plt.xlabel('height (cm)')
_ = plt.ylabel('number of people')
_ = plt.title('Distribution of heights in sample size 10')

```

```python
np.std(daily_sample_means_50, ddof=1)
```

    0.6745354088447525

What we've seen so far, then, is that we can estimate population parameters from a sample from the population, and that samples have their own distributions. Furthermore, the larger the sample size, the narrower are those sampling distributions.

### Normally testing time!

All of the above is well and good. We've been sampling from a population we know is normally distributed, we've come to understand when to use $n$ and when to use $n-1$ in the denominator to calculate the spread of a distribution, and we've  seen the Central Limit Theorem in action for a sampling distribution. All seems very well behaved in Frequentist land. But, well, why should we really care?

Remember, we rarely (if ever) actually know our population parameters but we still have to estimate them somehow. If we want to make inferences to conclusions like "this observation is unusual" or "my population mean has changed" then we need to have some idea of what the underlying distribution is so we can calculate relevant probabilities. In frequentist inference, we use the formulae above to deduce these population parameters. Take a moment in the next part of this assignment to refresh your understanding of how these probabilities work.

Recall some basic properties of the standard normal distribution, such as that about 68% of observations are within plus or minus 1 standard deviation of the mean. Check out the precise definition of a normal distribution on p. 394 of *AoS*.

__Q11:__ Using this fact, calculate the probability of observing the value 1 or less in a single observation from the standard normal distribution. Hint: you may find it helpful to sketch the standard normal distribution (the familiar bell shape) and mark the number of standard deviations from the mean on the x-axis and shade the regions of the curve that contain certain percentages of the population.

__A:__

```python
1 - ((1 - 0.68) / 2)
```

    0.8400000000000001

Calculating this probability involved calculating the area under the curve from the value of 1 and below. To put it in mathematical terms, we need to *integrate* the probability density function. We could just add together the known areas of chunks (from -Inf to 0 and then 0 to $+\sigma$ in the example above). One way to do this is to look up tables (literally). Fortunately, scipy has this functionality built in with the cdf() function.

__Q12:__ Use the cdf() function to answer the question above again and verify you get the same answer.

__A:__ The two answers are the same.

```python
norm.cdf(1)
```

    0.8413447460685429

__Q13:__ Using our knowledge of the population parameters for our townsfolks' heights, what is the probability of selecting one person at random and their height being 177 cm or less? Calculate this using both of the approaches given above.

NOTE: Assuming the following questions are using the actual population mean (172) and standard deviation (5) given in the description above.

__A:__ There is about an 84% chance of selecting someone that is 177 cm or less from this population.

```python
norm(172, 5).cdf(177)
```

    0.8413447460685429

__Q14:__ Turning this question around — suppose we randomly pick one person and measure their height and find they are 2.00 m tall. How surprised should we be at this result, given what we know about the population distribution? In other words, how likely would it be to obtain a value at least as extreme as this? Express this as a probability.

__A:__ This is VERY surprising. There is almost has no probability of it happening. It could be a measurement error or someone from out of town.

```python
1 - norm(172, 5).cdf(200)
```

    1.0717590259723409e-08

What we've just done is calculate the ***p-value*** of the observation of someone 2.00m tall (review *p*-values if you need to on p. 399 of *AoS*). We could calculate this probability by virtue of knowing the population parameters. We were then able to use the known properties of the relevant normal distribution to calculate the probability of observing a value at least as extreme as our test value.

We're about to come to a pinch, though. We've said a couple of times that we rarely, if ever, know the true population parameters; we have to estimate them from our sample and we cannot even begin to estimate the standard deviation from a single observation.

This is very true and usually we have sample sizes larger than one. This means we can calculate the mean of the sample as our best estimate of the population mean and the standard deviation as our best estimate of the population standard deviation.

In other words, we are now coming to deal with the sampling distributions we mentioned above as we are generally concerned with the properties of the sample means we obtain.

Above, we highlighted one result from the CLT, whereby the sampling distribution (of the mean) becomes narrower and narrower with the square root of the sample size. We remind ourselves that another result from the CLT is that _even if the underlying population distribution is not normal, the sampling distribution will tend to become normal with sufficiently large sample size_. (**Check out p. 199 of AoS if you need to revise this**). This is the key driver for us 'requiring' a certain sample size, for example you may frequently see a minimum sample size of 30 stated in many places. In reality this is simply a rule of thumb; if the underlying distribution is approximately normal then your sampling distribution will already be pretty normal, but if the underlying distribution is heavily skewed then you'd want to increase your sample size.

__Q15:__ Let's now start from the position of knowing nothing about the heights of people in our town.
* Use the random seed of 47, to randomly sample the heights of 50 townsfolk
* Estimate the population mean using np.mean
* Estimate the population standard deviation using np.std (remember which denominator to use!)
* Calculate the (95%) [margin of error](https://www.statisticshowto.datasciencecentral.com/probability-and-statistics/hypothesis-testing/margin-of-error/#WhatMofE) (use the exact critial z value to 2 decimal places - [look this up](https://www.statisticshowto.datasciencecentral.com/probability-and-statistics/find-critical-values/) or use norm.ppf()) Recall that the ***margin of error*** is mentioned on p. 189 of the *AoS* and discussed in depth in that chapter).
* Calculate the 95% Confidence Interval of the mean (***confidence intervals*** are defined on p. 385 of *AoS*)
* Does this interval include the true population mean?

__A:__

```python
seed(47)
sample_size = 50
# take your sample now
sample = townsfolk_sampler(sample_size)
```

```python
mean_sample = np.mean(sample)
print(f"Mean is: {mean_sample}.")
```

    Mean is: 172.7815108576788.

```python
std_sample = np.std(sample)
print(f"Standard deviation is: {std_sample}.")
```

    Standard deviation is: 4.153258225264712.

```python
# 95% margin of error has 2 tails of rejection:
# 1) 100% - 95% = 5%. 2) 5% / 2 = 0.025. 3) 1 - 0.025 = 0.975.
critical_value = norm.ppf(0.975)
std_error = std_sample / np.sqrt(sample_size)
margin_of_error = critical_value * std_error
print(f"Margin of error is: {margin_of_error}.")
```

    Margin of error is: 1.151203291581224.

```python
lower = mean_sample - margin_of_error
upper = mean_sample + margin_of_error
ci = np.array([lower, upper])
print(f"The 95% confidence interval is: {ci}.")
```

    The 95% confidence interval is: [171.63030757 173.93271415].

__Q16:__ Above, we calculated the confidence interval using the critical z value. What is the problem with this? What requirement, or requirements, are we (strictly) failing?

__A:__ Only using one sample. This may or may not accurately reflect the population. The t distribution is most likely better to find the confidence interval.

__Q17:__ Calculate the 95% confidence interval for the mean using the _t_ distribution. Is this wider or narrower than that based on the normal distribution above? If you're unsure, you may find this [resource](https://www.statisticshowto.datasciencecentral.com/probability-and-statistics/confidence-interval/) useful. For calculating the critical value, remember how you could calculate this for the normal distribution using norm.ppf().

__A:__ The confidence interval using the t distribution is a little wider.

### Steps to calculate a Confidence Interval For a Sample
1) Subtract 1 from your sample size.
2) Subtract the confidence level from 1 and then divide by two.
3) Lookup answers from step 1 and 2 in t-distribution table or calculate them.
4) Divide sample standard deviation by the square root of the sample size.
5) Multiple step 3 and 4.
6) Subtract step 5 from the sample mean for the lower end of the range.
7) Add step 5 from the sample mean for the upper end of the range.

```python
# 50- 1 = 49.
first_steps = t(49).ppf([0.025, 0.975])
step_4 = std_sample / np.sqrt(sample_size)
step_5 = first_steps * step_4
last_steps = step_5 + mean_sample
last_steps
```

    array([171.60116793, 173.96185378])

This is slightly wider than the previous confidence interval. This reflects the greater uncertainty given that we are estimating population parameters from a sample.

## 4. Learning outcomes

Having completed this project notebook, you now have hands-on experience:
* sampling and calculating probabilities from a normal distribution
* identifying the correct way to estimate the standard deviation of a population (the population parameter) from a sample
* with sampling distribution and now know how the Central Limit Theorem applies
* with how to calculate critical values and confidence intervals

Frequentist Inference Case Study - Part A (3).ipynb


The `hilbert_space_algebra` module defines a simple algebra of finite dimensional or countably infinite dimensional Hilbert spaces.

Local/primitive degrees of freedom (e.g. a single multi-level atom or a cavity mode) are described by a LocalSpace; it requires a label, and may define a basis through the basis or dimension arguments. The LocalSpace may also define custom identifiers for operators acting on that space (subclasses of LocalOperator):

```
!pip install QNET
```

    Collecting QNET
      Downloading https://files.pythonhosted.org/packages/3f/16/3a4a4fddadf3106f798e76edef2c653c6fd3a196c6b5e79d32d1bdff47b7/QNET-1.4.3.tar.gz (1.2MB)
        100% |████████████████████████████████| 1.2MB 6.6MB/s
    Requirement already satisfied: matplotlib in /usr/local/lib/python3.6/dist-packages (from QNET) (2.1.2)
    Requirement already satisfied: sympy in /usr/local/lib/python3.6/dist-packages (from QNET) (1.1.1)
    Collecting ply (from QNET)
      Downloading https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl (49kB)
        100% |████████████████████████████████| 51kB 11.1MB/s
    Requirement already satisfied: six in /usr/local/lib/python3.6/dist-packages (from QNET) (1.11.0)
    Requirement already satisfied: numpy in /usr/local/lib/python3.6/dist-packages (from QNET) (1.14.3)
    Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.6/dist-packages (from matplotlib->QNET) (0.10.0)
    Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.6/dist-packages (from matplotlib->QNET) (2.5.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->QNET) (2.2.0)
    Requirement already satisfied: pytz in /usr/local/lib/python3.6/dist-packages (from matplotlib->QNET) (2018.4)
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.6/dist-packages (from sympy->QNET) (1.0.0)
    Building wheels for collected packages: QNET
      Running setup.py bdist_wheel for QNET ... - \ done
      Stored in directory: /content/.cache/pip/wheels/62/13/87/57448b1ce8443509706d7de2f99d3c7a033608b6ac80eac455
    Successfully built QNET
    Installing collected packages: ply, QNET
    Successfully installed QNET-1.4.3 ply-3.11

```
import qnet
from qnet.algebra import *
import sympy as sp
```

```
from sympy import symbols, I, sqrt
from qnet.algebra.circuit_algebra import Create, LocalSigma, SLH, Destroy, Matrix, identity_matrix
from qnet.algebra.operator_algebra import Destroy
s = Destroy(hs="s")
p = Destroy(hs="p")
gamma_p, gamma_s, epsilon, p0 = sp.symbols("gamma_p, gamma_s, epsilon, p0", positive=True)

S = -identity_matrix(2)
L = [sp.sqrt(2*gamma_p)*p, sp.sqrt(2*gamma_s)*s]
H = sp.I*epsilon/2 * p*s.dag()*s.dag() ; H = H + H.dag()
OPO = SLH(S,L,H).coherent_input(p0, 0)
OPO
```

$\left( \begin{pmatrix} {\rm -1} & {\rm 0} \\ {\rm 0} & {\rm -1}\end{pmatrix}, \begin{pmatrix} {\rm  - p_{0}} +  \sqrt{2} \sqrt{\gamma_{p}} {a_{{{\rm p}}}} \\  \sqrt{2} \sqrt{\gamma_{s}} {a_{{{\rm s}}}}\end{pmatrix}, {\rm  \frac{i}{2} \left({\rm  \sqrt{2} \sqrt{\gamma_{p}} p_{0} {a_{{{\rm p}}}^\dagger}} -  \sqrt{2} \sqrt{\gamma_{p}} p_{0} {a_{{{\rm p}}}}\right)} -  \frac{i \epsilon}{2}  {a_{{{\rm p}}}^\dagger} {a_{{{\rm s}}}} {a_{{{\rm s}}}} +  \frac{i \epsilon}{2}  {a_{{{\rm p}}}} {a_{{{\rm s}}}^\dagger} {a_{{{\rm s}}}^\dagger} \right)$

```
k = sp.symbols("k", positive=True)
OPO_prelimit = OPO.substitute({gamma_p : k**2*gamma_p, epsilon : k*epsilon})
OPO_prelimit
```

$\left( \begin{pmatrix} {\rm -1} & {\rm 0} \\ {\rm 0} & {\rm -1}\end{pmatrix}, \begin{pmatrix} {\rm  - p_{0}} +  \sqrt{2} \sqrt{\gamma_{p}} k {a_{{{\rm p}}}} \\  \sqrt{2} \sqrt{\gamma_{s}} {a_{{{\rm s}}}}\end{pmatrix}, {\rm  \frac{i}{2} \left({\rm  \sqrt{2} \sqrt{\gamma_{p}} k p_{0} {a_{{{\rm p}}}^\dagger}} -  \sqrt{2} \sqrt{\gamma_{p}} k p_{0} {a_{{{\rm p}}}}\right)} -  \frac{i \epsilon}{2} k  {a_{{{\rm p}}}^\dagger} {a_{{{\rm s}}}} {a_{{{\rm s}}}} +  \frac{i \epsilon}{2} k  {a_{{{\rm p}}}} {a_{{{\rm s}}}^\dagger} {a_{{{\rm s}}}^\dagger} \right)$

```
from qnet.algebra.circuit_algebra import SLH, try_adiabatic_elimination, prepare_adiabatic_limit
try_adiabatic_elimination(OPO_prelimit, k=k)
```

$\left( \begin{pmatrix} 1 & 0 \\ 0 &  -1\end{pmatrix}, \begin{pmatrix} {\rm  p_{0}} -  \frac{\sqrt{2} \epsilon}{2 \sqrt{\gamma_{p}}}  {a_{{{\rm s}}}} {a_{{{\rm s}}}} \\  \sqrt{2} \sqrt{\gamma_{s}} {a_{{{\rm s}}}}\end{pmatrix}, {\rm  \frac{\sqrt{2} i \epsilon p_{0}}{4 \sqrt{\gamma_{p}}}  {a_{{{\rm s}}}^\dagger} {a_{{{\rm s}}}^\dagger}} -  \frac{\sqrt{2} i \epsilon p_{0}}{4 \sqrt{\gamma_{p}}}  {a_{{{\rm s}}}} {a_{{{\rm s}}}} \right)$

```
s = Destroy(hs="s")
p = Destroy(hs="p")
gamma_p, gamma_s, epsilon, p0 = sp.symbols("gamma_p, gamma_s, epsilon, p0", positive=True)

S = -identity_matrix(2)
L = [sp.sqrt(2*gamma_p)*p, sp.sqrt(2*gamma_s)*s]
H = sp.I*epsilon/2 * p*s.dag()*s.dag() ; H = H + H.dag()
OPO = SLH(S,L,H).coherent_input(p0, 0)
OPO
```

$\left( \begin{pmatrix} {\rm -1} & {\rm 0} \\ {\rm 0} & {\rm -1}\end{pmatrix}, \begin{pmatrix} {\rm  - p_{0}} +  \sqrt{2} \sqrt{\gamma_{p}} {a_{{{\rm p}}}} \\  \sqrt{2} \sqrt{\gamma_{s}} {a_{{{\rm s}}}}\end{pmatrix}, {\rm  \frac{i}{2} \left({\rm  \sqrt{2} \sqrt{\gamma_{p}} p_{0} {a_{{{\rm p}}}^\dagger}} -  \sqrt{2} \sqrt{\gamma_{p}} p_{0} {a_{{{\rm p}}}}\right)} -  \frac{i \epsilon}{2}  {a_{{{\rm p}}}^\dagger} {a_{{{\rm s}}}} {a_{{{\rm s}}}} +  \frac{i \epsilon}{2}  {a_{{{\rm p}}}} {a_{{{\rm s}}}^\dagger} {a_{{{\rm s}}}^\dagger} \right)$

```
k = sp.symbols("k", positive=True)
OPO_prelimit = OPO.substitute({gamma_p : k**2*gamma_p, epsilon : k*epsilon})
OPO_prelimit
```

$\left( \begin{pmatrix} {\rm -1} & {\rm 0} \\ {\rm 0} & {\rm -1}\end{pmatrix}, \begin{pmatrix} {\rm  - p_{0}} +  \sqrt{2} \sqrt{\gamma_{p}} k {a_{{{\rm p}}}} \\  \sqrt{2} \sqrt{\gamma_{s}} {a_{{{\rm s}}}}\end{pmatrix}, {\rm  \frac{i}{2} \left({\rm  \sqrt{2} \sqrt{\gamma_{p}} k p_{0} {a_{{{\rm p}}}^\dagger}} -  \sqrt{2} \sqrt{\gamma_{p}} k p_{0} {a_{{{\rm p}}}}\right)} -  \frac{i \epsilon}{2} k  {a_{{{\rm p}}}^\dagger} {a_{{{\rm s}}}} {a_{{{\rm s}}}} +  \frac{i \epsilon}{2} k  {a_{{{\rm p}}}} {a_{{{\rm s}}}^\dagger} {a_{{{\rm s}}}^\dagger} \right)$

```
prepare_adiabatic_limit(OPO_prelimit, k=k)[2]
```

${\rm  - \frac{p_{0}^{2}}{2}} -  \gamma_{s}  {a_{{{\rm s}}}^\dagger} {a_{{{\rm s}}}}$


[](https://colab.research.google.com/github/deepkick/Python_of_meteorology/blob/main/01_Thermodynamics%20of%20the%20atmosphere%20Equation%20of%20state%20of%20an%20ideal%20gas.ipynb)

# 大気の熱力学

## 01. 理想気体の状態方程式

Thermodynamics of the atmosphere Equation of state of an ideal gas

容積: $V$
質量: $m$
圧力: $p$
温度: $T$
とすると、

\begin{equation}
pV = mRT \tag{3.1}
\end{equation}

という関係があり、これを**理想気体の状態方程式**という。

気体の密度 $\rho$ は $\rho = \frac{m}{V}$ で定義されるので、式$(3.1)$ は

\begin{equation}
p = \rho R T \tag{3.2}
\end{equation}

である。また比容 $\alpha = \frac{1}{\rho}$ を使えば、

\begin{equation}
p\alpha = RT \tag{3.3}
\end{equation}

である。

分子量: $M$
の気体が $m\ kg$ 存在するとき、その期待のキロモル数 $n$ は、

\begin{equation}
n = \frac{m}{M} \tag{3.4}
\end{equation}

である。

**アボガドロの法則**により、同じ数の分子を含む気体は同じ圧力・同じ温度のもとでは、同じ容積を占める。

したがって、1キロモルの気体について、$\frac{pV}{T}$ という量は、気体の種類に関係なく一定である。

\begin{equation}
\frac{pV}{T} = mR = MR = R^* \tag{3.5}
\end{equation}

温度: $T = 273.15\ K$
気圧: $p = 1013.25\ hPa = 1気圧$
のとき、1キロモルの気体の容積は、
容積: $V = 22.4135\ m^3$
であるから、式$(3.5)$ により、

\begin{equation}
R^* = 8.3143 × 10^3\ JK^{-1}kmol^{-1}
\end{equation}

であり、これを一般気体定数という。

混合気体の平均分子量 $\overline{M}$ 、$1 kg$ の混合気体の気体定数 $\overline{R}$ を定義すると、

\begin{equation}
\overline{R} = \frac{R^*}{\overline{M}} \tag{3.9}
\end{equation}

\begin{equation}
p\alpha = \overline{R}T \tag{3.10}
\end{equation}

である。

## 1. 地球大気について

$Table 1$

地表付近の大気組成（「一般気象学　第２版補訂版」 p13, 表1.4 より）

|  成分  |  分子式  |  分子量  |  容積比（存在比率%）  |  重量比（存在比率%）  |
| ---- | ---- | ---- | ---- | ---- |
|  窒素分子  |  $N_2$  |  $28.01$  |  $78.088$  |  $75.527$  |
|  酸素分子  |  $O_2$  |  $32.00$  |  $20.949$  |  $23.413$  |
|  アルゴン  |  $Ar$  |  $39.94$  |  $0.93$  |  $1.282$  |
|  二酸化炭素  |  $CO_2$  |  $44.01$  |  $0.03$  |  $0.0456$  |
|  一酸化炭素  |  $CO$  |  $28.01$  |  $1×10^{-5}$  |  $1×10^{-5}$  |
|  ネオン  |  $Ne$  |  $20.18$  |  $1.8×10^{-3}$  |  $1.25×10^{-3}$  |
|  ヘリウム  |  $He$  |  $4.00$  |  $5.24×10^{-4}$  |  $7.24×10^{-5}$  |
|  メタン  |  $CH_4$  |  $16.05$  |  $1.4×10^{-4}$  |  $7.25×10^{-5}$  |
|  クリプトン  |  $Kr$  |  $83.7$  |  $1.14×10^{-4}$  |  $3.30×10^{-4}$  |
|  一酸化二窒素  |  $N_2O$  |  $44.02$  |  $5×10^{-5}$  |  $7.6×10^{-5}$  |
|  水素分子  |  $H_2$  |  $2.02$  |  $5×10^{-5}$  |  $3.48×10^{-6}$  |
|  オゾン  |  $O_3$  |  $48.0$  |  $2×10^{-6}$  |  $3×10^{-6}$D  |
|  水蒸気  |  $H_2O$  |  $18.02$  |  不定  |  不定  |

### (1) 容積比を使って、乾燥空気の平均分子量$\ M_d \ $を算出する。

$Table1$ より、大気組成のうち「窒素分子・酸素分子・アルゴン」のみを考慮するものとした。

乾燥空気の分子量: $M_d\ =\ $(窒素分子量$\ *\ $窒素分子の容積比)$\ +\ $(酸素分子量$\ *\ $酸素分子の容積比)$\ +\ $(アルゴン分子量$\ *\ $アルゴン分子の容積比)

```python
# 窒素分子・酸素分子・アルゴンそれぞれの容積比
N_2_v = 78.088 * (10 ** -2)
O_2_v = 20.949 * (10 ** -2)
Ar_v = 0.93 * (10 ** -2)

# 窒素分子・酸素分子・アルゴンそれぞれの分子量
N_2_mol_w = 28.01
O_2_mol_w = 32.00
Ar_mol_w = 39.94

M_d = (N_2_mol_w * N_2_v) + (O_2_mol_w * O_2_v) + (Ar_mol_w * Ar_v)
# M_d
print('乾燥空気の平均分子量: ', M_d)
```

    乾燥空気の平均分子量:  28.947570799999998

### (2) 重量比を使って、乾燥空気の平均分子量$\ M_d \ $を算出する。

$Table1$ より、大気組成のうち「窒素分子・酸素分子・アルゴン」のみを考慮するものとした。

乾燥空気の分子量: $M_d\ =\ $(窒素分子量$\ *\ $窒素分子の容積比)$\ +\ $(酸素分子量$\ *\ $酸素分子の容積比)$\ +\ $(アルゴン分子量$\ *\ $アルゴン分子の容積比)

\begin{eqnarray}
M_d &=& \frac{1}{\frac{窒素分子の重量比}{窒素分子量} + \frac{酸素分子の重量比}{酸素分子量} + \frac{アルゴン分子の重量比}{アルゴン分子量}} \nonumber \\
\end{eqnarray}

```python
# 窒素分子・酸素分子・アルゴンそれぞれの重量比
N_2_w = 75.527 * (10 ** -2)
O_2_w = 23.413 * (10 ** -2)
Ar_w = 1.282 * (10 ** -2)

M_d = 1 / ((N_2_w / N_2_mol_w) + (O_2_w / O_2_mol_w) + (Ar_w / Ar_mol_w))
# M_d
print('乾燥空気の平均分子量: ', M_d)
```

    乾燥空気の平均分子量:  28.90019517947606

### (3) $\ 1kg\ $の乾燥空気に対する気体定数 $\ R_d \ $ を算出する。

一般気体定数 $R^* = 8.3143 × 10^3\ JK^{-1}kmol^{-1}$

\begin{eqnarray}
R_d &=& \frac{R^*}{M_d} \nonumber \\
&=& \frac{8.3143 * 10^3}{28.9} \nonumber \\
&≒& 287\ JK^{-1}kg^{-1} \nonumber \\
\end{eqnarray}

## 2. 金星大気について

金星の大気では、容積比で、$CO_2$ が95%、$N_2$ が5%を占めているとする。$C, O, N$ それぞれの原子量は $12, 16, 14$ とする。

$Table 2$

金星の大気組成

|  成分  |  分子式  |  分子量  |  容積比（存在比率%）  |
| ---- | ---- | ---- | ---- |
|  二酸化炭素分子  |  $CO_2$  |  12 + 16 × 2 = 44  |  $95.0$  |
|  窒素分子  |  $N_2$  |  14 × 2 = 28  |  $5.0$  |

### (1) 金星大気の平均分子量 $M_dv$ を算出する。

\begin{eqnarray}
M_dv &=& 44 * 0.95 + 28 * 0.05 \nonumber \\
&=& 41.8 + 1.4 \nonumber \\
&=& 43.2 \nonumber \\
\end{eqnarray}

### (2) 金星大気 $\ 1kg\ $の気体定数 $\ R_dv \ $ を算出する。

\begin{eqnarray}
R_dv &=& \frac{R^*}{M_dv} \nonumber \\
&=& \frac{8.3143 * 10^3}{43.2} \nonumber \\
&=& 192.46\ JK^{-1}kg^{-1} \nonumber \\
\end{eqnarray}

## 3. 乾燥空気の状態方程式から気圧を算出

質量0.01kgの乾燥空気を高度5km、気圧540hPa、気温 -17度とした場合の体積を算出する。

\begin{equation}
V = \frac{m R_d T}{p}
\end{equation}

より、体積 0.0136 $(m^3)$

```python
m = 0.01
R_d = 287
T = 273 + (-17)
p = 540 *  (10 ** 2)

V = m * R_d * T / p
V
```

    0.013605925925925926

#### 参考文献
* 「一般気象学　第2版補訂版」 小倉義光著

```python

```

```python

```

01_Thermodynamics of the atmosphere Equation of state of an ideal gas.ipynb


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

regression_degree = 1
std_factor = 0.5
precision = 1
fig = plt.figure()
ax = fig.add_axes([0,0,1,1])

y = [720, 360, 180, 147, 108, 131, 86, 88, 69, 70, 105, 80, 61, 71]
X = list(np.arange(0, len(y)))

plt.scatter(X,y,c=y,cmap='Greens_r')

X_regression = np.linspace(0, len(y)-1, len(y)*10)
y_regression = np.poly1d(np.polyfit(X, y, regression_degree))

x = sym.symbols('x')
y_regression_equation = sym.printing.latex(sym.Poly([f'{coef:.{precision}f}' for coef in y_regression.coef],x).as_expr())

plt.plot(X_regression, y_regression(X_regression), '#ccffcc', label=rf'$\^{{y}} \approx {y_regression_equation}$')
plt.plot(X_regression, y_regression(X_regression)+np.std(y)*std_factor,'#ddd', label=rf'$\^{{y}} \pm {std_factor} \cdot \sigma $')
plt.plot(X_regression, y_regression(X_regression)-np.std(y)*std_factor, '#ddd')

ax.set_facecolor('#f8f4f8')
ax.legend()

ax.set_title(r'solving $\int \frac{x^2}{\sqrt{5-2x^3}} \,dx $ by hand via substitution')
plt.ylabel(r'$y := $ time required [seconds]')
plt.xlabel(r'$x := $ attempt number')

plt.show()

fig.savefig('repetition.png',bbox_inches='tight')
```

```python

```


# Exercise 3: Modelling transient fluid flow in the crust, background information

Elco Luijendijk

October 2019

<elco.luijendijk@geo.uni-goettingen.de>

## Introduction

In this exercise we will move from simulating steady-state diffusion to transient (time-dependent) diffusion of fluids in the crust. We will first construct a numerical model of transient fluid flow and then apply this model to a real world case study. You will have the choice between two case studies, one in which we compare predictions of our 1D model to data on seismicity induced by changes in hydraulic head and pore pressure at depth and a second case study where we will study the resilience of a spring to a change in precipitation and recharge.

## Objectives

* Learn to simulate transient fluid, heat or solute transport in the crust
* Learn to combine numerical models and geoscience datasets to study geological processes.
* Learn how fluid transport behaves over long(ish) timescales

Do not hesitate to ask questions if you get stuck anywhere. You can reach me by email at <eluijen@gwdg.de> or pass by at my office, room 122 in the Structural Geology dept.

**Deadline: 21 february 2020**

*Good luck !*

## The transient fluid, heat and solute transport equations

As discussed during the lectures, the transport of fluid, heat and solutes in the subsurface are all diffusion processes. The equations of these processes are very similar, as you can see:

\begin{equation}
    S_s \dfrac{\partial h}{\partial t} = \nabla K_f \nabla h + W_f
\end{equation}

\begin{equation}
    \rho_b c_b \dfrac{\partial T}{\partial t} = \nabla K_h \nabla T + W_h
\end{equation}

\begin{equation}
    \phi \dfrac{\partial C}{\partial t} = \nabla K_s \nabla C + W_s
\end{equation}

subscripts f, h and s denote the diffusion coefficient and source term for fluid, heat and solute.

In the remaining part of this document we will use a generalized diffusion equation to explain how your numerical model of transient fluid, heat or solute transport. The general equation is:

\begin{equation}
    S\dfrac{\partial u}{\partial t} = \nabla K \nabla u + W
\end{equation}

For one-dimensional fluid flow the storage term is equal to the specific storage (S_s). The specific storage is the change in fluid pressure and hydraulic head as a result of a change in fluid volume, and is a function of the compressibility of the rock matrix and water (see your lecture notes). Values of specific storage range from 10^-4^ to 10^-6^. The specific yield is the volume of water released from unconfined aquifers due to a change in watertable, ie. simply the volume of water that is drained from the porous rock or sediments. This ranges from 0.05 for clays to 0.4 for coarse sands.

For the model runs in this exercise you will have to change the coefficients *S*, *u*, *K* and *W* to the right coefficients for your case study. We will discuss how to setup the script for your case study further on, but lets first go through the theory of how transient fluid flow can be modeled the finite difference approximation.

## Solving the transient diffusion equation

In exercise 1 and 2 we used Euler's method to solve the steady-state groundwater flow equation. Euler's method for solving partial differential equations is to discretize derivatives: ie. to quantify derivatives at a discrete interval. Mathematically the method looks like this:

\begin{equation}
    \dfrac{\partial u}{\partial x} = \dfrac{u(x+\Delta x) - u(x)}{\Delta x}
\end{equation}

where *u(x+$\Delta$ x)* denotes the value of *u* at position $x + \Delta x$.

We will again use Euler's method to solve the partial differential equation of transient diffusion. First we simplify the diffusion equation to a 1D depth integrated equation by replacing $\nabla q$ by $\partial  q/ \partial x$:

\begin{equation}
    S\dfrac{\partial u}{\partial t} = - \dfrac{\partial q}{\partial x}  + W
\end{equation}

where q is a flux of fluid, heat or solutes. In this equation we have 2 derivatives, one derivative of the variable *u* over time, $\dfrac{\partial u}{\partial t}$, and one derivative of the flux (*q*) over space ($\dfrac{\partial q}{\partial x}$).

Now lets discretize these following Euler's method. First, lets get rid of the spatial derivative of the flux (*q*):

\begin{equation}
    S\dfrac{\partial u_i}{\partial t} = - \dfrac{q_{i+1/2} - q_{i-1/2}}{2 \Delta x} + W
\end{equation}

The subscript ~i+1/2~ means the flux (*q*) from node *i* to the next node over *i+1*. Similarly subscript ~i-1/2~ means the flux (*q*) from the node before the current one (*i-1*) to the current node (*i*).

Lets also replace the derivative of u over time with the discretized version:

\begin{equation}
    S\dfrac{u_{i,j+1} - u_{i,j}}{\Delta t} = - \dfrac{q_{i+1/2,j} - q_{i-1/2,j}}{2 \Delta x} + W
\end{equation}

In this equation the index term *i* denotes the position in space and *j* denotes the timestep. *i* would be the current node, *i-1* one node to the left, *i+1* one node to the right. Similarly, *j* is the current timestep, *j+1* is the next timestep.

We can rearrange this equation to yield an equation for the value of variable *u* at timestep *j+1*:

\begin{equation}
    u_{i,j+1} = - \dfrac{\Delta t}{S} \dfrac{q_{i+1/2,j} - q_{i-1/2,j}}{2 \Delta x} + \dfrac{\Delta t W}{S} + u_{i,j}
\end{equation}

This equation simply states that the value of variable *u* at node position *i* and timestep *j+1* is a function of of the flux towards this node at timestep *j*, a source term *W* and the value of the variable at the previous timestep *j*.

To complete the equation we need to calculate the flux towards node *i*. We can do this again by applying Euler's method. The equation for a diffusive flux of fluid, heat or solutes is:

\begin{equation}
    q = - K \dfrac{\partial u}{\partial x}
\end{equation}

in discretized form this is

\begin{equation}
    q_{1/2} = - K \dfrac{(u_{0} - u_1)}{\Delta x}
\end{equation}

Note that the notation for flux from node 0 to node 1 is $q_{1/2}$.

Note also that if you use depth-integrated fluid flux then you have to multiply hydraulic conductivity (*K*) by the thickness (*b*) in this equation.

## Transient diffusion in Python

Now we have two equations to solve the transient diffusion equation, one equation that calculates the flux *q* at timestep *j* and one that calculates the value of variable *u* at timestep *j+1* using the flux at timestep *j* and the value of *u* at timestep *j*.

The two equations are implemented in a python script (surprisingly called exercise_3.py). We will use this script and adjust the variables to model your fluid flow problem of choice.

The new part of the script where the transient diffusion equation is solved looks like this:

~~~~python
for j in range(1, n_timesteps):

    # calculate the flux between nodes
    q = -K * (h[j-1, 1:] - h[j-1, :-1]) / dx

    # set specified variable value at the left-hand node:
    h[j, 0] = h0[j]

    # implement no-flow boundary condition at right-hand side:
    q_bottom = 0.0
    h[j, -1] = h[j-1, -1] + (dt/S) * (-(q_bottom - q[-1])/dx) + (dt / S) * W[-1]

    # update nodes in the middle:
    h[j, 1:-1] = h[j-1, 1:-1] + (dt/S)*(-(q[1:] - q[:-1])/dx) + (dt/S) * W[1:-1]
~~~~

This part contains a loop through all timesteps (``for j in range(1, n_timesteps):``). Any line that is indented (shifted to the right) below this for loop is considered part of the loop by Python.

Next Python calculates the fluid flux between each node in your numerical model ``q = -K * (h[j-1, 1:] - h[j-1, :-1]) / dx``. Hopefully by now you recognise that this is Darcy's law of fluid flow, where fluid flow is a function of the hydraulic gradient (``(h[j-1, 1:] - h[j-1, :-1]) / dx``) and the hydraulic conductivity (``K``). The index ``[j-1, 1:]`` means that Python looks at the value of h at the last timestep (``j-1``) and then looks at all the nodes except the first one (``1:``).

Next there are three lines that calculate the hydraulic head at the left, middle and right-hand nodes at the current timestep j as a function of the flux (q), the size of the timestep, storativity and the value of hydraulic head at the last timestep (``h[j-1]``). For the top node the hydraulic head is fixed ``h[j, 0] = h0[j]``. For the lowest node ``h[j, -1]`` the flux from the bottom is 0. This ensures that there is no fluid flow over the bottom boundary of our system. For the middle nodes the hydraulic head at the new timestep is simply a function of how much water flows towards or away from it ((q[1:] - q[:-1])/dx), along with the hydraulic head at the previous timestep, storativity and timestep size.

Next: Choose a case study for the exercise. There are two choices:

1. Diffusion of fluid pressure and seismicity below Mt. Hood

2. Climate resilience of a qanat system in a semi-arid environment

Choose one of these exercises and move over to the jupyter notebook of one of these two case studies.


```python

```

```python

```

$\displaystyle r_{1} r_{2} \sqrt{\left(\sin{\left(\phi_{1} \right)} \sin{\left(\theta_{1} \right)} \cos{\left(\theta_{2} \right)} - \sin{\left(\phi_{2} \right)} \sin{\left(\theta_{2} \right)} \cos{\left(\theta_{1} \right)}\right)^{2} + \left(\sin{\left(\theta_{1} \right)} \cos{\left(\phi_{1} \right)} \cos{\left(\theta_{2} \right)} - \sin{\left(\theta_{2} \right)} \cos{\left(\phi_{2} \right)} \cos{\left(\theta_{1} \right)}\right)^{2} + \sin^{2}{\left(\theta_{1} \right)} \sin^{2}{\left(\theta_{2} \right)} \sin^{2}{\left(\phi_{1} - \phi_{2} \right)}}$

```python

```

$\displaystyle - \operatorname{atan}{\left(\frac{\sqrt{2} \sqrt{- 2 \cos{\left(2 \theta_{1} - 2 \theta_{2} \right)} - 2 \cos{\left(2 \theta_{1} + 2 \theta_{2} \right)} + \cos{\left(- \phi_{1} + \phi_{2} + 2 \theta_{1} + 2 \theta_{2} \right)} - \cos{\left(\phi_{1} - \phi_{2} - 2 \theta_{1} + 2 \theta_{2} \right)} - \cos{\left(\phi_{1} - \phi_{2} + 2 \theta_{1} - 2 \theta_{2} \right)} + \cos{\left(\phi_{1} - \phi_{2} + 2 \theta_{1} + 2 \theta_{2} \right)} + 4}}{4 \sin{\left(\theta_{1} \right)} \sin{\left(\theta_{2} \right)} \sin{\left(\phi_{1} - \phi_{2} \right)}} \right)}$

```python

```

$\displaystyle \operatorname{atan_{2}}{\left(r_{1} r_{2} \left(- \sin{\left(\theta_{1} \right)} \cos{\left(\phi_{1} \right)} \cos{\left(\theta_{2} \right)} + \sin{\left(\theta_{2} \right)} \cos{\left(\phi_{2} \right)} \cos{\left(\theta_{1} \right)}\right),r_{1} r_{2} \left(\sin{\left(\phi_{1} \right)} \sin{\left(\theta_{1} \right)} \cos{\left(\theta_{2} \right)} - \sin{\left(\phi_{2} \right)} \sin{\left(\theta_{2} \right)} \cos{\left(\theta_{1} \right)}\right) \right)}$

### Cylindrical Coordinate

$$\vec{r}(\rho, \phi, z) = \rho\vec{e}_\rho(\phi) + z \vec{e}_z$$

Suppose $(\rho, \phi, z)$ has an argument $t$. (they can have multiple args, as the following deduction also holds.)

i.e.

$$\rho = \rho(t)$$
$$\phi = \phi(t)$$
$$z = z(t)$$

$$\frac{\mathrm{d}\vec{r}(\rho, \phi, z)}{\mathrm{d}t} = \frac{\mathrm{d}\rho}{\mathrm{d}t}\vec{e}_\rho(\phi) + \rho\frac{\mathrm{d}\vec{e}_\rho}{\mathrm{d}t} + \frac{\mathrm{d}z}{\mathrm{d}t} \vec{e}_z$$
$$\frac{\mathrm{d}^n\vec{r}(\rho, \phi, z)}{\mathrm{d}t^n} = \sum_{k=0}^n C_n^k \frac{\mathrm{d}^k\rho}{\mathrm{d}t^k} \frac{\mathrm{d}^{n-k}}{\mathrm{d}t^{n-k}} \vec{e}_\rho(\phi) + \frac{\mathrm{d}^kz}{\mathrm{d}t^k} \vec{e}_z$$
$$\frac{\mathrm{d}^n\vec{r}(\rho, \phi, z)}{\mathrm{d}t^n} = \sum_{k=0}^n C_n^k \frac{\mathrm{d}^k\rho}{\mathrm{d}t^k} \frac{\mathrm{d}^{n-k}}{\mathrm{d}\phi^{n-k}} \vec{e}_\rho(\phi) \frac{\mathrm{d}^{n-k}\phi}{\mathrm{d}t^{n-k}} + \frac{\mathrm{d}^kz}{\mathrm{d}t^k} \vec{e}_z$$
$$\frac{\mathrm{d}^n\vec{r}(\rho, \phi, z)}{\mathrm{d}t^n} = \sum_{k=0}^n C_n^k \frac{\mathrm{d}^k\rho}{\mathrm{d}t^k} \frac{\mathrm{d}^{n-k}\phi}{\mathrm{d}t^{n-k}} \frac{\mathrm{d}^{n-k \pmod{4}}}{\mathrm{d}\phi^{n-k \pmod{4}}} \vec{e}_\rho(\phi) + \frac{\mathrm{d}^kz}{\mathrm{d}t^k} \vec{e}_z$$

Note that $\mathrm{d}\vec{e}_\rho(\phi)/ \mathrm{d}\phi = \vec{e}_\phi(\phi)$, $\mathrm{d}\vec{e}_\phi(\phi)/ \mathrm{d}\phi = -\vec{e}_\rho(\phi)$, therefore

$$\frac{\mathrm{d}\vec{e}_\rho(\phi)}{\mathrm{d}\phi} = \vec{e}_\phi(\phi)$$
$$\frac{\mathrm{d}^2\vec{e}_\rho(\phi)}{\mathrm{d}\phi^2} = -\vec{e}_\rho(\phi)$$
$$\frac{\mathrm{d}^3\vec{e}_\rho(\phi)}{\mathrm{d}\phi^3} = -\vec{e}_\phi(\phi)$$
$$\frac{\mathrm{d}^4\vec{e}_\rho(\phi)}{\mathrm{d}\phi^4} = \vec{e}_\rho(\phi)$$

```python

```

```python

```

### Metric Tensor

First basic form
$$g_{ij} = \vec{r}_i \cdot \vec{r}_j$$

Second basic form
$$\Omega_{ij} = \vec{r}_{ij}$$

### Christoffel symbol, also named as connection coefficients.

$$\Gamma^k_{ij} = \frac{1}{2} g^{km}(\frac{\partial g_{mj}}{\partial u^i} + \frac{\partial g_{im}}{\partial u^j} - \frac{\partial g_{ij}}{\partial u^m})$$

```python
from sympy.abc import t, u, v
from sympy import cos, sin, pi
```

```python
c = Curve([5*cos(t),5*sin(t), 3*t], (t, -2*pi, 2*pi))
c.curvature().simplify()
```

$\displaystyle \frac{5}{34}$

```python
norm_c = c.param_norm()
print(norm_c)
norm_c._r
```

    A curve = [5*cos(sqrt(34)*s/34), 5*sin(sqrt(34)*s/34), 3*sqrt(34)*s/34] in XYZ coordinate system, with s in (-2*sqrt(34)*pi, 2*sqrt(34)*pi).

$\displaystyle \left[\begin{matrix}5 \cos{\left(\frac{\sqrt{34} s}{34} \right)} & 5 \sin{\left(\frac{\sqrt{34} s}{34} \right)} & \frac{3 \sqrt{34} s}{34}\end{matrix}\right]$

```python
from sympy import symbols, oo, sin, cos, pi
u, v = symbols('u, v', real=True)
R = 5
s = Surface([R*cos(u), R*sin(u), v], (u, 0, 2*pi), (v, -oo, oo))
```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python
import plotly
import numpy as np
from sympy import lambdify, N

f_s = lambdify(s.uv(), s.r(), "numpy")

# Make data
u_limit = tuple([float(u) for u in s.u_limit()])
u = np.linspace(*u_limit, num=100)
v = np.linspace(0, 6.28, num=100)
ug, vg = np.meshgrid(u, v)
xg, yg, zg = f_s(ug, vg)

# ax.plot_surface(xg, yg, zg, facecolors=cm.jet(zg/np.max(zg)), shade=True)

# plt.show()
```

```python

```

```python
import plotly.graph_objects as go
from plotly.subplots import make_subplots

# Equation of ring cyclide
# see https://en.wikipedia.org/wiki/Dupin_cyclide
import numpy as np
a, b, d = 1.32, 1., 0.8
c = a**2 - b**2
u, v = np.mgrid[0:2*np.pi:100j, 0:2*np.pi:100j]
x = (d * (c - a * np.cos(u) * np.cos(v)) + b**2 * np.cos(u)) / (a - c * np.cos(u) * np.cos(v))
y = b * np.sin(u) * (a - d*np.cos(v)) / (a - c * np.cos(u) * np.cos(v))
z = b * np.sin(v) * (c*np.cos(u) - d) / (a - c * np.cos(u) * np.cos(v))

fig = make_subplots(rows=1, cols=2,
                    specs=[[{'is_3d': True}, {'is_3d': True}]],
                    subplot_titles=['Color corresponds to z', 'Color corresponds to distance to origin'],
                    )

fig.add_trace(go.Surface(x=x, y=y, z=z, colorbar_x=-0.07), 1, 1)
fig.add_trace(go.Surface(x=x, y=y, z=z, surfacecolor=x**2 + y**2 + z**2), 1, 2)
fig.update_layout(title_text="Ring cyclide")
fig.show()
```

```python

```

```python

```

```python

```

```python

```

```python

```


# 巡回符号・ガロア体上の多項式環における剰余

参考 https://stackoverflow.com/questions/14173007/polynomial-with-modular-coefficients-library-in-python

https://numpy.org/doc/stable/reference/generated/numpy.poly1d.html#numpy.poly1d

https://stackoverflow.com/questions/28646336/pretty-printing-polynomials-in-ipython-notebook (表記)

```
# import numpy.polynomial.polynomial
import numpy as np
```

```
# 表示だけ．計算には不要
import sympy
from sympy.abc import x
sympy.init_printing()
def printp(poly):
  return sympy.Poly(poly.coef,x).as_expr()
```

## 実数体上の多項式 $\mathbb{R}[x]$ とその演算を Python + NumPy で行う方法．表示にはSymPy

この計算は，（教科書で出てくる）整数環上の多項式 $\mathbb{Z}[x]$ の演算ではない

```
A = np.poly1d([1,2,3,4])
print(A)
```

       3     2
    1 x + 2 x + 3 x + 4

```
printp(A)
```

```
# 加算，減算，乗算
A = np.poly1d([1,2])
B = np.poly1d([3,1])
printp( A ), printp( B ), printp( A + B ), printp( A - B ), printp( A * B )
```

```
# 除算
A = np.poly1d([1,2,3])
B = np.poly1d([1,1])
printp( A ), printp( B )
```

```
A / B
```

    (poly1d([1., 1.]), poly1d([2.]))

```
# 商
printp( (A / B)[0] )
```

```
# 剰余
printp( (A / B)[1] )
```

A/Bは商と剰余をリストで返す．(A/B)[1]とすると剰余だけ

## 有限体上の多項式 $\mathbb{F}_2[x]$ の演算

係数を(0,1)に限って$\mathbb{R}[x]$ の演算を行った後で mod 2すればよい（例外的なものがあるかもしれない）

```
# 情報ビット
X = np.poly1d([1,1,1,0])
# 生成多項式
G = np.poly1d([1,0,1,1])
printp(X), printp(G)
```

```
xm = np.poly1d( [1] + [0] * G.order )
printp(xm)
```

$ x^m = x^{\mathrm{deg}(G(x))}$

$ C(x) = X(x) \, x^{\mathrm{deg}(G(x))} \bmod G(x) $

$ \mathbb{R}[x]$ 上の剰余

```
printp( (X*xm/G)[1] )
```

$ \mathbb{F}_2[x] $上の剰余

```
np.remainder( (X*xm/G)[1], 2 )
```

    array([1., 0., 0.])

```
printp( np.poly1d( np.remainder( (X*xm/G)[1], 2 ) ) )
```

np.remainderは正の値，np.fmodはdividendと同じ符号

```
np.remainder(-5,3), np.fmod(-5,3)
```

    (1, -2)

```
## 4週目．巡回ハミング符号．単一誤りパターンに対するシンドロームの計算
```

```
for i in range(7):
  E = np.poly1d( [1] + [0] * i )
  S = np.poly1d( np.remainder( (E/G)[1], 2 ) )

  print("i = %d" % i), print("%s = E(x)" % E), print("%s = S(x)" % S), print('\n===\n')
```

    i = 0

    1 = E(x)

    1 = S(x)

    ===

    i = 1

    1 x = E(x)

    1 x = S(x)

    ===

    i = 2
       2
    1 x = E(x)
       2
    1 x = S(x)

    ===

    i = 3
       3
    1 x = E(x)

    1 x + 1 = S(x)

    ===

    i = 4
       4
    1 x = E(x)
       2
    1 x + 1 x = S(x)

    ===

    i = 5
       5
    1 x = E(x)
       2
    1 x + 1 x + 1 = S(x)

    ===

    i = 6
       6
    1 x = E(x)
       2
    1 x + 1 = S(x)

    ===

forループの中ではprintpは動かないみたい

### 参考

SymPyに，$ \mathbb{F}_2[x]$ をちゃんと計算する方法はある

ただし，上記の方が筆算の途中経過等が分かりやすいと思われる

https://docs.sympy.org/latest/modules/polys/internals.html

https://stackoverflow.com/questions/48065360/interpolate-polynomial-over-a-finite-field?rq=1

```
from sympy.polys.domains import ZZ
from sympy.polys.galoistools import gf_div, gf_add_mul
print(gf_div(ZZ.map([1,1,1,0,0,0,0]), ZZ.map([1,0,1,1]), 2, ZZ)[1])
```

    [1, 0, 0]

```
P = ZZ.map([1,1,1,0,0,0,0])
sympy.Poly(P,x).as_expr()
```

```

```