```python
# Import necessary resources
import os
import sys
import inspect
import numpy as np
import torch
import torch.optim as optim
import matplotlib.pyplot as plt
%matplotlib inline

currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0, os.path.join(parentdir, 'scripts'))

import models as m
import utils
```

# First-Order ODEs

Note that a first-order ODE must be able to be written in the form

\begin{align}
\frac{d}{dx}\Psi + p(x)\Psi = g(x)
\end{align}

Where both $p(x)$ and $g(x)$ are continuous functions.

## Example I
Consider the first-order ODE:
\begin{align}
\frac{d}{dx}\Psi + (x+\frac{1 + 3 x^2}{1 + x+ x^3})\Psi = x^3 + 2x +x^2\frac{1 + 3x^2}{1 + x + x^3},\quad \Psi(0) = 1 \quad and \quad x\in [0,1]
\end{align}
Solve for $\Psi(x)$

```python
# Define Differential Equation
# Initial condition
f_0 = 1

# differential equation
def g(x):
    return x**3 + 2*x + (x**2)*((1+3*x**2)/(1+x+x**3))

def p(x):
    return x + ((1+3*(x**2))/(1+x+x**3))
```

```python
learning_rate = 0.003
epochs = 1000
display_step = 100

# Define NN
neural_network = m.MLP()

# Create optimizer
optimizer = optim.Adam(neural_network.parameters(), lr=learning_rate)

for epoch in range(epochs):
    optimizer.zero_grad()
    loss = utils.first_order_loss_with_ic(neural_network, p, g, f_0)
    loss.backward()
    optimizer.step()
    # print(loss.item())
    if epoch % display_step == 0:
        print(loss.item())
```

    6.5093231201171875
    0.48920372128486633
    0.07198688387870789
    0.062412891536951065
    0.05132240056991577
    0.04936061054468155
    0.0378345362842083
    0.03119318000972271
    0.02378513664007187
    0.025509227067232132

```python
x = torch.linspace(0, 1, 50, requires_grad=True).unsqueeze(1)
y_approx = (f_0 + x * neural_network(x)).squeeze(0).detach().numpy().transpose()[0]
y_exact = ((torch.exp(-(x**2)/2)/(1+x+x**3))+x**2).squeeze(0).detach().numpy().transpose()[0]
x = x.squeeze(0).detach().numpy().transpose()[0]
plt.plot(x, y_approx, label='NN Approximation')
plt.plot(x, y_exact, label='Exact Solution')
plt.legend()
```

## Example II: More Complicated
Consider the first-order ODE:
\begin{align}
\frac{d}{dx}\Psi + \frac{1}{5}\Psi = e^{-\frac{x}{5}}cos(x),\quad \Psi(0) = 0 \quad and \quad x\in [0,10]
\end{align}
Solve for $\Psi(x)$

```python
# Define sample problem
# Initial condition
f_0 = 0

# differential equation
def g(x):
    return torch.exp(-x/5)*torch.cos(x)

def p(x):
    return 1/5

# Domain
domain = (0, 6)
```

```python
learning_rate = 0.003
epochs = 5000
display_step = 500

# Define NN
neural_network = m.MLP()

# Create optimizer
optimizer = optim.Adam(neural_network.parameters(), lr=learning_rate)

for epoch in range(epochs):
    optimizer.zero_grad()
    loss = utils.first_order_loss_with_ic(neural_network, p, g, f_0, domain[0], domain[1], 20)
    loss.backward()
    optimizer.step()
    # print(loss.item())
    if epoch % display_step == 0:
        print(loss.item())
```

    5.921851634979248
    0.06333978474140167
    0.017188403755426407
    0.011580437421798706
    0.010117428377270699
    0.020874321460723877
    0.013394061475992203
    0.005748686380684376
    0.022075042128562927
    0.005784706678241491

```python
x = torch.linspace(domain[0], domain[1], 100, requires_grad=True).unsqueeze(1)
y_approx = (f_0 + x * neural_network(x)).squeeze(0).detach().numpy().transpose()[0]
y_exact = (torch.exp(-x/5)*torch.sin(x)).squeeze(0).detach().numpy().transpose()[0]
x = x.squeeze(0).detach().numpy().transpose()[0]
plt.plot(x, y_approx, label='NN Approximation')
plt.plot(x, y_exact, label='Exact Solution')
plt.legend()
```

# Second-Order ODEs

Note that a second-order ODE must be able to be written in the form

\begin{align}
\frac{d^2}{dx^2}\Psi + a(x)\frac{d}{dx}\Psi + b(x)\Psi = g(x)
\end{align}

Where $a(x)$, $b(x)$, and $g(x)$ are continuous functions.

## Second-Order Example 1.
The method described will be used to approximate the solution $\Psi(x)$ to the following second-order ODE:

\begin{align}
\frac{d^2}{dx^2}\Psi + \frac{1}{5}\frac{d}{dx}\Psi + \Psi= -\frac{1}{5}e^{-\frac{x}{5}}cos(x), \quad \Psi(0) = 0, \; \frac{d}{dx}\Psi(0)=1 \quad over \quad x\in [0,2]
\end{align}

```python
# Define sample problem
# Initial condition
f_0 = 0
f_prime_0 = 1

# differential equation
def g(x):
    result = -(1.0/5.0)*torch.ones_like(x, requires_grad=True)*torch.exp(-x/5.0)*torch.cos(x)
#     print(result.requires_grad)
    return result

def a(x):
    result =(1.0/5.0)*torch.ones_like(x, requires_grad=True)
#     print(result.requires_grad)
    return result

def b(x):
    return torch.ones_like(x, requires_grad=True)

domain = (0, 2)
```

```python
learning_rate = 0.0005
epochs = 2000
display_step = 100

# Define NN
neural_network = m.MLP()

# Create optimizer
optimizer = optim.Adam(neural_network.parameters(), lr=learning_rate)

for epoch in range(epochs):
    optimizer.zero_grad()
    loss = utils.second_order_loss_with_ic(neural_network, a, b, g, f_0, f_prime_0, domain[0], domain[1], 9)
    loss.backward()
    optimizer.step()

    if epoch % display_step == 0:
        print(loss.item())
```

    63.24591827392578
    1.3990896940231323
    0.5923507809638977
    0.5044746994972229
    0.3762977719306946
    0.3536129891872406
    0.2876780927181244
    0.15113455057144165
    0.14972996711730957
    0.5419094562530518
    0.11331117153167725
    0.12796147167682648
    0.13508619368076324
    0.17914557456970215
    0.133964404463768
    0.12941011786460876
    0.12181040644645691
    0.09451949596405029
    0.18439215421676636
    0.24913735687732697

```python
x = torch.linspace(domain[0], domain[1], 100, requires_grad=True).unsqueeze(1)
y_approx = (f_0 + x*f_prime_0 + x**2 * neural_network(x)).squeeze(0).detach().numpy().transpose()[0]
y_exact = (torch.exp(-x/5)*torch.sin(x)).squeeze(0).detach().numpy().transpose()[0]
x = x.squeeze(0).detach().numpy().transpose()[0]
plt.plot(x, y_approx, label='NN Approximation')
plt.plot(x, y_exact, label='Exact Solution')
plt.legend()
```

```python

```


## First Assignment

#### 1) Apply the appropriate string methods to the **x** variable (as '.upper') to change it exactly to: "$Dichlorodiphenyltrichloroethane$".

```python
x = "DiClOrod  IFeNi  lTRicLOr oETaNo  DiChlorod iPHeny lTrichL oroEThaNe"
```

```python
if x == "DiChlorod":
    print ("Dichlorod")
elif x == "iPHeny":
    print ("ipheny")
elif x == "lTrichL":
    print ("ltrichl")
elif x == "oroEThaNe":
    print ("oroethane")
else: print()
```

#### 2) Assign respectively the values: 'word', 15, 3.14 and 'list' to variables A, B, C and D in a single line of code. Then, print them in that same order on a single line separated by a space, using only one print statement.

```python

```

#### 3) Use the **input()** function to receive an input in the form **'68.4 1.71'**, that is, two floating point numbers in a line separated by space. Then, assign these numbers to the variables **w** and **h** respectively, which represent an individual's weight and height (hint: take a look at the '.split()' method). With this data, calculate the individual's Body Mass Index (BMI) from the following relationship:

\begin{equation}
BMI = \dfrac{weight}{height^2}
\end{equation}

```python

```

#### This value can also be classified according to ranges of values, following to the table below. Use conditional structures to classify and print the classification assigned to the individual.

<\center>

(source: https://healthtravelguide.com/bmi-calculator/)

```python

```

#### 4) Receive an integer as an input and, using a loop, calculate the factorial of this number, that is, the product of all the integers from one to the number provided.

```python

```

#### 5) Using a while loop and the input function, read an indefinite number of integers until the number read is -1. Present the sum of all these numbers in the form of a print, excluding the -1 read at the end.

```python

```

#### 6) Read the **first name** of an employee, his **amount of hours worked** and the **salary per hour** in a single line separated by commas. Next, calculate the **total salary** for this employee and show it to two decimal places.

```python

```

#### 7) Read three floating point values **A**, **B** and **C** respectively. Then calculate itens a, b, c, d and e:

```python

```

    a) the area of the triangle rectangle with A as the base and C as the height.

```python

```

    b) the area of the circle of radius C. (pi = 3.14159)

```python

```

    c) the area of the trapezoid that has A and B for bases and C for height.

```python

```

    d) the area of the square that has side B.

```python

```

    e) the area of the rectangle that has sides A and B.

```python

```

#### 8) Read **the values a, b and c** and calculate the **roots of the second degree equation** $ax^{2}+bx+c=0$ using [this formula](https://en.wikipedia.org/wiki/Quadratic_equation). If it is not possible to calculate the roots, display the message **“There are no real roots”**.

```python

```

#### 9) Read four floating point numerical values corresponding to the coordinates of two geographical coordinates in the cartesian plane. Each point will come in a line with its coordinates separated by space. Then calculate and show the distance between these two points.

(obs: $d=\sqrt{(x_1-x_2)^2 + (y_1-y_2)^2}$)

```python

```

    'you can type anything'

#### 10) Read **two floating point numbers** on a line that represent **coordinates of a cartesian point**. With this, use **conditional structures** to determine if you are at the origin, printing the message **'origin'**; in one of the axes, printing **'x axis'** or **'y axis'**; or in one of the four quadrants, printing **'q1'**, **'q2**', **'q3'** or **'q4'**.

```python

```

#### 11) Read an integer that represents a phone code for international dialing.
#### Then, inform to which country the code belongs to, considering the generated table below:
(You just need to consider the first 10 entries)

```python
import pandas as pd
df = pd.read_html('https://en.wikipedia.org/wiki/Telephone_numbers_in_Europe')[1]
df = df.iloc[:,:2]
df.head(20)
```

      Country
      Country calling code

      0
      Austria
      43

      1
      Belgium
      32

      2
      Bulgaria
      359

      3
      Croatia
      385

      4
      Cyprus
      357

      5
      Czech Republic
      420

      6
      Denmark
      45

      7
      Estonia
      372

      8
      Finland
      358

      9
      France
      33

      10
      Germany
      49

      11
      Greece
      30

      12
      Hungary
      36

      13
      Iceland
      354

      14
      Ireland
      353

      15
      Italy
      39

      16
      Latvia
      371

      17
      Liechtenstein
      423

      18
      Lithuania
      370

      19
      Luxembourg
      352

```python

```

#### 12) Write a piece of code that reads 6 numbers in a row. Next, show the number of positive values entered. On the next line, print the average of the values to one decimal place.

```python

```

#### 13) Read an integer **N**. Then print the **square of each of the even values**, from 1 to N, including N, if applicable, arranged one per line.

```python

```

#### 14) Using **input()**, read an integer and print its classification as **'even / odd'** and **'positive / negative'** . The two classes for the number must be printed on the same line separated by a space. In the case of zero, print only **'null'**.

```python

```

## Challenge
#### 15) Ordering problems are recurrent in the history of programming. Over time, several algorithms have been developed to fulfill this function. The simplest of these algorithms is the [**Bubble Sort**](https://en.wikipedia.org/wiki/Bubble_sort), which is based on comparisons of elements two by two in a loop of passes through the elements. Your mission, if you decide to accept it, will be to input six whole numbers ramdonly ordered. Then implement the **Bubble Sort** principle to order these six numbers **using only loops and conditionals**.
#### At the end, print the six numbers in ascending order on a single line separated by spaces.

```python

```


Create a reduced basis for a simple sinusoid model

```
%matplotlib inline
import numpy as np
from misc import *
import matplotlib.pyplot as plt

from lalapps import pulsarpputils as pppu
```

Create the signal model:
\begin{equation}
h(t) = \frac{h_0}{2}\left[\frac{1}{2}F_+ (1+\cos{}^2\iota)\cos{\phi_0} + F_{\times}\cos{\iota} \sin{\phi_0 }\right].
\end{equation}

```
def signalmodel(t, h0, cosi, phi0, psi, FA, FB):
  """
  The real part of a heterodyned CW signal model, where the frequency and frequency
  derivatives represents offsets from the values used in the initial heterodyne.
  """

  ctwopsi = np.cos(2.*psi)
  stwopsi = np.sin(2.*psi)
  fps = FA*ctwopsi + FB*stwopsi
  fcs = FB*ctwopsi - FA*stwopsi

  phase = phi0

  ht = 0.5*h0*(0.5*fps*(1.+cosi**2)*np.cos(phase) + fcs*cosi*np.sin(phase))
  return ht
```

Initialise the model time series and other constant parameters, including antenna patterns that don't including the polarisation angle.

```
# a time series
t0 = 0
tend = 86400.*10
N = (tend-t0)/60.
ts = np.linspace(t0, tend, N)
dt = ts[1]-ts[0]

ra = 0. # right ascension at zero radians
dec = 0. # declination at zero radians
det = 'H1' # LIGO Hanford detector

FAs = np.zeros(len(ts))
FBs = np.zeros(len(ts))
# create antenna response (without polsaition angle)
for i, t in enumerate(ts):
  FA, FB = pppu.antenna_response(t, ra, dec, 0.0, det)
  FAs[i] = FA
  FBs[i] = FB
```

Create a training set of 2000 waveforms with random frequency and frequency derivatives within a narrow range.

```
# number of training waveforms
TS_size = 2000
phi0s = np.random.rand(TS_size)*(2.*np.pi)
psis = np.random.rand(TS_size)*(np.pi/2.)-(np.pi/4.)
cosis = np.random.rand(TS_size)*2. - 1.

# allocate memory and create training set
TS = np.zeros(TS_size*len(ts)).reshape(TS_size, len(ts)) # store training space in TS_size X len(ts) array
h0 = 1.

for i in range(TS_size):
  TS[i] = signalmodel(ts, h0, cosis[i], phi0s[i], psis[i], FAs, FBs)

  # normalize
  TS[i] /= np.sqrt(abs(dot_product(dt, TS[i], TS[i])))
```

Allocate memory for reduced basis vectors.

```
# Allocate storage for projection coefficients of training space waveforms onto the reduced basis elements
proj_coefficients = np.zeros(TS_size*TS_size).reshape(TS_size, TS_size)

# Allocate matrix to store the projection of training space waveforms onto the reduced basis
projections = np.zeros(TS_size*len(ts)).reshape(TS_size, len(ts))

rb_errors = []

#### Begin greedy: see Field et al. arXiv:1308.3565v2 ####

tolerance = 1e-12 # set maximum RB projection error

sigma = 1 # (2) of Algorithm 1. (projection error at 0th iteration)

rb_errors.append(sigma)
```

Run greedy algorithm for creating the reduced basis

```
RB_matrix = [TS[0]] # (3) of Algorithm 1. (seed greedy algorithm (arbitrary))

iter = 0

while sigma >= tolerance: # (5) of Algorithm 1.
  # project the whole training set onto the reduced basis set
  projections = project_onto_basis(dt, RB_matrix, TS, projections, proj_coefficients, iter)

  residual = TS - projections
  # Find projection errors
  projection_errors = [dot_product(dt, residual[i], residual[i]) for i in range(len(residual))]

  sigma = abs(max(projection_errors)) # (7) of Algorithm 1. (Find largest projection error)

  # break out if sigma is less than tolerance, so another basis is not added to the set
  # (this can be required is the waveform only requires a couple of basis vectors, and it
  # stops a further basis containing large amounts of numerical noise being added)
  if sigma < tolerance:
    break

  print sigma, iter
  index = np.argmax(projection_errors) # Find Training-space index of waveform with largest proj. error

  rb_errors.append(sigma)

  #Gram-Schmidt to get the next basis and normalize

  next_basis = TS[index] - projections[index] # (9) of Algorithm 1. (Gram-Schmidt)
  next_basis /= np.sqrt(abs(dot_product(dt, next_basis, next_basis))) #(10) of Alg 1. (normalize)

  RB_matrix.append(next_basis) # (11) of Algorithm 1. (append reduced basis set)

  iter += 1
```

    0.999999972831 0

Check that this basis does give the expected residuals for a new set of random waveforms generated from the same parameter range.

```
#### Error check ####

TS_rand_size = 4000

TS_rand = np.zeros(TS_rand_size*len(ts)).reshape(TS_rand_size, len(ts)) # Allocate random training space

phi0s_rand = np.random.rand(TS_rand_size)*(2.*np.pi)
psis_rand = np.random.rand(TS_rand_size)*(np.pi/2.)-(np.pi/4.)
cosis_rand = np.random.rand(TS_rand_size)*2. - 1.

for i in range(TS_rand_size):
  TS_rand[i] = signalmodel(ts, h0, cosis_rand[i], phi0s_rand[i], psis_rand[i], FAs, FBs)
  # normalize
  TS_rand[i] /= np.sqrt(abs(dot_product(dt, TS_rand[i], TS_rand[i])))

### find projection errors ###
iter = 0
proj_rand = np.zeros(len(ts))
proj_error = []

for h in TS_rand:
  while iter < len(RB_matrix):
    proj_coefficients_rand = dot_product(dt, RB_matrix[iter], h)
    proj_rand += proj_coefficients_rand*RB_matrix[iter]

    iter += 1

  residual = h - proj_rand
  projection_errors = abs(dot_product(dt, residual, residual))
  proj_error.append(projection_errors)
  proj_rand = np.zeros(len(ts))

  iter = 0

plt.scatter(np.linspace(0, len(proj_error), len(proj_error)), np.log10(proj_error))
plt.ylabel('log10 projection error')
plt.show()
```

Now we will create the empirical interpolant and find time time stamp 'nodes' at which it matches the full model.

```
# put basis into complex form
e = np.array(RB_matrix)

indices = []
ts_nodes = []
V = np.zeros((len(e), len(e)))
```

```
from scipy.linalg import inv

# seed EIM algorithm

indices.append( int(np.argmax( np.abs(e[0]) )) ) # (2) of Algorithm 2
ts_nodes.append(ts[indices]) # (3) of Algorithm 2

for i in range(1, len(e)): #(4) of Algorithm 2
  #build empirical interpolant for e_iter
  for j in range(len(indices)): # Part of (5) of Algorithm 2: making V_{ij}
    for k in range(len(indices)): # Part of (5) of Algorithm 2: making V_{ij}
      V[k][j] = e[j][indices[k]] # Part of (5) of Algorithm 2: making V_{ij}
  invV = inv(V[0:len(indices), 0:len(indices)]) # Part of (5) of Algorithm 2: making V_{ij}
  B = B_matrix(invV, e) # Part of (5) of Algorithm 2: making B_j(f)
  interpolant = emp_interp(B, e[i], indices) # Part of (5) of Algorithm 2: making the empirical interpolant of e
  res = interpolant - e[i] # 6 of Algorithm 2

  index = int(np.argmax(np.abs(res))) # 7 of Algorithm 2
  print "ts_{%i} = %f"%(i, ts[index])
  indices.append(index) # 8 of Algorithm 2
  ts_nodes.append( ts[index] ) # 9 of Algorithm 2

# make B matrix with all the indices
for j in range(len(indices)):
  for k in range(len(indices)):
    V[k][j] = e[j][indices[k]]

invV = inv(V[0:len(indices), 0:len(indices)])
B = B_matrix(invV, e)
```

    ts_{1} = 83585.804570

Compare the interpolant with the full signal model

```
h_for_comparison = signalmodel(ts, h0, cosis[0], phi0s[0], psis[0], FAs, FBs)
interpolant_for_comparison = np.inner(B.T, h_for_comparison[indices])

plt.plot(ts, h_for_comparison-interpolant_for_comparison, 'b')
plt.xlabel('time (s)')
plt.show()
```

```
print len(ts_nodes)
print len(ts)
```

    2
    14400

```
H_size = 2000

H = np.zeros(H_size*len(ts)).reshape(H_size, len(ts)) # Allocate random training space

phi0s_rand = np.random.rand(H_size)*(2.*np.pi)
psis_rand = np.random.rand(H_size)*(np.pi/2.)-(np.pi/4.)
cosis_rand = np.random.rand(H_size)*2. - 1.

# create set of test waveforms
for i in range(H_size):
  H[i] = signalmodel(ts, h0, cosis[i], phi0s[i], psis[i], FAs, FBs)

# find errors between full waveform and interpolants
list_of_errors = []
for i in range(H_size):
  interpolant = np.inner(B.T, H[i][indices])
  interpolant /= np.sqrt(np.vdot(interpolant, interpolant)) #normalize

  H[i] /= np.sqrt(np.vdot(H[i], H[i]) ) #normalize

  error = abs(np.vdot(H[i] - interpolant, H[i] - interpolant ))
  list_of_errors.append(error)

  print error

plt.scatter(np.linspace(0, H_size, H_size), np.log10(list_of_errors))
plt.ylabel('log10 interpolation error')
plt.show()
```

Now let's find the weights for the "reduced order quadrature", which are calculated as
\begin{equation}
w_j = \sum_{i=1}^N d_i B_{j,i}
\end{equation}
where $N$ is the full number of time steps, and $B$ is the matrix produced for the empirical interpolant.

First we'll just create some fake data consisting of Gaussian noise.

```
data = np.random.randn(len(ts))

# create weights
w = np.inner(B, data.T)
```

Now, compare calculating $\sum_{i=1}^N d_i h_i$ with the full set of time stamps, with the using the interpolant $\sum_{i=1}^M w_i h(F_i)$, where in this case $h$ is only calculated at the interpolant nodes.

```
d_dot_h = np.vdot(data, signalmodel(ts, 1., cosis_rand[12], phi0s_rand[65], psis_rand[101], FAs, FBs))

FA_nodes = FAs[indices]
FB_nodes = FBs[indices]

hsig = signalmodel(ts_nodes, 1., cosis_rand[12], phi0s_rand[65], psis_rand[101], FA_nodes, FB_nodes)

ROQ = np.dot(w, hsig)

print "regular inner product = %.15e"%d_dot_h
print "ROQ = %.15e"%ROQ
```

    regular inner product = -8.576471621009784e+00
    ROQ = -8.576471621009867e+00

Now test the speed-up

```
import time

t1 = time.time()
for i in range(50000):
    np.dot(data, signalmodel(ts, 1., cosis_rand[0], phi0s_rand[0], psis_rand[0], FAs, FBs)) # regular inner product
e1 = time.time()

t2 = time.time()
for i in range(50000):
    np.dot(w, signalmodel(ts_nodes, 1., cosis_rand[0], phi0s_rand[0], psis_rand[0], FA_nodes, FB_nodes)) # ROQ inner product
e2 = time.time()

print "regular inner product took %f s"%((e1-t1)/50000.)
print "ROQ took %f s"%((e2-t2)/50000.)
print "speedup = %f"%((e1-t1) / (e2-t2))
```

    regular inner product took 0.000124 s
    ROQ took 0.000018 s
    speedup = 6.927863

Now we want to see if we can do the same thing to compute the inner product of the model $\sum_{i=1}^N h_ih_i$. For this we need to use our interpolant
\begin{equation}
\mathcal{I}[h](t) = \sum_{j=1}^M B_j(t)h(T_j)
\end{equation}
where $M$ is the number of nodes in the intepolant time stamps $T$, and $B$ is the matrix produced for the interpolant. Given this we have
\begin{equation}
\langle\, h | h\,\rangle \approx \langle\, \mathcal{I}[h] | \mathcal{I}[h]\,\rangle = \sum_{i=1}^N \left( \sum_{j=1}^M B_j(t_i)h(T_j)\right)^2.
\end{equation}
We want to try and separate out the longer sum over $N$, so that it can be performed in pre-processing. This part can be separated out as an array with components given by $\bar{B}_{mn} = \sum_{i=1}^N B_m(t_i) B_n(t_i)$, where $m$ and $n$ run from 1 to $M$.

Then to get the "reduced order quadrature" we need to do
\begin{equation}
\langle\, \mathcal{I}[h] | \mathcal{I}[h]\,\rangle = \vec{H} \bar{B} \vec{H}^{T},
\end{equation}
where $\vec{H} = [h(T_1), h(T_2), \ldots]$. Note that this is an order $M^2$ operation, which is quick for $M^2 \ll N$, but otherwise does not save computational time.

```
# create new weights
w2 = np.zeros((B.shape[0], B.shape[0]))
for i in range(B.shape[0]):
  for j in range(B.shape[0]):
    w2[i,j] = np.sum(B[i]*B[j])
```

```
sigfull = signalmodel(ts, 1., cosis_rand[0], phi0s_rand[0], psis_rand[0], FAs, FBs)

sigred = signalmodel(ts_nodes, 1., cosis_rand[0], phi0s_rand[0], psis_rand[0], FA_nodes, FB_nodes)

h_dot_h = np.vdot(sigfull, sigfull)
ROQh = np.dot(np.dot(sigred, w2), sigred)

print "regular inner product = %.15e"%h_dot_h
print "ROQ = %.15e"%ROQh
```

    regular inner product = 4.557619458915702e+02
    ROQ = 4.557619458915702e+02

```
t1 = time.time()
for i in range(50000):
    sigfullnew = signalmodel(ts, 1., cosis_rand[0], phi0s_rand[0], psis_rand[0], FAs, FBs)
    np.dot(sigfullnew, sigfullnew) # regular inner product
e1 = time.time()

t2 = time.time()
for i in range(50000):
    sigrednew = signalmodel(ts_nodes, 1., cosis_rand[0], phi0s_rand[0], psis_rand[0], FA_nodes, FB_nodes)
    np.dot(np.dot(sigrednew, w2), sigrednew) # ROQ inner product
e2 = time.time()

print "regular inner product took %f s"%((e1-t1)/50000.)
print "ROQ took %f s"%((e2-t2)/50000.)
print "speedup = %f"%((e1-t1) / (e2-t2))
```

    regular inner product took 0.000124 s
    ROQ took 0.000018 s
    speedup = 6.796928

The other way to get this inner product is to analytically integrate the model using the known form of the antenna pattern (e.g. equations 10-13 of [Jaranowski, Krolak & Schutz (1998)](http://adsabs.harvard.edu/abs/1998PhRvD..58f3001J)), such that
\begin{align}
\langle\,h(t)|h(t)\,\rangle =& \frac{1}{\Delta t}\int_{t_1}^{t_2} h^2 {\rm d}t \\
=& \frac{h_0^2}{2^2\Delta t}\Bigg[ \left(\frac{(1+\cos{}^2\iota)}{2}\cos{\phi_0}\right)^2 \left(\int_{t_1}^{t_2} F_+^2 {\rm d}t\right) + \left(\cos{\iota}\sin{\phi_0}\right)^2\left(\int_{t_1}^{t_2} F_{\times}^2 {\rm d}t\right) +\\
& 2\frac{(1+\cos{}^2\iota)}{2}\cos{\iota}\cos{\phi_0}\sin{\phi_0}\left(\int_{t_1}^{t_2} F_+ F_{\times} {\rm d}t\right)  \Bigg].
\end{align}
These integral would be cheap to produce once at the start of the run. Things would become more complex if the model has a varying phase evolution due to searching over frequency parameters. In that case it might be possible to use the stationary phase approx

_Note_: write down the above integrals in full. Also note that the above is all just for the real part of a heterodyned signal. The imaginary part is given by
\begin{equation}
h(t) = \frac{h_0}{2}\left[\frac{1}{2}F_+ (1+\cos{}^2\iota)\sin{\phi_0} - F_{\times}\cos{\iota} \cos{\phi_0 }\right].
\end{equation}
I should redo all the above, but just getting the reduced basis, interpolant and ROQ for the antenna patterns (i.e. over $\psi$ [or in fact not even over that as it just gives pre-factors, so really it's just the two $a$ and $b$ functions of the antenna pattern that need orthogonalising!]), as the other things are all just pre-factors that are very quickly computed.

ROQ/Reduced basis for CW signal model (no frequency range).ipynb


Computational Seismology

The Fourier Pseudospectral Method - Acoustic Waves in 1D

Seismo-Live: http://seismo-live.org

##### Authors:
* David Vargas ([@dvargas](https://github.com/davofis))
* Heiner Igel ([@heinerigel](https://github.com/heinerigel))
---

## Basic Equations
We use the Fourier method to calculate exact n-th derivatives on a regular spaced grid (to machine precision). This property combined with classical time extrapolation schemes result in the so call Fourier pseudospectral method.  The problem of solving the 1D acoustic wave equation in an homogeneous media

\begin{equation}
\partial_t^2 p(x,t) = c(x)^2 \ \partial_x^2 p(x,t) + s(x,t)
\end{equation}

is covered in this notebook. We explore the benefits of calculating exact spatial derivatives (up to machine precision), numerical dispersion, comparison with a Finite Difference scheme

```python
# This is a configuration step for the exercise. Please run it before calculating the derivative!
import numpy as np
import matplotlib.pyplot as plt
from ricker import ricker

# Show the plots in the Notebook.
plt.switch_backend("nbagg")
```

### 1. Fourier derivative method
The second spatial derivative is computed by multiplying the spatial Fourier transform of the pressure field $P(k,t)$ with $ (ik)^2 $

\begin{equation}
\partial_x^2 p(x,t) = \mathscr{F}^{-1}[(ik)^{2}P(k,t)] = \frac{1}{\sqrt{2\pi}} \int_{-\infty}^{\infty} (ik)^{2} P(k,t) e^{ikx} dk
\end{equation}

where $k$ is the wavenumber and $IFT$ the Inverse Fourier Transform. A function to perform this task is implemented in the next cell.

```python
def fourier_derivative_2nd(f, dx):
    # Length of vector f
    nx = np.size(f)
    # Initialize k vector up to Nyquist wavenumber
    kmax = np.pi / dx
    dk = kmax / (nx / 2)
    k = np.arange(float(nx))
    k[: int(nx/2)] = k[: int(nx/2)] * dk
    k[int(nx/2) :] = k[: int(nx/2)] - kmax

    # Fourier derivative
    ff = np.fft.fft(f)
    ff = (1j*k)**2 * ff
    df_num = np.real(np.fft.ifft(ff))
    return df_num
```

### 2. Initialization of setup

```python
# Basic parameters
# ---------------------------------------------------------------
nt    = 3500    # number of time steps
c     = 343.    # acoustic velocity [m/s]
eps   = 0.2     # stability limit
isnap = 50      # snapshot frequency
isx   = 1250    # source location
f0    = 60.     # Frequency [Hz](div by 5)
nx    = 2024    # number of grid points in x

# pressure fields Initialization
p = np.zeros(nx) ; pnew = p ; pold = p ; d2p = p; dp = p
ap = np.zeros(nx); apnew = ap; apold = ap; ad2p = ap; adp = ap
sp = np.zeros(nx); spnew = sp; spold = sp; sd2p = sp; sdp = p

dx = 1250./(nx-1)  # calculate space increment
x  = np.arange(0, nx)*dx   # initialize space coordinates
dt = eps*dx/c; # calculate time step from stability criterion
```

### 3. Source Initialization

```python
# source time function
# ---------------------------------------------------------------
t   = np.arange(1, nt+1)*dt  # initialize time axis
T0  = 1./f0
tmp = ricker(dt, T0)
tmp = np.diff(tmp)
src = np.zeros(nt)
src[0:np.size(tmp)] = tmp
lam = c*T0

#spatial source function
# ---------------------------------------------------------------
sigma = 2*dx
x0 = x[isx-1]
sg = np.exp(-1/sigma**2 *(x - x0)**2); sg = sg/np.amax(sg)
```

### 4. Time Extrapolation
The final solution for our 1D acoustic wave problem after introducing a finite differences time extrapolation schem can be written as

\begin{equation}
p_{j}^{n+1} = dt^2c_{j}^{2} \partial_{x}^{2}p_{j}^{n} + dt^2s_{j}^{n} + 2p_{j}^{n} - p_{j}^{n-1}
\end{equation}

where the space derivative is computed with the Fourier method. In order to compare the above numerical solution, we implement a 3-point finite difference operator, as well as a 5-point finite difference operator to compute spatial derivatives. They are given as:

1.) **3-point finite difference operator**
\begin{equation}
\partial_x^2 p(x,t) = \frac{p(x+\mathrm{d}x,t) - 2 p(x,t) + p(x-\mathrm{d}x,t)}{\mathrm{d}x^2}
\end{equation}

2.) **5-point finite difference operator**
\begin{equation}
\partial_x^2 p(x,t) = \frac{-p(x+2\mathrm{d}x,t) + 16p(x+\mathrm{d}x,t) - 30p(x,t) + 16p(x-\mathrm{d}x,t) - p(x-2\mathrm{d}x,t)}{12\mathrm{d}x^2}
\end{equation}

#### Numerical dispersion

One of the most prominent characteristic of the Fourier method is the low numerical dispersion in comparison with the finite difference method. The animation displayed below compare the effects of numerical dispersion on the solution of the 1D acoustic equation using our three different approaches.

```python
# Initialize animated plot
# ---------------------------------------------------------------
fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True, sharey=True, figsize=(12,7))
line1 = ax1.plot(x[isx:], p[isx:], 'k', lw=1.5, label='FD-3pt')
line2 = ax2.plot(x[isx:], ap[isx:], 'r', lw=1.5, label='FD-5pt')
line3 = ax3.plot(x[isx:], sp[isx:], 'b', lw=1.5, label='Fourier')
ax1.axis([isx*dx, nx*dx, -6E-7, 6E-7]); ax3.set_xlabel('x [m]')
ax1.legend(loc=4)
ax2.legend(loc=4)
ax3.legend(loc=4)

plt.ion()   # set interective mode
plt.show()

# ---------------------------------------------------------------
# Time extrapolation
# ---------------------------------------------------------------
for it in range(nt):
    # ----------------------------------------
    # Fourier Pseudospectral Method
    # ----------------------------------------
    sd2p  = fourier_derivative_2nd(sp, dx)      # 2nd space derivative
    spnew = 2*sp - spold + c**2 * dt**2 * sd2p  # Time Extrapolation
    spnew = spnew + sg*src[it]*dt**2            # Add sources
    spold, sp = sp, spnew                       # Time levels
    sp[1] = 0; sp[nx-1] = 0     # set boundaries pressure free

    # ----------------------------------------
    # Finite Differences Method 3pt
    # ----------------------------------------
    for i in range(1, nx-1):
        d2p[i] = (p[i+1] - 2*p[i] + p[i-1])/dx**2 # Space derivative
    pnew = 2*p - pold + dt**2 * c**2 * d2p # Time Extrapolation
    pnew = pnew + sg*src[it]*dt**2 # Add source
    pold, p = p, pnew # Time levels
    p[0] = 0; p[nx-1] = 0 # set boundaries pressure free

    # ----------------------------------------
    # Finite Differences Method 5pt
    # ----------------------------------------
    for i in range(2, nx-2):
        ad2p[i] = (-1/12*ap[i+2] + 4/3*ap[i+1] - 5/2*ap[i] \
                   + 4/3*ap[i-1] - 1/12*ap[i-2])/dx**2 # Space derivative
    apnew = 2*ap - apold + dt**2 * c**2 * ad2p # Time Extrapolation
    apnew = apnew + sg*src[it]*dt**2 # Add source
    apold, ap = ap, apnew # Time levels
    ap[0] = 0; ap[nx-1] = 0 # set boundaries pressure free

    # --------------------------------------
    # Animation plot. Display solution
    if not it % isnap:
        for l in line1:
            l.remove()
            del l
        for l in line2:
            l.remove()
            del l
        for l in line3:
            l.remove()
            del l

        # --------------------------------------
        # Display lines
        line1 = ax1.plot(x[isx:], p[isx:], 'k', lw=1.5)
        line2 = ax2.plot(x[isx:], ap[isx:], 'r', lw=1.5)
        line3 = ax3.plot(x[isx:], sp[isx:], 'b', lw=1.5)
        plt.gcf().canvas.draw()
```

```python

```


```python
%matplotlib inline
from IPython.display import display,Math
from sympy import *
init_session()
```

    IPython console for SymPy 1.7.1 (Python 3.8.5-64-bit) (ground types: gmpy)

    These commands were executed:
    >>> from __future__ import division
    >>> from sympy import *
    >>> x, y, z, t = symbols('x y z t')
    >>> k, m, n = symbols('k m n', integer=True)
    >>> f, g, h = symbols('f g h', cls=Function)
    >>> init_printing()

    Documentation can be found at https://docs.sympy.org/1.7.1/

```python
from ipywidgets import interact
from ipywidgets import interact,Dropdown,IntSlider
@interact
def _(n="1"):
    dlist = []
    for s in str(n)[::-1]: # 逆順に1文字ずつ登録
        dlist.append(s)
    sum1 = int(dlist[0]) # 各桁の和
    sum1str = dlist[0] # 計算式
    for s in dlist[1:]:
        d = int(s)
        sum1 += d
        sum1str += "+{}".format(s)
    if sum1%3 == 0:
        check3 = "3の倍数です"
    else:
        check3 = "3の倍数ではない"
    return display(Math("{}={}\\equiv{} \mod 3 \\\\ より{}は{}".format(sum1str,sum1,sum1%3,n,check3)))
```

    interactive(children=(Text(value='1', description='n'), Output()), _dom_classes=('widget-interact',))

```python
from ipywidgets import interact
from ipywidgets import interact,Dropdown,IntSlider
@interact
def _(n="1"):
    dlist = []
    for s in str(n)[::-1]: # 逆順に1文字ずつ登録
        dlist.append(s)
    altsum = int(dlist[0]) # 交代和
    altsumstr = dlist[0] # 計算式
    k = -1
    for s in dlist[1:]:
        d = int(s)
        altsum += d*k
        if k<0:
            altsumstr += "-{}".format(s)
        else:
            altsumstr += "+{}".format(s)
        k *= -1
    if altsum%11 == 0:
        check11 = "11の倍数です"
    else:
        check11 = "11の倍数ではない"
    return display(Math("{}={}\\equiv{} \mod 11 \\\\ より{}は{}".format(
        altsumstr,altsum,altsum%11,n,check11)))
```

    interactive(children=(Text(value='1', description='n'), Output()), _dom_classes=('widget-interact',))

```python
from ipywidgets import interact
from ipywidgets import interact,Dropdown,IntSlider
@interact
def _(n="1"):
    dlist = [n[-3:]] # 最初の3桁
    for i in range(3,len(n),3): # 逆順に3文字ずつ登録
        dlist.append(n[-i-3:-i])
    altsum = int(dlist[0]) # 3桁交代和
    altsumstr = dlist[0] # 計算式
    k = -1
    for s in dlist[1:]:
        d = int(s)
        altsum += d*k
        if k<0:
            altsumstr += "-{}".format(s)
        else:
            altsumstr += "+{}".format(s)
        k *= -1
    if altsum%7 == 0:
        check7 = "7の倍数です"
    else:
        check7 = "7の倍数ではない"
    return display(Math("{}={}\\equiv{} \mod 7 \\\\ より{}は{}".format(
                        altsumstr,altsum,altsum%7,n,check7)))
```

    interactive(children=(Text(value='1', description='n'), Output()), _dom_classes=('widget-interact',))

```python

```


# Random Signals and LTI-Systems

*This jupyter notebook is part of a [collection of notebooks](../index.ipynb) on various topics of Digital Signal Processing. Please direct questions and suggestions to [Sascha.Spors@uni-rostock.de](mailto:Sascha.Spors@uni-rostock.de).*

## Auto-Correlation Function

The auto-correlation function (ACF) $\varphi_{yy}[\kappa]$ of the output signal of an LTI system $y[k] = \mathcal{H} \{ x[k] \}$ is derived. It is assumed that the input signal is a wide-sense stationary (WSS) real-valued random process and that the LTI system has a real-valued impulse repsonse $h[k] \in \mathbb{R}$.

Introducing the output relation $y[k] = h[k] * x[k]$ of an LTI system into the definition of the ACF and rearranging terms yields

\begin{equation}
\begin{split}
\varphi_{yy}[\kappa] &= E \{ y[k+\kappa] \cdot y[k] \} \\
&= E \left\{  \sum_{\mu = -\infty}^{\infty} h[\mu] \; x[k+\kappa-\mu] \cdot
\sum_{\nu = -\infty}^{\infty} h[\nu] \; x[k-\nu] \right\} \\
&= \underbrace{h[\kappa] * h[-\kappa]}_{\varphi_{hh}[\kappa]} * \varphi_{xx}[\kappa]
\end{split}
\end{equation}

where the ACF $\varphi_{hh}[\kappa]$ of the deterministic impulse response $h[k]$ is commonly termed as *filter ACF*. This is related to the [link between ACF and convolution](../random_signals/correlation_functions.ipynb#Definition). The relation above is known as the *Wiener-Lee theorem*. It states that the ACF of the output $\varphi_{yy}[\kappa]$ of an LTI system is given by the convolution of the input signal's ACF $\varphi_{xx}[\kappa]$ with the filter ACF $\varphi_{hh}[\kappa]$. For a system which just attenuates the input signal $y[k] = A \cdot x[k]$ with $A \in \mathbb{R}$, the ACF at the output is given as $\varphi_{yy}[\kappa] = A^2 \cdot \varphi_{xx}[\kappa]$.

### Example - System Response to White Noise

Let's assume that the wide-sense ergodic input signal $x[k]$ of an LTI system with impulse response $h[k] = \text{rect}_N[k]$ is normal distributed white noise. Introducing $\varphi_{xx}[\kappa] = N_0\, \delta[\kappa]$ and $h[k]$ into the Wiener-Lee theorem yields

\begin{equation}
\varphi_{yy}[\kappa] = N_0 \cdot \varphi_{hh}[\kappa] = N_0 \cdot (\text{rect}_N[\kappa] * \text{rect}_N[-\kappa])
\end{equation}

The example is evaluated numerically for $N_0 = 1$ and $N=5$

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

L = 10000  # number of samples
K = 30  # limit for lags in ACF

# generate input signal (white Gaussian noise)
np.random.seed(2)
x = np.random.normal(size=L)
# compute system response
y = np.convolve(x, [1, 1, 1, 1, 1], mode='full')

# compute and truncate ACF
acf = 1/len(y) * np.correlate(y, y, mode='full')
acf = acf[len(y)-K-1:len(y)+K-1]
kappa = np.arange(-K, K)

# plot ACF
plt.figure(figsize=(10, 6))
plt.stem(kappa, acf, use_line_collection=True)
plt.title('Estimated ACF of output signal $y[k]$')
plt.ylabel(r'$\hat{\varphi}_{yy}[\kappa]$')
plt.xlabel(r'$\kappa$')
plt.axis([-K, K, 1.2*min(acf), 1.1*max(acf)])
plt.grid()
```

**Exercise**

* Derive the theoretic result for $\varphi_{yy}[\kappa]$ by calculating the filter-ACF $\varphi_{hh}[\kappa]$.
* Why is the estimated ACF $\hat{\varphi}_{yy}[\kappa]$ of the output signal not exactly equal to its theoretic result $\varphi_{yy}[\kappa]$?
* Change the number of samples `L` and rerun the example. What changes?

Solution: The filter-ACF is given by $\varphi_{hh}[\kappa] = \text{rect}_N[\kappa] * \text{rect}_N[-\kappa]$. The convolution of two rectangular signals $\text{rect}_N[\kappa]$ results in a triangular signal. Taking the time reversal into account yields

\begin{equation}
\varphi_{hh}[\kappa] = \begin{cases}
N - |\kappa| & \text{for } -N < \kappa \leq N \\
0 & \text{otherwise}
\end{cases}
\end{equation}

for even $N$. The estimated ACF $\hat{\varphi}_{yy}[\kappa]$ differs from its theoretic value due to the statistical uncertainties when using random signals of finite length. Increasing its length `L` lowers the statistical uncertainties.

## Cross-Correlation Function

The cross-correlation functions (CCFs) $\varphi_{xy}[\kappa]$ and $\varphi_{yx}[\kappa]$ between the in- and output signal of an LTI system $y[k] = \mathcal{H} \{ x[k] \}$ are derived. As for the ACF it is assumed that the input signal originates from a wide-sense stationary real-valued random process and that the LTI system's impulse response is real-valued, i.e. $h[k] \in \mathbb{R}$.

Introducing the convolution into the definition of the CCF and rearranging the terms yields

\begin{equation}
\begin{split}
\varphi_{xy}[\kappa] &= E \{ x[k+\kappa] \cdot y[k] \} \\
&= E \left\{ x[k+\kappa] \cdot \sum_{\mu = -\infty}^{\infty} h[\mu] \; x[k-\mu] \right\} \\
&= \sum_{\mu = -\infty}^{\infty} h[\mu] \cdot E \{ x[k+\kappa] \cdot x[k-\mu] \} \\
&= h[-\kappa] * \varphi_{xx}[\kappa]
\end{split}
\end{equation}

The CCF $\varphi_{xy}[\kappa]$ between in- and output is given as the time-reversed impulse response of the system convolved with the ACF of the input signal.

The CCF between out- and input is yielded by taking the symmetry relations of the CCF and ACF into account

\begin{equation}
\varphi_{yx}[\kappa] = \varphi_{xy}[-\kappa] = h[\kappa] * \varphi_{xx}[\kappa]
\end{equation}

The CCF $\varphi_{yx}[\kappa]$ between out- and input is given as the impulse response of the system convolved with the ACF of the input signal.

For a system which just attenuates the input signal $y[k] = A \cdot x[k]$, the CCFs between input and output are given as $\varphi_{xy}[\kappa] = A \cdot \varphi_{xx}[\kappa]$ and $\varphi_{yx}[\kappa] = A \cdot \varphi_{xx}[\kappa]$.

## System Identification by Cross-Correlation

The process of determining the impulse response or transfer function of a system is referred to as *system identification*. The CCFs of an LTI system play an important role in the estimation of the impulse response $h[k]$ of an unknown system. This is illustrated in the following.

The basic idea is to use a specific measurement signal as input signal to the system. Let's assume that the unknown LTI system is excited by [white noise](../random_signals/white_noise.ipynb). The ACF of the wide-sense stationary input signal $x[k]$ is then given as $\varphi_{xx}[\kappa] = N_0 \cdot \delta[\kappa]$. According to the relation derived above, the CCF between out- and input for this special choice of the input signal becomes

\begin{equation}
\varphi_{yx}[\kappa] = h[\kappa] * N_0 \cdot \delta[\kappa] = N_0 \cdot h[\kappa]
\end{equation}

For white noise as input signal $x[k]$, the impulse response of an LTI system can be estimated by estimating the CCF between its out- and input signals. Using noise as measurement signal instead of a Dirac impulse is beneficial since its [crest factor](https://en.wikipedia.org/wiki/Crest_factor) is limited.

### Example

The application of the CCF to the identification of a system is demonstrated. The system is excited by wide-sense ergodic normal distributed white noise with $N_0 = 1$. The ACF of the in- and output, as well as the CCF between out- and input is estimated and plotted.

```python
import scipy.signal as sig

N = 10000  # number of samples for input signal
K = 50  # limit for lags in ACF

# generate input signal
np.random.seed(5)  # normally distributed (zero-mean, unit-variance) white noise
x = np.random.normal(size=N)
# impulse response of the system
h = np.concatenate((np.zeros(10), sig.triang(10), np.zeros(10)))
# output signal by convolution
y = np.convolve(h, x, mode='full')

# compute correlation functions
acfx = 1/len(x) * np.correlate(x, x, mode='full')
acfy = 1/len(y) * np.correlate(y, y, mode='full')
ccfyx = 1/len(y) * np.correlate(y, x, mode='full')

def plot_correlation_function(cf):
    cf = cf[N-K-1:N+K-1]
    kappa = np.arange(-len(cf)//2, len(cf)//2)
    plt.stem(kappa, cf, use_line_collection=True)
    plt.xlabel(r'$\kappa$')
    plt.axis([-K, K, -0.2, 1.1*max(cf)])

# plot ACFs and CCF
plt.rc('figure', figsize=(10, 3))
plt.figure()
plot_correlation_function(acfx)
plt.title('Estimated ACF of input signal')
plt.ylabel(r'$\hat{\varphi}_{xx}[\kappa]$')

plt.figure()
plot_correlation_function(acfy)
plt.title('Estimated ACF of output signal')
plt.ylabel(r'$\hat{\varphi}_{yy}[\kappa]$')

plt.figure()
plot_correlation_function(ccfyx)
plt.plot(np.arange(len(h)), h, 'g-')
plt.title('Estimated and true impulse response')
plt.ylabel(r'$\hat{h}[k]$, $h[k]$')
```

**Exercise**

* Why is the estimated CCF $\hat{\varphi}_{yx}[k]$ not exactly equal to the true impulse response $h[k]$ of the system?
* What changes if you change the number of samples `N` of the input signal?

Solution: The derived relations for system identification hold for the case of a wide-sense ergodic input signal of infinite duration. Since we can only numerically simulate signals of finite duration, the observed deviations are a result of the resulting statistical uncertainties. Increasing the length `N` of the input signal improves the estimate of the impulse response.

**Copyright**

This notebook is provided as [Open Educational Resource](https://en.wikipedia.org/wiki/Open_educational_resources). Feel free to use the notebook for your own purposes. The text is licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/), the code of the IPython examples under the [MIT license](https://opensource.org/licenses/MIT). Please attribute the work as follows: *Sascha Spors, Digital Signal Processing - Lecture notes featuring computational examples*.


```python
import sympy as sm
sm.init_printing()
from pchem import solve
```

```python
P, V, n, R, T = sm.symbols('P V n R T', positive=True)
subs = dict(
P=2,
V=0.1,
R=0.083145,
T=275,
n=1,
)
gas_law = P*V - n * R *T
n1 = solve(gas_law, n, subs)
```

```python
R_J = 8.3145
n1 * 5/2*R_J*(550-275)
```

```python
sm.diff(5/2*R*T, T)
```

```python

```


```python
import numpy as np
import scipy as sci
import matplotlib.pyplot as plt
from jinja2 import Template
from pyne import data
from pyne.material import Material
from pyne import serpent
from pyne import nucname
import pdb
```

First we want the helium density in $10^24$ atoms per cc.

\begin{align}
PV&=nRT\\
\frac{n}{V}&=\frac{P}{RT}
\end{align}

```python

R = 8.314 # j/k-mol
temp = [250 ,750] #celsius
pres = [6*10**6, 5.84*10**6] #pascals
hedensity = np.zeros(len(temp))

for i, t in enumerate(temp):
    hedensity[i] = ((pres[i])/((t+273.15)*R)) # mol/m^3

hedensavg = np.sum(hedensity)/len(hedensity)*(6.022*10**-7)# 10^24/cc

print(hedensavg)
```

    0.000622077124202638

### Isotopic composition of the fuel

The enrichment of the UCO kernels in the TRISO particles is known.  Additionally, we know the molar contentrations of the three compounds that make UCO: $UO_2$ , $UC$, and $UC_{1.86}$. The [PyNE](https://pyne.io/) python library is used to create the material cards for the fuel.

UCO is created from $UO_2$, $UC$, and $UC_{1.86}$ in molar concentrations of 0.714, 0.164, and 0.123, respectively.

```python
leu = Material({'U235': 0.1975 , 'U238': 0.8025})
uo2 = Material()
uc = Material()
uc186 = Material()

uo2.from_atom_frac({'O16': 2.0 , leu :1.0})
uc.from_atom_frac({'C12':1, leu: 1.0})
uc186.from_atom_frac({'C12':1.86, leu : 1.0})

uco = Material()
uco.from_atom_frac({uo2: 0.714, uc: 0.164, uc186: 0.123})
print(uco.mcnp()) #the negative values for composition mean it is mass fraction, just like serpent

```

    m?
         6012 -1.7770e-02
         8016 -8.6113e-02
         92235 -1.7698e-01
         92238 -7.1913e-01

    /home/zoe/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:1: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
      """Entry point for launching an IPython kernel.

In order to find the core dimensions, I used an image of the reactor core given in a public presentation to the NRC.

Then, I opened it in an image viewer that gave the cursor's coordinates in pixels, measured the reactor core features in pixels.  Knowing the outer core diameter, developed a scale between pixels and meters, and used that to determine the core geometry.

\begin{align}
s (\frac{cm}{px}) = 100* \frac{RPV_D (m)}{RPV_D (px)}\\
x (cm) = x (px) * scale
\end{align}

```python
core_dm = 4.88 # m
core_dpx = 69 #px
scale = core_dm/core_dpx *100 # cm/px

inner_corerad_px = 17.5
inner_corerad_cm = inner_corerad_px *scale
reflect_px = 13
reflect_cm = reflect_px *scale

print(2*(reflect_cm+inner_corerad_cm)) #can't be more than 488, which is the diameter including RPV
print()
print(inner_corerad_cm)
print(reflect_cm)
print(inner_corerad_cm+reflect_cm)
```

    431.4202898550724

    123.76811594202897
    91.94202898550724
    215.7101449275362

I found the midpoint of the cones on the top and bottom , and used the distance between these two midpoints as an adjusted height.  Then, I simplified by letting the reflector directly surround this region.

### Creating the input file

The first step to creating the input file is to create the pebble and particle location input cards.  This can be done using serpent's built in particle dispersal routine, which takes the dimensions of the particle, the volume they are dispersed in, and the particle number or packing fraction, the universe the particles are in, and creates a plain text file, where each line is:

\begin{align}
x-coordinate\ \ y-coordinate\ \  z-coordinate\ \  universe
\end{align}

In order to split the automatically generated distribution file into several sub-files for modeling different fuel compositions, the cell below splits a given text file into multiple.  It can also append a character to the universe name, if needed.

origin = open('pbbls-20.inp','r')

filenames = ['pbbls-20-0.inp','pbbls-20-1.inp','pbbls-20-2.inp','pbbls-20-3.inp','pbbls-20-4.inp','pbbls-20-5.inp','pbbls-20-6.inp']

lines = 0
for line in list(origin):
    lines += 1
origin.seek(0)
i = 0

file = ['f0','f1','f2','f3','f4','f5','f6']
for index, name in enumerate(filenames):
    file[index] = open(name,'a+')
subuniverse = ['a','b','c','d']

while i < lines:
    for index, f in enumerate(file):
        #use lines below if you need to append a new character to universe name
        #newline = origin.readline()
        #f.write(newline[:-1] + subuniverse[index] + newline[-1:])
        f.write(origin.readline())
        i += 1
        if i == lines:
            break
for f in file:
    f.close()
origin.close()

After the above is used to create the input file, it is run in the terminal with Serpent 2.1.31

```python
dep = serpent.parse_dep('../single-pebble/burn-20/htgr-mr-burn-20.inp_dep.m')
```

```python
mats = dep['MAT_fuel_MATERIAL']
#mats[1].comp.keys
newdict = {nucname.serpent(k): v for k,v in mats[6].comp.items()}

for k,v in newdict.items():
    print('{}.08c'.format(k),"     " ,v)
```

    H-1.08c       1.4039761314610683e-08
    H-2.08c       8.575872266426048e-09
    H-3.08c       1.7809450461743885e-07
    He-3.08c       2.403414454964853e-09
    He-4.08c       4.1936188654818196e-06
    Li-6.08c       3.7024814426859424e-08
    Li-7.08c       1.3328754919071045e-08
    Be-9.08c       3.1081994308479828e-09
    B-10.08c       2.3048449742374247e-09
    B-11.08c       4.931884902411149e-10
    C-nat.08c       0.017770521110676857
    N-14.08c       2.1979529803730034e-12
    N-15.08c       1.618496866254497e-09
    O-16.08c       0.08611036021385192
    O-17.08c       2.766667140376986e-08
    Ca-46.08c       6.52385887434054e-26
    Ca-48.08c       1.5542179587800361e-30
    Ti-47.08c       1.0258065919017555e-23
    Ti-48.08c       1.5385370706399525e-24
    Ti-49.08c       1.8358918241640898e-17
    Ti-50.08c       9.113288319649272e-15
    Cr-52.08c       3.286683228976092e-14
    Cr-53.08c       6.121212960079972e-14
    Cr-54.08c       1.178486051483799e-13
    Mn-55.08c       2.0837389940765826e-13
    Fe-56.08c       4.405310855372948e-13
    Fe-57.08c       1.199569751430849e-12
    Fe-58.08c       1.1696268954232183e-12
    Co-58.08c       2.402495795044813e-20
    Co-59.08c       2.6646776979670346e-12
    Ni-60.08c       9.624827269184251e-15
    Ni-61.08c       1.970598293627337e-11
    Ni-62.08c       3.251437613231995e-11
    Ni-64.08c       8.867360330361813e-11
    Cu-63.08c       4.4008176078432486e-13
    Cu-65.08c       1.7387503593519712e-10
    Ge-70.08c       3.400460624419425e-14
    Ge-72.08c       1.9313233081349465e-08
    Ge-73.08c       4.6451083814316273e-08
    Ge-74.08c       1.6058539228004855e-07
    Ge-76.08c       1.5540542372101281e-06
    As-75.08c       3.0189438883197693e-07
    Se-74.08c       6.961495726347571e-15
    Se-76.08c       3.978161279482806e-09
    Se-77.08c       2.810690051396711e-06
    Se-78.08c       7.786761586397492e-06
    Se-79.08c       1.463989182475147e-05
    Se-80.08c       4.577254886347508e-05
    Se-82.08c       0.00011516630009471945
    Br-79.08c       5.045280280858029e-11
    Br-81.08c       6.863144541046688e-05
    Kr-80.08c       1.0215953092980091e-10
    Kr-82.08c       9.205154311653243e-07
    Kr-83.08c       0.00016408539561897208
    Kr-84.08c       0.00039723309724655243
    Kr-85.08c       9.696046152094258e-05
    Kr-86.08c       0.0007233610071177548
    Rb-85.08c       0.0003782614055513167
    Rb-86.08c       3.314188452720647e-08
    Rb-87.08c       0.0009433273032391814
    Sr-84.08c       2.1208310253035318e-13
    Sr-86.08c       6.474842455438619e-07
    Sr-87.08c       7.080184768888145e-09
    Sr-88.08c       0.0013062070672976353
    Sr-89.08c       0.00010505376446006461
    Sr-90.08c       0.0020587532633800573
    Y-89.08c       0.00165270557872251
    Y-90.08c       5.376170669285003e-07
    Y-91.08c       0.0001574082842595548
    Zr-90.08c       7.859463059079445e-05
    Zr-91.08c       0.002084421167284533
    Zr-92.08c       0.0023872242108294995
    Zr-93.08c       0.002605128524734362
    Zr-94.08c       0.0026731639326739356
    Zr-95.08c       0.00022050657730212313
    Zr-96.08c       0.002724854470551021
    Nb-93.08c       5.110332317968168e-10
    Nb-94.08c       1.7335476516860038e-10
    Nb-95.08c       0.00012332418209752742
    Mo-94.08c       6.982661287079576e-10
    Mo-95.08c       0.002351441952161815
    Mo-96.08c       6.521184755365375e-05
    Mo-97.08c       0.0026526714495071096
    Mo-98.08c       0.0026157977137067044
    Mo-99.08c       9.48084324409288e-06
    Mo-100.08c       0.002945705772714228
    Tc-99.08c       0.002658783721450344
    Ru-98.08c       3.3299330103601394e-14
    Ru-99.08c       1.0514017306640498e-07
    Ru-100.08c       0.00017041505342447336
    Ru-101.08c       0.0024791993505467414
    Ru-102.08c       0.002272018799470858
    Ru-103.08c       9.941992332667261e-05
    Ru-104.08c       0.0012946374096907988
    Ru-105.08c       2.5220762105771095e-07
    Ru-106.08c       0.0002676738520283595
    Rh-103.08c       0.0012237004917067431
    Rh-105.08c       1.8791325099053645e-06
    Pd-104.08c       0.0004711824921169164
    Pd-105.08c       0.0007716670563191295
    Pd-106.08c       0.00033717547758288773
    Pd-107.08c       0.0003222167834789542
    Pd-108.08c       0.00020848486624948481
    Pd-110.08c       7.584010613351898e-05
    Ag-107.08c       3.9662641787361535e-11
    Ag-109.08c       0.0001083063663155715
    Ag-111.08c       5.786420540903523e-07
    Cd-108.08c       3.630689534281255e-11
    Cd-110.08c       2.3687418647154336e-05
    Cd-111.08c       3.6454517625012996e-05
    Cd-112.08c       1.7609983015739227e-05
    Cd-113.08c       1.70410505603087e-07
    Cd-114.08c       2.955137954269487e-05
    Cd-116.08c       1.230931527711016e-05
    In-113.08c       1.0656909854599148e-08
    In-115.08c       5.4682276697871984e-06
    Sn-112.08c       5.449772610601451e-16
    Sn-114.08c       3.820097199379341e-10
    Sn-115.08c       4.908781969768567e-07
    Sn-116.08c       4.568586738785153e-06
    Sn-117.08c       1.0589965923792296e-05
    Sn-118.08c       1.1514174185923321e-05
    Sn-119.08c       1.065245298964054e-05
    Sn-120.08c       1.1260587665421277e-05
    Sn-122.08c       1.5802769753237366e-05
    Sn-123.08c       1.950706122883507e-07
    Sn-124.08c       2.4604896133633587e-05
    Sn-125.08c       1.1319527430588177e-07
    Sn-126.08c       5.657025926819515e-05
    Sb-121.08c       9.705869446288743e-06
    Sb-123.08c       1.1775492002782138e-05
    Sb-124.08c       5.056422443254551e-08
    Sb-125.08c       1.9926552273510624e-05
    Sb-126.08c       1.3953807490408949e-08
    Te-120.08c       3.625150287832699e-18
    Te-122.08c       3.148511319616458e-07
    Te-123.08c       1.4683187084349379e-09
    Te-124.08c       2.652689640792655e-07
    Te-125.08c       7.0331239131823495e-06
    Te-126.08c       1.223482196280199e-06
    Te-128.08c       0.00025368657257254714
    Te-130.08c       0.0012035354516797333
    Te-132.08c       1.0814446387421798e-05
    I-127.08c       0.00010533118156463104
    I-129.08c       0.0005388895473523968
    I-130.08c       2.6075388700691195e-08
    I-131.08c       1.8696184675651513e-05
    I-135.08c       1.3022868452626146e-06
    Xe-126.08c       1.2155507957824304e-11
    Xe-128.08c       2.5976519063752267e-06
    Xe-129.08c       9.94481198192679e-09
    Xe-130.08c       1.7192856838184566e-05
    Xe-131.08c       0.0015154978074967786
    Xe-132.08c       0.0030517609674435638
    Xe-133.08c       2.6782938751977217e-05
    Xe-134.08c       0.004799898030136931
    Xe-135.08c       6.459270715011808e-07
    Xe-136.08c       0.0066041006348810365
    Cs-133.08c       0.003789872378445758
    Cs-134.08c       0.0002337107219152086
    Cs-135.08c       0.0017022768318335631
    Cs-136.08c       1.0998724109996098e-06
    Cs-137.08c       0.0038152856043525986
    Ba-132.08c       3.201666255979943e-11
    Ba-134.08c       7.900839138052323e-05
    Ba-135.08c       2.1001384379957073e-07
    Ba-136.08c       3.214809459786451e-05
    Ba-137.08c       0.0001326572211465644
    Ba-138.08c       0.0042493205818216525
    Ba-140.08c       6.274429062585602e-05
    La-138.08c       8.209063184689801e-09
    La-139.08c       0.004009432099335247
    La-140.08c       8.479531218177925e-06
    Ce-140.08c       0.004015708092848389
    Ce-141.08c       0.000152572130997326
    Ce-142.08c       0.0037873710766832738
    Ce-143.08c       6.189548524231043e-06
    Ce-144.08c       0.0012071009436466198
    Pr-141.08c       0.003579535639327757
    Pr-142.08c       1.1052479358782579e-07
    Pr-143.08c       6.191131166073487e-05
    Nd-142.08c       4.2745700861585854e-05
    Nd-143.08c       0.003738527474994034
    Nd-144.08c       0.0023031349933961627
    Nd-145.08c       0.0024229609912833215
    Nd-146.08c       0.002164335484685214
    Nd-147.08c       2.0460102678555464e-05
    Nd-148.08c       0.0011843618367149442
    Nd-150.08c       0.0004967339717939018
    Pm-147.08c       0.0007329832876069628
    Pm-148.08c       2.6268398240327256e-06
    Pm-149.08c       2.9494622731793407e-06
    Pm-151.08c       5.512250580806922e-07
    Sm-147.08c       0.0003673220759886261
    Sm-148.08c       0.00024148112953590085
    Sm-149.08c       7.3091311931190285e-06
    Sm-150.08c       0.0008057429724026613
    Sm-151.08c       4.413733420580441e-05
    Sm-152.08c       0.0003868640644857082
    Sm-153.08c       1.8017467811954855e-06
    Sm-154.08c       8.066843714296329e-05
    Eu-151.08c       3.321583210294828e-08
    Eu-152.08c       9.545149438495657e-08
    Eu-153.08c       0.00028763242096442994
    Eu-154.08c       3.1003044129213055e-05
    Eu-155.08c       6.898053618008195e-06
    Eu-156.08c       6.06030853607417e-06
    Eu-157.08c       2.4534041076434486e-08
    Gd-152.08c       3.5969719865229664e-09
    Gd-154.08c       2.423688642705135e-06
    Gd-155.08c       1.0104349556159427e-07
    Gd-156.08c       0.00012224907712179777
    Gd-157.08c       2.311284689320475e-07
    Gd-158.08c       3.002344340259646e-05
    Gd-160.08c       3.6021292159750704e-06
    Tb-159.08c       5.320869161227167e-06
    Tb-160.08c       7.159189522011559e-08
    Dy-160.08c       1.9064740120800111e-07
    Dy-161.08c       1.474403693449854e-06
    Dy-162.08c       1.4674728136570795e-06
    Dy-163.08c       8.475210787860906e-07
    Dy-164.08c       2.1274162706709452e-07
    Ho-165.08c       4.589624960518339e-07
    Er-162.08c       7.575069596506364e-22
    Er-164.08c       1.2478949014820462e-12
    Er-166.08c       1.2077558299262522e-07
    Er-167.08c       1.0917409063608425e-08
    Er-168.08c       7.132129984762866e-08
    Er-170.08c       7.789790435440792e-09
    Lu-175.08c       3.121306252083401e-10
    Lu-176.08c       1.4730575383194993e-12
    Hf-174.08c       1.125713132116766e-20
    Hf-176.08c       1.3028052969006565e-11
    Hf-177.08c       4.563347648548094e-11
    Hf-178.08c       8.29646321609239e-11
    Hf-179.08c       3.887896120606826e-11
    Hf-180.08c       1.4972155655237113e-11
    Ta-181.08c       6.070359221337971e-12
    Ta-182.08c       3.136887088152985e-14
    W-182.08c       6.283579279214908e-14
    W-183.08c       2.442689440457243e-12
    W-184.08c       1.1926388716380743e-12
    W-186.08c       2.9865634000490633e-13
    Re-185.08c       3.2405228419047907e-13
    Re-187.08c       1.799491061787863e-13
    Ir-191.08c       1.4457888012870338e-16
    Ir-193.08c       1.4222128952202722e-16
    Au-197.08c       5.381455237735923e-18
    Hg-196.08c       2.5301531413592404e-20
    Hg-198.08c       2.0837662810049006e-18
    Hg-199.08c       1.3929522124205919e-18
    Hg-200.08c       2.1318822312723265e-18
    Hg-201.08c       1.0895943346664923e-21
    Pb-206.08c       8.041175810391852e-19
    Pb-207.08c       1.983505010721756e-16
    Pb-208.08c       4.350400459954338e-13
    Bi-209.08c       1.0206948406635148e-17
    Ra-223.08c       7.865693574378725e-18
    Ra-224.08c       7.464875884315463e-15
    Ra-225.08c       1.024060228489403e-18
    Ra-226.08c       3.4134037240849344e-17
    Ac-225.08c       6.625684595180583e-19
    Ac-226.08c       3.22032141930669e-23
    Ac-227.08c       6.357881585024852e-15
    Th-227.08c       1.3121010438143258e-17
    Th-228.08c       1.4760772917200254e-12
    Th-229.08c       2.0031788860390415e-13
    Th-230.08c       4.728897442653467e-12
    Th-232.08c       8.803545300668759e-10
    Th-234.08c       1.0061054296561518e-11
    Pa-231.08c       1.1117058422468537e-10
    Pa-232.08c       5.162432159770024e-13
    Pa-233.08c       2.36133801149848e-11
    U-232.08c       1.9014441216267248e-10
    U-233.08c       4.605014788089697e-10
    U-234.08c       2.0524590785813694e-06
    U-235.08c       0.06474769690296438
    U-236.08c       0.019038089887476187
    U-237.08c       1.8842897393574686e-05
    U-238.08c       0.6919173700526337
    Np-235.08c       6.879625845750764e-13
    Np-236.08c       1.0675374009427669e-10
    Np-237.08c       0.000747542682993176
    Np-238.08c       2.5556209411227175e-06
    Np-239.08c       9.927348347803263e-05
    Pu-236.08c       7.055435524903709e-13
    Pu-237.08c       2.1334830644003167e-11
    Pu-238.08c       0.0002880690118175181
    Pu-239.08c       0.0038013874621959584
    Pu-240.08c       0.002441416050469069
    Pu-241.08c       0.0021209310773740316
    Pu-242.08c       0.001197523231806998
    Pu-243.08c       1.3815735633119815e-07
    Pu-244.08c       1.4219036433660014e-08
    Pu-246.08c       4.217476736474533e-26
    Am-241.08c       4.086726871617399e-05
    Am-242.08c       2.2492069273970006e-07
    Am-243.08c       0.00010783885027705624
    Am-244.08c       3.4243457823404566e-09
    Cm-240.08c       6.512325599304793e-15
    Cm-241.08c       5.922982521492395e-13
    Cm-242.08c       3.302973525181945e-05
    Cm-243.08c       3.8386795975639064e-07
    Cm-244.08c       1.769630066565187e-05
    Cm-245.08c       4.112394775521873e-07
    Cm-246.08c       2.0879866592514197e-08
    Cm-247.08c       8.544692403001335e-11
    Cm-248.08c       2.804314005813068e-12
    Cm-249.08c       1.523411016709002e-17
    Cm-250.08c       4.5126212488049116e-20
    Bk-249.08c       1.8548380480595623e-14
    Cf-249.08c       3.4291846642955174e-15
    Cf-251.08c       9.052456660785653e-22

```python
serpent.parse_res('../full-core/htgr-20/htgr-20-full-core.inp_res.m')
```

    {'np': <module 'numpy' from '/home/zoe/anaconda3/lib/python3.7/site-packages/numpy/__init__.py'>,
     'TOT_FLUX': array([[9.3077e+02, 3.5800e-03]]),
     'TOT_CUTRATE': array([[0., 0.]]),
     'PU239_CAPT': array([[0.0191427, 0.02283  , 0.0424034, 0.02234  ]]),
     'ANA_INV_SPD': array([[1.51219e-06, 2.35000e-03]]),
     'RES_MEMSIZE': array([30.69]),
     'URES_DILU_CUT': array([1.e-09]),
     'SRC_NORM_MODE': array([2]),
     'TOT_RR': array([[3.1922e+02, 4.5900e-03]]),
     'ANA_EALF': array([[1.78105e-07, 1.31000e-02]]),
     'TOT_NUCLIDES': array([316]),
     'PU240_FISS': array([[1.91763e-05, 7.03540e-01, 4.48023e-05, 7.03600e-01]]),
     'ADJ_NAUCHI_LIFETIME': array([[0.00100877, 0.00856   , 0.00100924, 0.00871   , 0.00082817,
             0.12952   ]]),
     'TOT_SF_RATE': array([0.]),
     'ANA_THERM_TIME': array([[0.00182407, 0.00564   , 0.0018247 , 0.00566   , 0.00169437,
             0.08766   ]]),
     'TOT_CELLS': array([4]),
     'ELECTRON_DECAY_SOURCE': array([0.]),
     'VERSION': array([b'Serpent 2.1.31'], dtype='|S14'),
     'REA_SAMPLING_EFF': array([[1., 0., 0., 0.]]),
     'POP': array([1000]),
     'GROUP_CONSTANT_GENERATION': array([0]),
     'DT_EFF': array([[8.82274e-01, 4.00000e-04, 0.00000e+00, 0.00000e+00]]),
     'IMP_ALF': array([[0., 0.]]),
     'SIX_FF_F': array([[0.806689, 0.00207 ]]),
     'SIX_FF_KEFF': array([[1.05763, 0.00424]]),
     'BRA_DATA_FILE_PATH': array([b'N/A'], dtype='|S3'),
     'BALA_LOSS_NEUTRON_TOT': array([[100273., 100004.]]),
     'IFP_CHAIN_LENGTH': array([15]),
     'MG_MAJORANT_MODE': array([0]),
     'START_DATE': array([b'Thu Oct  8 09:33:08 2020'], dtype='|S24'),
     'ADJ_MEULEKAMP_BETA_EFF': array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
             0., 0.]]),
     'SAMPLE_FISS': array([1]),
     'BALA_LOSS_NEUTRON_ERR': array([[0., 0.]]),
     'ADJ_PERT_GEN_TIME': array([[0.00095968, 0.00618   ]]),
     'REA_SAMPLING_FAIL': array([[0., 0., 0., 0.]]),
     'INIT_TIME': array([[0.4074, 0.4074]]),
     'UNION_CELLS': array([0]),
     'COL_KEFF': array([[1.05763, 0.00424]]),
     'ABS_KINF': array([[1.20568, 0.00392]]),
     'ANA_DELAYED_EMTIME': array([[13.8674 ,  0.10186]]),
     'USE_URES': array([0]),
     'CONVERSION_RATIO': array([[0.325096, 0.00852 ]]),
     'UNKNOWN_MEMSIZE': array([0.]),
     'B1_CALCULATION': array([[0., 0., 0.]]),
     'ANA_THERM_FRAC': array([[0.754093, 0.00176 , 0.754085, 0.00173 , 0.903614, 0.0633  ]]),
     'PU241_CAPT': array([[0.00403593, 0.04573   , 0.00893952, 0.04559   ]]),
     'ADJ_PERT_BETA_EFF': array([[0.00677884, 0.03251   ]]),
     'U238_CAPT': array([[0.154593, 0.00716 , 0.342579, 0.00639 ]]),
     'ABS_KEFF': array([[1.05763, 0.00424]]),
     'TOT_POWDENS': array([[0., 0.]]),
     'ACTINIDE_ACTIVITY': array([0.]),
     'GEOM_ALBEDO': array([[1., 0., 1., 0., 1., 0.]]),
     'RECONSTRUCT_MICROXS': array([0]),
     'IMPL_FISS': array([0]),
     'SAMPLE_SCATT': array([1]),
     'TOT_FMASS': array([0.]),
     'RECONSTRUCT_MACROXS': array([0]),
     'UNUSED_MEMSIZE': array([115.55]),
     'SFY_DATA_FILE_PATH': array([b'N/A'], dtype='|S3'),
     'LOST_PARTICLES': array([0]),
     'ST_FRAC': array([[0.129563, 0.00446 , 0.      , 0.      ]]),
     'SHARE_RES2_ARRAY': array([1]),
     'NEUTRON_EMAX': array([20.]),
     'U235_CAPT': array([[0.0826571, 0.01047  , 0.183158 , 0.00988  ]]),
     'INI_FMASS': array([0.]),
     'ADJ_PERT_LIFETIME': array([[0.00101256, 0.00407   ]]),
     'MPI_REPRODUCIBILITY': array([0]),
     'SIX_FF_ETA': array([[1.84948, 0.00272]]),
     'AVG_REAL_COL': array([[3.19211e+02, 4.59000e-03, 0.00000e+00, 0.00000e+00]]),
     'ADJ_IFP_IMP_LAMBDA': array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
             0., 0.]]),
     'TOT_SRCRATE': array([[9.99961e-01, 1.90000e-05]]),
     'SIX_FF_P': array([[0.718891, 0.00205 ]]),
     'ADJ_IFP_ROSSI_ALPHA': array([[0., 0.]]),
     'FISSE': array([[2.02947e+02, 4.60000e-05]]),
     'TOT_PHOTON_NUCLIDES': array([0]),
     'CPU_USAGE': array([3.34979]),
     'BATCH_INTERVAL': array([1]),
     'TOT_COL_EFF': array([[8.7259e-01, 4.6000e-04, 0.0000e+00, 0.0000e+00]]),
     'TOT_REA_CHANNELS': array([7308]),
     'BALA_SRC_NEUTRON_VR': array([[0., 0.]]),
     'AVAIL_MEM': array([32014.02]),
     'TOT_POWER': array([[1.3846e-11, 4.2600e-03]]),
     'SIMULATED_HISTORIES': array([100273]),
     'PHOTON_TRANSPORT_MODE': array([0]),
     'MIN_MACROXS': array([[0.05, 0.  , 0.  , 0.  ]]),
     'NEUTRON_EMIN': array([1.e-11]),
     'IMP_AFGE': array([[0., 0.]]),
     'TITLE': array([b'Untitled'], dtype='|S8'),
     'ADJ_IFP_IMP_BETA_EFF': array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
             0., 0.]]),
     'U235_FISS': array([[0.379486, 0.00476 , 0.891112, 0.00164 ]]),
     'BALA_LOSS_NEUTRON_LEAK': array([[12321. , 12285.1]]),
     'OMP_SHARED_QUEUE_LIM': array([0]),
     'CPU_MHZ': array([220.]),
     'ALPHA_DECAY_SOURCE': array([0.]),
     'TOT_FISSRATE': array([[0.425824, 0.00427 ]]),
     'NORM_COEF': array([[9.99961e-04, 1.90000e-05, 0.00000e+00, 0.00000e+00]]),
     'IFC_MEMSIZE': array([0.]),
     'IMPL_NXN': array([1]),
     'TOT_CPU_TIME': array([8.24053]),
     'OMP_PARALLEL_FRAC': array([0.83553]),
     'FISSION_PRODUCT_INH_TOX': array([0.]),
     'PU239_FISS': array([[0.0349243, 0.01539  , 0.0820846, 0.01536  ]]),
     'NEUTRON_TRANSPORT_MODE': array([1]),
     'TOT_ACTIVITY': array([0.]),
     'ADJ_NAUCHI_LAMBDA': array([[3.88742e-01, 9.76300e-02, 1.24667e-02, 0.00000e+00, 2.82917e-02,
             0.00000e+00, 4.25244e-02, 0.00000e+00, 1.33042e-01, 5.00000e-09,
             2.92467e-01, 0.00000e+00, 6.66488e-01, 4.60000e-09, 1.63478e+00,
             3.80000e-09, 3.55460e+00, 0.00000e+00]]),
     'SIX_FF_LT': array([[0.877538, 0.00118 ]]),
     'DT_FRAC': array([[8.70437e-01, 6.60000e-04, 0.00000e+00, 0.00000e+00]]),
     'SKIP': array([50]),
     'AVG_TRACKING_LOOPS': array([[2.63819e+00, 1.70000e-03, 0.00000e+00, 0.00000e+00, 0.00000e+00,
             0.00000e+00, 0.00000e+00, 0.00000e+00]]),
     'TOT_TRANSMU_REA': array([0]),
     'PU241_FISS': array([[0.0102574, 0.03068  , 0.0240757, 0.03044  ]]),
     'ANA_MEAN_NCOL': array([[3.19211e+02, 4.59000e-03, 2.43344e+02, 5.04000e-03]]),
     'BALA_SRC_NEUTRON_NXN': array([[0.     , 3.93141]]),
     'NEUTRON_ERG_TOL': array([0.]),
     'TOT_ABSRATE': array([[0.877154, 0.00117 ]]),
     'TOT_PHOTON_PRODRATE': array([[0., 0., 0., 0.]]),
     'ADJ_IFP_ANA_BETA_EFF': array([[7.40891e-03, 1.75210e-01, 1.05042e-04, 1.00000e+00, 1.84086e-03,
             3.25290e-01, 1.00948e-03, 3.85840e-01, 1.69922e-03, 3.22050e-01,
             8.50243e-04, 4.05490e-01, 5.00441e-04, 6.99510e-01, 1.20680e-03,
             4.36730e-01, 1.96834e-04, 7.89880e-01]]),
     'COMPLETE_DATE': array([b'Thu Oct  8 09:35:36 2020'], dtype='|S24'),
     'CYCLES': array([100]),
     'TOT_LEAKRATE': array([[0.122846, 0.00838 ]]),
     'DECAY_DATA_FILE_PATH': array([b'N/A'], dtype='|S3'),
     'ANA_AFGE': array([[0.0106403, 0.08362  ]]),
     'URES_EMIN': array([1.e+37]),
     'MEAN_POP_WGT': array([[1.00273e+03, 6.03000e-03]]),
     'SIX_FF_EPSILON': array([[1.12429, 0.00161]]),
     'ANA_KEFF': array([[1.05722   , 0.00437   , 1.05091   , 0.00418   , 0.00671967,
             0.05835   ]]),
     'USE_DBRC': array([0]),
     'ACTINIDE_ING_TOX': array([0.]),
     'BALA_LOSS_NEUTRON_CAPT': array([[45253. , 45134.8]]),
     'TMS_MODE': array([0]),
     'CRIT_SPEC_MODE': array([0]),
     'NEUTRON_ERG_NE': array([3277381]),
     'IMPL_CAPT': array([0]),
     'FISSION_PRODUCT_ING_TOX': array([0.]),
     'BALA_NEUTRON_DIFF': array([[ 0.00000e+00, -8.73115e-11]]),
     'IMPLICIT_REACTION_RATES': array([0]),
     'TOT_DECAY_NUCLIDES': array([0]),
     'DOPPLER_PREPROCESSOR': array([0]),
     'CYCLE_IDX': array([100]),
     'CPU_TYPE': array([b'Intel(R) Xeon(R) CPU E3-1225 v5 @ 3.30GHz'], dtype='|S41'),
     'CONFIDENTIAL_DATA': array([0]),
     'ESTIMATED_RUNNING_TIME': array([[2.45692, 0.     ]]),
     'ALLOC_MEMSIZE': array([2145.58]),
     'XS_DATA_FILE_PATH': array([b'/home/zoe/xsdata/jeff312/sss_jeff312.xsdata'], dtype='|S43'),
     'AVG_SURF_CROSS': array([[4.98209e+01, 2.12000e-03, 0.00000e+00, 0.00000e+00]]),
     'TOT_TRANSPORT_NUCLIDES': array([316]),
     'CS137_ACTIVITY': array([0.]),
     'PHOTON_DECAY_SOURCE': array([0.]),
     'SM149_CAPT': array([[0.00369083, 0.05636   , 0.00818966, 0.05664   ]]),
     'SR90_ACTIVITY': array([0.]),
     'BALA_LOSS_NEUTRON_CUT': array([[0., 0.]]),
     'B1_BURNUP_CORRECTION': array([0]),
     'TOT_CAPTRATE': array([[0.45133, 0.00372]]),
     'ADJ_PERT_ROSSI_ALPHA': array([[-7.07219,  0.03239]]),
     'MEAN_POP_SIZE': array([[1.00273e+03, 6.03000e-03]]),
     'URES_AVAIL': array([152]),
     'SHARE_BUF_ARRAY': array([0]),
     'FISSION_PRODUCT_ACTIVITY': array([0.]),
     'ADJ_NAUCHI_GEN_TIME': array([[0.00095644, 0.01019   , 0.00095686, 0.01029   , 0.00078566,
             0.13059   ]]),
     'AVG_VIRT_COL': array([[4.65468e+01, 1.91000e-03, 0.00000e+00, 0.00000e+00]]),
     'I131_ACTIVITY': array([0.]),
     'MEMSIZE': array([2030.03]),
     'RUNNING_TIME': array([2.46002]),
     'COMPILE_DATE': array([b'Aug 23 2019 14:35:57'], dtype='|S20'),
     'UFS_ORDER': array([1.]),
     'MPI_TASKS': array([1]),
     'AVG_TRACKS': array([[3.19334e+02, 4.59000e-03, 0.00000e+00, 0.00000e+00]]),
     'INHALATION_TOXICITY': array([0.]),
     'U238_FISS': array([[0.00095697, 0.09563   , 0.00226411, 0.09631   ]]),
     'INGESTION_TOXICITY': array([0.]),
     'TOT_DECAY_HEAT': array([0.]),
     'ADJ_MEULEKAMP_LAMBDA': array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
             0., 0.]]),
     'HOSTNAME': array([b'Talbot226-Precision-Tower-3420'], dtype='|S30'),
     'SIMULATION_COMPLETED': array([1]),
     'TRANSPORT_CPU_USAGE': array([[3.8639e+00, 3.8600e-03]]),
     'OPTIMIZATION_MODE': array([1]),
     'OMP_HISTORY_PROFILE': array([[0.994381, 0.993636, 1.01266 , 0.999328]]),
     'SIX_FF_KINF': array([[1.20568, 0.00392]]),
     'BALA_LOSS_NEUTRON_FISS': array([[42699., 42584.]]),
     'OMP_THREADS': array([4]),
     'CS134_ACTIVITY': array([0.]),
     'BALA_SRC_NEUTRON_TOT': array([[100273., 100004.]]),
     'ADJ_IFP_LIFETIME': array([[0.00100872, 0.02506   , 0.00101028, 0.02531   , 0.00041787,
             0.22276   ]]),
     'ADJ_IFP_GEN_TIME': array([[0.00095488, 0.02493   , 0.00095645, 0.02522   , 0.00039806,
             0.22288   ]]),
     'ALBEDO_LEAKRATE': array([[0., 0.]]),
     'ACTINIDE_DECAY_HEAT': array([0.]),
     'TE132_ACTIVITY': array([0.]),
     'IMP_KEFF': array([[1.05763, 0.00424]]),
     'TOT_DOSIMETRY_NUCLIDES': array([0]),
     'TOT_LOSSRATE': array([[1., 0.]]),
     'TOT_GENRATE': array([[1.05759, 0.00424]]),
     'IMP_EALF': array([[20.,  0.]]),
     'NEUTRON_DECAY_SOURCE': array([0.]),
     'PU240_CAPT': array([[0.0173142, 0.02214  , 0.0383578, 0.02171  ]]),
     'UFS_MODE': array([0]),
     'DEBUG': array([0]),
     'MPI_OVERHEAD_TIME': array([[0., 0.]]),
     'DOUBLE_INDEXING': array([0]),
     'ADJ_IFP_ANA_LAMBDA': array([[3.92775e-01, 2.42450e-01, 1.24667e-02, 0.00000e+00, 2.82917e-02,
             0.00000e+00, 4.25244e-02, 0.00000e+00, 1.33042e-01, 0.00000e+00,
             2.92467e-01, 3.90000e-09, 6.66488e-01, 0.00000e+00, 1.63478e+00,
             0.00000e+00, 3.55460e+00, 0.00000e+00]]),
     'BALA_SRC_NEUTRON_FISS': array([[100273., 100000.]]),
     'ADJ_NAUCHI_BETA_EFF': array([[6.26892e-03, 5.86200e-02, 2.92733e-04, 2.64520e-01, 1.20637e-03,
             1.29130e-01, 4.99253e-04, 2.29760e-01, 1.04269e-03, 1.30460e-01,
             1.73179e-03, 1.17480e-01, 7.54190e-04, 1.89200e-01, 6.35833e-04,
             1.96450e-01, 1.06069e-04, 5.00230e-01]]),
     'ANA_SLOW_TIME': array([[3.94887e-05, 1.61000e-03, 3.94822e-05, 1.61000e-03, 4.08988e-05,
             2.44200e-02]]),
     'MAT_MEMSIZE': array([4.55]),
     'USE_DELNU': array([1]),
     'SIX_FF_LF': array([[9.99559e-01, 6.60000e-05]]),
     'BALA_SRC_NEUTRON_SRC': array([[0., 0.]]),
     'PRECURSOR_GROUPS': array([8]),
     'ACTINIDE_INH_TOX': array([0.]),
     'TRANSPORT_CYCLE_TIME': array([[1.91707, 1.91707, 0.     ]]),
     'URES_EMAX': array([-1.e+37]),
     'WORKING_DIRECTORY': array([b'/home/zoe/Documents/uiuc-microreactors/htgr-mr/full-core/htgr-20'],
           dtype='|S64'),
     'INPUT_FILE_NAME': array([b'htgr-20-full-core.inp'], dtype='|S21'),
     'SAMPLE_CAPT': array([1]),
     'FWD_ANA_LAMBDA': array([[0.402581  , 0.06846   , 0.00249334, 0.20101   , 0.0195213 ,
             0.06737   , 0.0178602 , 0.11811   , 0.0944598 , 0.06423   ,
             0.242748  , 0.04548   , 0.266595  , 0.12309   , 0.67026   ,
             0.12056   , 0.497644  , 0.2491    ]]),
     'OMP_REPRODUCIBILITY': array([1]),
     'NUBAR': array([[2.48366e+00, 2.60000e-04]]),
     'XS_MEMSIZE': array([1949.35]),
     'PROCESS_TIME': array([[0.13555, 0.13555]]),
     'FISSION_PRODUCT_DECAY_HEAT': array([0.]),
     'XE135_CAPT': array([[0.010857 , 0.02993  , 0.0240953, 0.03008  ]]),
     'SEED': array([1602167588952]),
     'MISC_MEMSIZE': array([45.44]),
     'I132_ACTIVITY': array([0.]),
     'FWD_ANA_BETA_ZERO': array([[5.60575e-03, 4.06000e-02, 1.99883e-04, 2.08180e-01, 9.99651e-04,
             9.26300e-02, 5.01447e-04, 1.33630e-01, 1.08743e-03, 8.67100e-02,
             1.67557e-03, 7.67700e-02, 5.22515e-04, 1.45770e-01, 4.74484e-04,
             1.37020e-01, 1.44776e-04, 2.59130e-01]]),
     'NFY_DATA_FILE_PATH': array([b'N/A'], dtype='|S3'),
     'ANA_ALF': array([[1.85449e+01, 7.00000e-04]]),
     'URES_USED': array([0]),
     'DT_THRESH': array([[0.9, 0.9]]),
     'IDX': 1,
     'idx': 0}

```python
fuel = (4/3)*np.pi*(0.02125**3)*(18000)
pyroc = 18000*( ((4/3)*np.pi*(0.03475**3) -
                 (4/3)*np.pi*(0.03075**3))
         + ( (4/3)*np.pi*(0.04225**3) -
            (4/3)*np.pi*(0.03828**3) ))
sic = ((4/3)*np.pi*(0.03825**3) - (4/3)*np.pi*(0.03475**3))*18000

graph = ((4/3)*np.pi*(3.0**3)) - fuel - pyroc - sic

tot = (4/3)*np.pi*(3.0**3)
```

```python
print(fuel/tot)
print(pyroc/tot)
print(sic/tot)
print(graph/tot)
```

    0.006397135416666668
    0.021474480715333345
    0.009332895833333325
    0.9627954880346667

```python
(fuel+pyroc+sic+graph)/tot
```

    1.0

$$Bg^2 = \frac{( \upsilon\Sigma_f -\Sigma_a )}{D}$$
$$Power Density = \frac{kW}{g_{fuel}} $$
$$ 0.11 {kW/g} = \frac{20*1000}{m_{fuel}}$$
$$m_{fuel} = 181818.18 g$$
$$m_{fuel}/\rho = 181818.18/11.0 = 16528.92 cc$$
$$ V_{p} = 18,000 * 4/3 * \pi * 0.02125^{3} = 0.7235$$
$$ 16528.92 cc / 0.7235 = # pebb = 22846 $$
$$ \theta = \frac{tot pebb vol}{inner core vol} = 0.56 (natural pacfrac)$$
$$ inner core vol = 4613967.37 = v = H * \pi * R^{2} $$
$$ H = \frac{V}{\pi*R^{2}} $$
$$ Bg^{2} = (\frac{\pi}{H})^{2} + (\frac{2.405}{R})^{2} = \frac{( \nu\Sigma_f -\Sigma_a )}{D} $$
$$(\frac{\pi^{2}R^{2}}{v})^{2} + (\frac{2.405}{R})^{2} = \frac{( \nu\Sigma_f -\Sigma_a )}{D} = 9.98^{-5}$$
$$(\frac{\pi^{4}}{v^{2}})* R^{4} + (\frac{2.405}{R})^{2} = \frac{( \nu\Sigma_f -\Sigma_a )}{D} = 9.98^{-5}$$

((clean up variable names))

```python
tvp = 22846*4/3*np.pi*3.0**3
print(tvp/0.56)
print(tvp/0.58)
```

    4613967.370537226
    4454865.047415253

```python
B2 = 1.10568E-04
R = np.linspace(1,200)
H = np.pi/(np.sqrt(np.abs( B2 - (2.405/R)**2 )))
plt.plot(R,H)
print(np.pi/(np.sqrt(np.abs( B2 - (2.405/90)**2 ))))
```

```python
R = np.linspace(50,200)
H = 4613967.37/(np.pi*R**2)
print(4454865.047415253/(np.pi*89**2))
plt.plot(R,H)
```

$$ Bg^{2} = (\frac{\pi}{H})^{2} + (\frac{2.405}{R})^{2} = \frac{( \nu\Sigma_f -\Sigma_a )}{D} $$
$$(\frac{\pi}{H})^{2} + (\frac{2.405}{R})^{2} = \frac{( \nu\Sigma_f -\Sigma_a )}{D} = 9.98^{-5}$$
$$(\frac{\pi}{H})^{2} + (\frac{2.405}{R})^{2} = 9.98^{-5}$$

In order to determine the outward surface current, the result for the current given by Serpent's surface detector must be divided by the surface area of the detector, to get units of $\frac{n}{cm^2s}$

```python
r20det = 164 #detector radius, cm
h20det = 2*164 #detector height, cm

sa20det = (2*r20det*np.pi*h20det)+ 2*(np.pi*r20det**2) #detector surface area, cm

print(sa20det)
```

    506977.6560657064

The formula for the outward current at the reflector surface is then
$$ J^+[\frac{n}{cm^2s}] = \frac{J^+[\frac{n}{s}]}{S_{det}[cm^2]} $$
The error becomes:
$$ \delta J^+[\frac{n}{cm^2s}] = |\frac{1}{S_{det}}|\delta J^+[\frac{n}{s}] $$

For the values below, the model run was:

- 20 MWth core
- 65 cm reflector thickness on sides and top, for a current surface detector radius of 164 cm, and a height of 328 cm
- 50,000 neutrons, 100/50 active/inactive cycles
- A $\frac{1}{6}$ slice of the core is used with a periodic boundary condition

```python
J_0_60 = 3.002 * 10**17
delJ_0_60 = 0.00147
J_0_60c = J_0_60/sa20det
delJ_0_60c = delJ_0_60/sa20det
print("0-60: " + "{:.3e}".format(J_0_60c) + " +/- " + "{:.3e}".format(delJ_0_60c))
print()

J_60_120 = 2.983 * 10**17
delJ_60_120 = 0.00141
J_60_120c = J_60_120/sa20det
delJ_60_120c = delJ_60_120/sa20det
print("60-120: " + "{:.3e}".format(J_60_120c) + " +/- " + "{:.3e}".format(delJ_60_120c))
print()

J_120_180 = 2.995 * 10**17
delJ_120_180 = 0.00126
J_120_180c = J_120_180/sa20det
delJ_120_180c = delJ_120_180/sa20det
print("120-180: " + "{:.3e}".format(J_120_180c) + " +/- " + "{:.3e}".format(delJ_120_180c))
print()

J_180_240 = 2.996 * 10**17
delJ_180_240 = 0.00147
J_180_240c = J_180_240/sa20det
delJ_180_240c = delJ_180_240/sa20det
print("180-240: " + "{:.3e}".format(J_180_240c) + " +/- " + "{:.3e}".format(delJ_180_240c))
print()

J_240_300 = 2.983 * 10**17
delJ_240_300 = 0.00151
J_240_300c = J_240_300/sa20det
delJ_240_300c = delJ_240_300/sa20det
print("240-300: " + "{:.3e}".format(J_240_300c) + " +/- " + "{:.3e}".format(delJ_240_300c))
print()

J_300_360 = 2.985 * 10**17
delJ_300_360 = 0.00144
J_300_360c = J_300_360/sa20det
delJ_300_360c = delJ_300_360/sa20det
print("300-360: " + "{:.3e}".format(J_300_360c) + " +/- " + "{:.3e}".format(delJ_300_360c))
print()

```

    0-60: 5.921e+11 +/- 2.900e-09

    60-120: 5.884e+11 +/- 2.781e-09

    120-180: 5.908e+11 +/- 2.485e-09

    180-240: 5.910e+11 +/- 2.900e-09

    240-300: 5.884e+11 +/- 2.978e-09

    300-360: 5.888e+11 +/- 2.840e-09

A control run was also made, with all parameters the same as the model the slices came from, but I full core model instead.  The resulting outer $J^+$ is:

```python
J_20 = 2.983 * 10**17
delJ_20 = 0.00135
J_20c = J_20/sa20det
delJ_20c = delJ_20/sa20det
print("Full 20 MWth Core: " + "{:.3e}".format(J_20c) + " +/- " + "{:.3e}".format(delJ_20c))
print()
```

    Full 20 MWth Core: 5.884e+11 +/- 2.663e-09

```python
diff20_0_60 = (J_0_60c - J_20c)/(J_20c)*100 # in %
print("{:.3f}".format(abs(diff20_0_60)) + "% from control")

diff20_60_120 = (J_60_120c - J_20c)/(J_20c)*100 # in %
print("{:.3f}".format(abs(diff20_60_120)) + "% from control")

diff20_120_180 = (J_120_180c - J_20c)/(J_20c)*100 # in %
print("{:.3f}".format(abs(diff20_120_180)) + "% from control")

diff20_180_240 = (J_180_240c - J_20c)/(J_20c)*100 # in %
print("{:.3f}".format(abs(diff20_180_240)) + "% from control")

diff20_240_300 = (J_240_300c - J_20c)/(J_20c)*100 # in %
print("{:.3f}".format(abs(diff20_240_300)) + "% from control")

diff20_300_360 = (J_300_360c - J_20c)/(J_20c)*100 # in %
print("{:.3f}".format(abs(diff20_300_360)) + "% from control")
```

    0.637% from control
    0.000% from control
    0.402% from control
    0.436% from control
    0.000% from control
    0.067% from control

The same exercise was performed as above for testing the sensitivity of the model to the pebble burnup locations (the model still uses a 1/6 symmetry).  The locations of the pebbles stayed the same, but had different burnups assigned to them, as follows:

Original (Run 1):

Positions "0":  Burnup 0

Positions "1":  Burnup 1

Positions "2":  Burnup 2

Positions "3":  Burnup 3

Positions "4":  Burnup 4

Positions "5":  Burnup 5

Positions "6":  Burnup 6

This model is identical to the 0-60 slice run described above.  To change the level of the burnup locations, subsequent runs had the pebble compositions re-assigned as follows:

Run 2:

Positions "0":  Burnup 6

Positions "1":  Burnup 0

Positions "2":  Burnup 1

Positions "3":  Burnup 2

Positions "4":  Burnup 3

Positions "5":  Burnup 4

Positions "6":  Burnup 5

Run 3:

Positions "0":  Burnup 5

Positions "1":  Burnup 6

Positions "2":  Burnup 0

Positions "3":  Burnup 1

Positions "4":  Burnup 2

Positions "5":  Burnup 3

Positions "6":  Burnup 4

Run 4:

Positions "0":  Burnup 4

Positions "1":  Burnup 5

Positions "2":  Burnup 6

Positions "3":  Burnup 0

Positions "4":  Burnup 1

Positions "5":  Burnup 2

Positions "6":  Burnup 3

Run 5:

Positions "0":  Burnup 3

Positions "1":  Burnup 4

Positions "2":  Burnup 5

Positions "3":  Burnup 6

Positions "4":  Burnup 0

Positions "5":  Burnup 1

Positions "6":  Burnup 2

Run 6:

Positions "0":  Burnup 2

Positions "1":  Burnup 3

Positions "2":  Burnup 4

Positions "3":  Burnup 5

Positions "4":  Burnup 6

Positions "5":  Burnup 0

Positions "6":  Burnup 1

Run 7:

Positions "0":  Burnup 1

Positions "1":  Burnup 2

Positions "2":  Burnup 3

Positions "3":  Burnup 4

Positions "4":  Burnup 5

Positions "5":  Burnup 6

Positions "6":  Burnup 0

The detector size didn't change from the previous runs, so the conversion to $[\frac{n}{cm^2s}]$ is simple.

```python
J_0_60 = 3.002 * 10**17
delJ_0_60 = 0.00147
J_0_60c = J_0_60/sa20det
delJ_0_60c = delJ_0_60/sa20det
print("Run 1: " + "{:.3e}".format(J_0_60c) + " +/- " + "{:.3e}".format(delJ_0_60c))
print()

J_r2 = 2.995 * 10**17
delJ_r2 = 0.00155
J_r2c = J_r2/sa20det
delJ_r2c = delJ_r2/sa20det
print("Run 2: " + "{:.3e}".format(J_r2c) + " +/- " + "{:.3e}".format(delJ_r2c))
print()

J_r3 = 2.985 * 10**17
delJ_r3 = 0.00131
J_r3c = J_r3/sa20det
delJ_r3c = delJ_r3/sa20det
print("Run 3: " + "{:.3e}".format(J_r3c) + " +/- " + "{:.3e}".format(delJ_r3c))
print()

J_r4 = 2.994 * 10**17
delJ_r4 = 0.00144
J_r4c = J_r4/sa20det
delJ_r4c = delJ_r4/sa20det
print("Run 4: " + "{:.3e}".format(J_r4c) + " +/- " + "{:.3e}".format(delJ_r4c))
print()

J_r5 = 3.006 * 10**17
delJ_r5 = 0.00139
J_r5c = J_r5/sa20det
delJ_r5c = delJ_r5/sa20det
print("Run 5: " + "{:.3e}".format(J_r5c) + " +/- " + "{:.3e}".format(delJ_r5c))
print()

J_r6 = 3.011 * 10**17
delJ_r6 = 0.00132
J_r6c = J_r6/sa20det
delJ_r6c = delJ_r6/sa20det
print("Run 6: " + "{:.3e}".format(J_r6c) + " +/- " + "{:.3e}".format(delJ_r6c))
print()

J_r7 = 3.016 * 10**17
delJ_r7 = 0.00133
J_r7c = J_r7/sa20det
delJ_r7c = delJ_r7/sa20det
print("Run 7: " + "{:.3e}".format(J_r7c) + " +/- " + "{:.3e}".format(delJ_r7c))
print()
```

    Run 1: 5.921e+11 +/- 2.900e-09

    Run 2: 5.908e+11 +/- 3.057e-09

    Run 3: 5.888e+11 +/- 2.584e-09

    Run 4: 5.906e+11 +/- 2.840e-09

    Run 5: 5.929e+11 +/- 2.742e-09

    Run 6: 5.939e+11 +/- 2.604e-09

    Run 7: 5.949e+11 +/- 2.623e-09

These results can be compared to the control, as before, using the relative difference...

```python
diff20_0_60 = (J_0_60c - J_20c)/(J_20c)*100 # in %
print("{:.3f}".format(abs(diff20_0_60)) + "% from control")

diff20_r2 = (J_r2c - J_20c)/(J_20c)*100 # in %
print("{:.3f}".format(abs(diff20_r2)) + "% from control")

diff20_r3 = (J_r3c - J_20c)/(J_20c)*100 # in %
print("{:.3f}".format(abs(diff20_r3)) + "% from control")

diff20_r4 = (J_r4c - J_20c)/(J_20c)*100 # in %
print("{:.3f}".format(abs(diff20_r4)) + "% from control")

diff20_r5 = (J_r5c - J_20c)/(J_20c)*100 # in %
print("{:.3f}".format(abs(diff20_r5)) + "% from control")

diff20_r6 = (J_r6c - J_20c)/(J_20c)*100 # in %
print("{:.3f}".format(abs(diff20_r6)) + "% from control")

diff20_r7 = (J_r7c - J_20c)/(J_20c)*100 # in %
print("{:.3f}".format(abs(diff20_r7)) + "% from control")
```

    0.637% from control
    0.402% from control
    0.067% from control
    0.369% from control
    0.771% from control
    0.939% from control
    1.106% from control

The reflector thickness is chosen such that the outward surface current is less than or equal to the outward current of the 200MWth design.  The outward current for this design can be, as above, found by dividing the surface current given in the Serpent output, in $[\frac{n}{s}]$, by the surface area of the detector.  Thus, the "target" current is:

```python
r200det = 215 #detector radius, cm
h200det = 2*574 #detector height, cm

sa200det = (2*r200det*np.pi*h200det)+ 2*(np.pi*r200det**2) #detector surface area, cm

J_200 = 1.35352 * 10**18
delJ_200 = 0.00130
J_200c = J_200/sa200det
delJ_200c = delJ_200/sa200det
print("200MWth full core: " + "{:.3e}".format(J_200c) + " +/- " + "{:.3e}".format(delJ_200c))
```

    200MWth full core: 7.351e+11 +/- 7.060e-10

The relative difference is simply
$$ \frac{J^+_{200} - J^+_{20}}{J^+_{200}} $$

```python
diff_0_60 = (J_0_60c - J_200c)/(J_200c)*100 # in %
if diff_0_60 < 0:
    print("{:.3f}".format(abs(diff_0_60)) + "% decrease")
else:
    print("{:.3f}".format(abs(diff_0_60)) + "% increase")

diff_60_120 = (J_60_120c - J_200c)/(J_200c)*100 # in %
if diff_60_120 < 0:
    print("{:.3f}".format(abs(diff_60_120)) + "% decrease")
else:
    print("{:.3f}".format(abs(diff_60_120)) + "% increase")

diff_120_180 = (J_120_180c - J_200c)/(J_200c)*100 # in %
if diff_120_180 < 0:
    print("{:.3f}".format(abs(diff_120_180)) + "% decrease")
else:
    print("{:.3f}".format(abs(diff_120_180)) + "% increase")

diff_180_240 = (J_180_240c - J_200c)/(J_200c)*100 # in %
if diff_180_240 < 0:
    print("{:.3f}".format(abs(diff_180_240)) + "% decrease")
else:
    print("{:.3f}".format(abs(diff_180_240)) + "% increase")

diff_240_300 = (J_240_300c - J_200c)/(J_200c)*100 # in %
if diff_240_300 < 0:
    print("{:.3f}".format(abs(diff_240_300)) + "% decrease")
else:
    print("{:.3f}".format(abs(diff_240_300)) + "% increase")

diff_300_360 = (J_300_360c - J_200c)/(J_200c)*100 # in %
if diff_300_360 < 0:
    print("{:.3f}".format(abs(diff_300_360)) + "% decrease")
else:
    print("{:.3f}".format(abs(diff_300_360)) + "% increase")
```

    19.449% decrease
    19.959% decrease
    19.637% decrease
    19.610% decrease
    19.959% decrease
    19.905% decrease

The same can be calculated for the burnup-shuffling test:

```python
diff_0_60 = (J_0_60c - J_200c)/(J_200c)*100 # in %
if diff_0_60 < 0:
    print("{:.3f}".format(abs(diff_0_60)) + "% decrease")
else:
    print("{:.3f}".format(abs(diff_0_60)) + "% increase")

diff_r2 = (J_r2c - J_200c)/(J_200c)*100 # in %
if diff_r2 < 0:
    print("{:.3f}".format(abs(diff_r2)) + "% decrease")
else:
    print("{:.3f}".format(abs(diff_r2)) + "% increase")

diff_r3 = (J_r3c - J_200c)/(J_200c)*100 # in %
if diff_r3 < 0:
    print("{:.3f}".format(abs(diff_r3)) + "% decrease")
else:
    print("{:.3f}".format(abs(diff_r3)) + "% increase")

diff_r4 = (J_r4c - J_200c)/(J_200c)*100 # in %
if diff_r4 < 0:
    print("{:.3f}".format(abs(diff_r4)) + "% decrease")
else:
    print("{:.3f}".format(abs(diff_r4)) + "% increase")

diff_r5 = (J_r5c - J_200c)/(J_200c)*100 # in %
if diff_r5 < 0:
    print("{:.3f}".format(abs(diff_r5)) + "% decrease")
else:
    print("{:.3f}".format(abs(diff_r5)) + "% increase")

diff_r6 = (J_r6c - J_200c)/(J_200c)*100 # in %
if diff_r6< 0:
    print("{:.3f}".format(abs(diff_r6)) + "% decrease")
else:
    print("{:.3f}".format(abs(diff_r6)) + "% increase")

diff_r7 = (J_r7c - J_200c)/(J_200c)*100 # in %
if diff_r7 < 0:
    print("{:.3f}".format(abs(diff_r7)) + "% decrease")
else:
    print("{:.3f}".format(abs(diff_r7)) + "% increase")
```

    19.449% decrease
    19.637% decrease
    19.905% decrease
    19.664% decrease
    19.342% decrease
    19.207% decrease
    19.073% decrease

How much damage is ~6.0e11 $\frac{n}{cm^2s}$?  Using the following example from Light Water Reactor Materials, page 555 by Olander to make an estimate,

With 6.0e11 $\frac{n}{cm^2s}$ as the incoming neutron current and 20 years as the lifetime, we have

```python
etp = 0.069
sigS = 3*10**(-24)
Ed = 4

flux = 6*10**11

dpas = flux*(sigS/(Ed*25))*etp*10**6

print("{:.2e}".format(dpas) + " dpa per second")
```

    1.24e-09 dpa per second

```python
time = 60*60*24*365*20

dpa = time*dpas

print("{:.2e}".format(dpa) + " total dpa in lifetime")
```

    7.83e-01 total dpa in lifetime

```python
fluence = flux*time
print("{:.2e}".format(fluence) + " total fluence in lifetime")
```

    3.78e+20 total fluence in lifetime

```python
rdet = [149,150,151, 152, 153, 154, 155, 156, 157, 158, 159, 160,
        161, 162, 163, 164, 165, 166, 167, 168, 169,  174] #detector radius, cm, h = 2r

jout = [3.81909E+17,3.69988E+17,3.69706E+17,3.63749E+17,3.56906E+17,3.51244E+17,
        3.44891E+17,3.40773E+17,3.34593E+17,3.30305E+17,3.24404E+17,3.19477E+17,
        3.14463E+17,3.09080E+17,3.04416E+17,2.99788E+17,2.94288E+17,2.91140E+17,
        2.85709E+17,2.82150E+17,2.77906E+17,2.57451E+17]

delj = [.00150,0.00145,0.00135,0.00130,0.00136,0.00124,0.00129,0.00133,
        0.00135,0.00152,0.00125,0.00143,0.00152,0.00140,0.00138,0.00139,
        0.00136,0.00156,0.00128,0.00132,0.00152,0.00143]

imp_keff = [1.01545,1.01916,1.01962,1.02139,1.02344,1.02535,1.02772,
            1.02815,1.03027,1.03107,1.03313,1.03501,1.03587,1.03761,
            1.03922,1.03988,1.04224,1.04183,1.04401,1.04432,1.04548,1.04976]

delimp_keff = [0.00038,0.00039,0.00039,0.00037,0.00037,0.00036,0.00034,
             0.00036,0.00039,0.00037,0.00034,0.00038,0.00040,0.00037,
             0.00035,0.00034,0.00037,0.00038,0.00032,0.00036,0.00035,0.00035]

joutc = []
deljc = []

for i, r in enumerate(rdet):
    sadet = (2*r*np.pi*2*r)+ 2*(np.pi*r**2)
    joutc.append(jout[i]/(sadet))
    deljc.append(delj[i]/sadet)

for i, v in enumerate(joutc):
    print("At Reflector thickness " + "{:d}".format(rdet[i]-89) + " cm, Jout is " +
          "{:.3e}".format(v) + " +/- " + "{:.3e}".format(deljc[i])  )
    print()
```

    At Reflector thickness 60 cm, Jout is 9.126e+11 +/- 3.584e-09

    At Reflector thickness 61 cm, Jout is 8.724e+11 +/- 3.419e-09

    At Reflector thickness 62 cm, Jout is 8.602e+11 +/- 3.141e-09

    At Reflector thickness 63 cm, Jout is 8.352e+11 +/- 2.985e-09

    At Reflector thickness 64 cm, Jout is 8.089e+11 +/- 3.082e-09

    At Reflector thickness 65 cm, Jout is 7.857e+11 +/- 2.774e-09

    At Reflector thickness 66 cm, Jout is 7.616e+11 +/- 2.849e-09

    At Reflector thickness 67 cm, Jout is 7.429e+11 +/- 2.899e-09

    At Reflector thickness 68 cm, Jout is 7.201e+11 +/- 2.906e-09

    At Reflector thickness 69 cm, Jout is 7.019e+11 +/- 3.230e-09

    At Reflector thickness 70 cm, Jout is 6.808e+11 +/- 2.623e-09

    At Reflector thickness 71 cm, Jout is 6.621e+11 +/- 2.963e-09

    At Reflector thickness 72 cm, Jout is 6.436e+11 +/- 3.111e-09

    At Reflector thickness 73 cm, Jout is 6.248e+11 +/- 2.830e-09

    At Reflector thickness 74 cm, Jout is 6.078e+11 +/- 2.756e-09

    At Reflector thickness 75 cm, Jout is 5.913e+11 +/- 2.742e-09

    At Reflector thickness 76 cm, Jout is 5.735e+11 +/- 2.650e-09

    At Reflector thickness 77 cm, Jout is 5.605e+11 +/- 3.003e-09

    At Reflector thickness 78 cm, Jout is 5.435e+11 +/- 2.435e-09

    At Reflector thickness 79 cm, Jout is 5.303e+11 +/- 2.481e-09

    At Reflector thickness 80 cm, Jout is 5.162e+11 +/- 2.823e-09

    At Reflector thickness 85 cm, Jout is 4.511e+11 +/- 2.506e-09

```python
plt.plot(rdet,joutc)
plt.plot(rdet, J_200c*np.ones(len(rdet)))
plt.title("Jout(det_radius) vs Detector Radius")
plt.xlabel("Detector Radius [cm]")
plt.ylabel("Outward Current [n/cm^2-s]")
plt.legend(["Outward Current: 20 MWth","Outward Current at Outer Bound, 200 MWth"])
```

```python
plt.plot(rdet,imp_keff)
plt.plot(rdet,np.ones(len(rdet)))
plt.title("Implicit K Effective vs Reflector Thickness")
plt.xlabel("Reflector Thickness [cm]")
plt.ylabel("K")
```

```python

```