# Using Jupyter Notebooks in Math Classes

---

### Jan Reimann, University Park

### April 17, 2021

## What is a Jupyter notebook?

Jupyter notebooks are interactive notebooks that combine

- content input/output via **Markdown**

- interactive computations using a REPL kernel (IPython, Sage, Julia, R, ...)

**REPL:** **R**ead–**E**val–**P**rint **L**oop

```python
6*7
```

    42

- A notebook is a **dynamic document** consisting of **cells**.

- A cell can be either **Markdown** (for text, images, etc) or **code**.

- A user can **add/delete cells** at any time.

## What can I do with a Jupyter notebook?

- Easily input and display math/latex:

Let $H_n = \sum\limits_{j=1}^n \frac{1}{j}$ be the $n$-th
harmonic number.

__Claim:__
For each $n \ge 1$, it holds that
$$\sum\limits_{d|n} d \le H_n + \exp (H_n) \log (H_n),$$
with equality only for $n = 1.$

- Include links:

> Some helpful resources for Jupyter:
>
>    - [The Jupyter Project](https://jupyter.org)
>
>    - [Teaching and Learning with Jupyter](https://jupyter4edu.github.io/jupyter-edu-book/)
>
>    - [Markdown Guide](https://www.markdownguide.org)
>
>    - [IPython documentation](https://ipython.readthedocs.io/en/stable/)

- Include graphics:

- Embed videos:

```python
from IPython.display import IFrame
IFrame("https://www.youtube.com/embed/fs4ImYH0GRg?rel=0&controls=0&showinfo=0", '80%', '600px')
```

- Run code (Python, Sage, Julia, R, many more...)

```python
from sympy import diff, sympify, Symbol
x = Symbol('x')

f = sympify('x^3+2/x')

diff(f)
```

$\displaystyle 3 x^{2} - \frac{2}{x^{2}}$

- Run code and generate interactive elements:

```python
from ipywidgets import interact, fixed

def diff_quot(a,h):
    if h != 0: return ((float(f.subs(x, a+h))-float(f.subs(x,a)))/h)

interact(diff_quot, a=fixed(1), h= (0.01,1));
```

    interactive(children=(FloatSlider(value=0.505, description='h', max=1.0, min=0.01), Output()), _dom_classes=('…

- **Prepare slides!**

    [Creating Presentations With Jupyter Notebook](https://rise.readthedocs.io/en/stable/index.html)

- Prepare handouts (pdf, ...)
- Write books
- ...

## Possibilities / Advantages

- Open source

- Authoring and delivery in the **same** platform

- Notebook format text-based, easily portable (`Pandoc`, `nbconvert`)

- Interactivity

- Engagement: students can add cells:
    - perform additional computations
    - add content

    _It is "their" document._

## Possibilities / Advantages

- Ubiquitous platform, becoming de-facto standard in the data science world.

- Plenty of tools to pull and analyze data sets in notebooks (e.g. Pandas)

- Better data-oriented activities

## Possibilities / Advantages

- Computational power of Python, Julia, R, ...

- Powerful libraries can be utilized:

    - Symbolic computation / CAS: `Sympy`
    - Numerical computation: `Numpy`
    - Graphing: `matplotlib`
    - Machine learning: `tensorflow`, `pytorch`, `scikit-learn`
    - Data science: `pandas`, `seaborn`

Sample activity 1

---

## Challenges

- Coding competency

- User interface

- Accessibility

- Authoring problems for non-programmers

- Deployment

Coding competency

User interface

Accessibility

## Advantages of a "pure" notebook approach

- Better accessibility

- Computational mindset

- Platform independence: works in Jupyter, VSCode, Google Colaboratory, Nteract, ...

- Works also **great on mobile**

## Authoring problems

- **`ProblemTool`**: "Markdown" for math problems

        Type : Free Response
            Function f(x) = x^2+x
            Function g(x) = f.derv(x)
            statement = "Assume that the function
                $f(x) = $ {f(x)}. Find $f'(x)$."
            solution:  g(x)

## Authoring problems

- **`ProblemTool`**: "Markdown" for math problems

        Type : Multiple Choice
            Function two(x) = sin(3*x^2)
            Function g(x) = x^3-x^2
            Function h(x) = g.derv(x)
            statement = "Assume that the function
                $f(x) = $ {g(x)}.
                Find $f'(x)$ and the graph of
                $g(x) =$ {two(x)}."
            solution :
            (
            # * "First Solution $f'(x)=$ {h(x)}"
            # "Second Solution $f'(x)=1$"
            # "Third Solution  $f'(x)=$ {two(x)}"
            # "Fourth Solution $f'(x)=$ {g(x)}"
            )

## Authoring problems

- `Antlr`-based parser translates problem definitions into Python classes that can be pulled directly into Jupyter notebooks, using our Python library `Cyllene`.

- Once this is complete, we plan to implement export for other formats:

    - WebAssign (Perl)
    - QTI (Canvas quizzes)

## Deployment

- Local install

- Docker

- Cloud

    - All major cloud providers offer some form of Jupyter notebook service.

    - Together with TLT, we are running a `Binder` server for the PSU community

    - `Binder` allows for very easy deployment of notebooks: Paste a link into Canvas.

Questions? Interested?

- Contact me at <jan.reimann@psu.edu>

- I can give a more thorough walk-through and add you to our Binder server.

- Pilot in Math 110 WC, Fall 2021


# Associated Integral Equation
### https://www.youtube.com/watch?v=Xz-81xdFxN0
# Picard Integration Method

```python
import numpy as np
import sympy as sym
import sympy.utilities
import scipy as sci
import scipy.optimize

import matplotlib.pyplot as plt
import matplotlib.patches as pat

x,y,alpha, beta = sym.symbols('x y \alpha \beta')
F = lambda x: (x+2)**2 - 1

f = sym.diff(F(x), x)
fx = sym.lambdify(x,f)

print(F(-3))
print(F(1))
sn = sym.integrate(f,(x,-4,-3))

a0 = -4
b0 = -3
px = np.linspace(a0,b0)
py = sym.lambdify(x,f)(px)
pa = [(a0,0), *zip(px,py), (b0,0)]
po = pat.Polygon(pa)

fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot()
xi = np.linspace(-5,5,1000)
ax.plot(xi,F(xi))

#a = sci.optimize.fmin_bfgs(F,0)
#print (a)
#ax.anotate('(%s, %s)' %xy, xy)
ax.plot(xi,fx(xi))
ax.add_patch(po)
ax.text(0.2*a0+b0, -2, sn)
ax.plot(-4,F(-4),'o')
ax.text(-4,F(-4),"(-4, " + str(F(-4)) +")")

a2 = np.linspace(-2,1,100)
a3 = F(a2)
ax.fill_between(a2,a3)
ax.axhline(0,color='gray')
ax.axvline(0,color='gray')

```

```python
import numpy as np
import sympy as sym
import scipy as sci

import matplotlib.pyplot as plt
import matplotlib.patches as patches
import warnings
warnings.filterwarnings("ignore")

x,y = sym.symbols('x y')
an = sym.solve(sym.ln(x),x)
ans = sym.solveset(sym.ln(x),x)
it = iter(ans)
print(next(it))

xi = np.linspace(0.01,10,1000)
yd = [sym.ln(x) for x in xi]

fig = plt.figure()
ax = fig.add_subplot()
ax.set_xlabel('$x$')
ax.set_ylabel("$f(x)$")
ax.set_xlim(xmin=-5, xmax=5)
ax.set_ylim(ymin=-5, ymax=5)
ax.axvline(0)
ax.axhline(0)
ax.axvline(an)
ax.grid()
ax.plot(xi,yd, color='orange')
```

```python
import sympy as sym
x,y,theta = sym.symbols('x y theta')
sym.integrate(x**2+2*x*y,x)
sym.integrate(x**2+2*x*y,y)
#sym.integrate(x**2+2*x*y,(x,0,1),(y,0,1))
sym.limit(sym.sin(x)/x,x,0)
sym.series(sym.exp(sym.I*theta),theta)
```

$\displaystyle 1 + i \theta - \frac{\theta^{2}}{2} - \frac{i \theta^{3}}{6} + \frac{\theta^{4}}{24} + \frac{i \theta^{5}}{120} + O\left(\theta^{6}\right)$

```python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon

def func(x):
    return (x - 3) * (x - 5) * (x - 7) + 85

a, b = 2, 9  # integral limits
x = np.linspace(0, 10)
y = func(x)

fig, ax = plt.subplots()
ax.plot(x, y, 'r', linewidth=2)
ax.set_ylim(bottom=0)

# Make the shaded region
ix = np.linspace(a, b)
iy = func(ix)
verts = [(a, 0), *zip(ix, iy), (b, 0)]
print("verts: ", verts)
poly = Polygon(verts, facecolor='0.9', edgecolor='0.5')
ax.add_patch(poly)

ax.text(0.5 * (a + b), 30, r"$\int_a^b f(x)\mathrm{d}x$",
        horizontalalignment='center', fontsize=20)

fig.text(0.9, 0.05, '$x$')
fig.text(0.1, 0.9, '$y$')

ax.spines.right.set_visible(False)
ax.spines.top.set_visible(False)
ax.xaxis.set_ticks_position('bottom')

ax.set_xticks((a, b))
ax.set_xticklabels(('$a$', '$b$'))
ax.set_yticks([])

plt.show()
```

```python

```


| |Pierre Proulx, ing, professeur|
|:---|:---|
|Département de génie chimique et de génie biotechnologique |** GCH200-Phénomènes d'échanges I **|

### Section 18-3, diffusion binaire avec réaction hétérogène sur la surface

>> Ce développement est très similaire à la section précédente, mais avec deux différences importantes:
>>> * La concentration en $x=\delta$ est nulle car il y a une réaction catalytique rapite
* La substance A ne diffuse pas dans un milieu stagnant $N_B \ne 0$

>> On se base donc sur le développement fait en 18.2, mais seulement en partie

```python
#
# Pierre Proulx
#
#
# Préparation de l'affichage et des outils de calcul symbolique
#
import sympy as sp
from IPython.display import *
sp.init_printing(use_latex=True)
%matplotlib inline
```

```python
# Paramètres, variables et fonctions

z,D_AB,C,S,delta_z,C1,C2,X0,delta=sp.symbols('z,D_AB,C,S,delta_z,C1,C2,X_0,delta')
N_A=sp.symbols('N_A')
N_B=sp.symbols('N_B')
X_A=sp.Function('X_A')(z)
X_B=sp.Function('X_B')(z)
```

```python
#
# Bilan sur le film
#
eq=(S*N_A(z)-S*N_A(z+delta_z))/(S*delta_z)
display(eq)
```

```python
eq=sp.limit(eq,delta_z,0).doit()
display(eq)
```

```python
fick=C*D_AB/(1-1/2*X_A)*sp.diff(X_A,z)   # équation 18.0-1 avec NB=-1/2NA
eq=eq.subs(N_A(z),fick)
display(eq)
```

```python
X_A=sp.dsolve(eq,X_A)
display(X_A)
```

```python
X_A=X_A.rhs
#
# Pose et solution des 2 équations de conditions aux limites pour C1 et C2
condition_1=sp.Eq(X_A.subs(z,0)-X0,0)
condition_2=sp.Eq(X_A.subs(z,delta),0)
constantes=sp.solve([condition_1,condition_2],sp.symbols('C1,C2'),dict=True) #  C1 et C2
constantes=constantes[0]          # voir section 18.2
display(constantes)
```

```python
X_A=X_A.subs(constantes)             # qui avait été crée par solve. ?????
display(X_A.simplify())
```

```python
#
# Tracons
#
dico={'delta':0.2,'X_0':1}
X_Aplot=X_A.subs(dico)
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize']=10,8
sp.plot(X_Aplot,(z,0.0,delta.subs(dico)),ylabel='X_A',title=X_Aplot);
```

```python
# L'expression de X_A est un peu différente de celle de Transport Phenomena, problème?
#
# Vérifions si notre expression ci-haut est équivalente à 18.3-8, en x/delta=0.5
#
if (1-1/2*X0.subs(dico))**(1-0.5)==(1-1/2*X_Aplot.subs(z,0.5*delta.subs(dico))):
    print(' Ok')
else:
    Print(' Oups')
```

     Ok


+++
title = "Predictive asymmetry"
subtitle = ""
date = 2019-04-15T00:00:00
lastmod = 2019-04-15T00:00:00
draft = false

# Authors. Comma separated list, e.g. `["Bob Smith", "David Jones"]`.
authors = ["Kristian A. Haaga", "David Diego", "Bjarte Hannisdal"]

tags = []
summary = "A simple and robust test for causal directionality in complex systems."

# Projects (optional).
#   Associate this post with one or more of your projects.
#   Simply enter your project's folder or file name without extension.
#   E.g. `projects = ["deep-learning"]` references
#   `content/project/deep-learning/index.md`.
#   Otherwise, set `projects = []`.
# projects = ["asymmetrytest"]
+++

## A brief tutorial on the predictive asymmetry test

Here's an example of how to compute the remarkable predictive asymmetry test of causality from time series proposed by Haaga *et al.* (forthcoming). The ``jupyter`` notebook was run using Julia 1.2 with our package ``CausalityTools.jl``.

Let's start by importing the necessary functions.

```julia
using CausalityTools, UncertainData
using StatsBase # statistical functions, e.g. mean
using Distributions # probability distributions
using Plots; pyplot() # use pyplot backend
using LaTeXStrings # enable TeX i pyplot
```

## Some example data

Let's create some example data with three dynamical variables coupled $x \to y \to z$.

```julia
npts = 400
y = rand(npts, 3)

N = Normal(0, 1)

a₁, a₂ = 0.7, 0.1
b₁, b₂ = 0.75, 0.2
c₁, c₂ = 0.6, 0.2
C₁₂, C₂₃ = 0.5, 0.5
ξ₁, ξ₂, ξ₃ = 0.3, 0.3, 0.3

for t in 5:npts
    y[t,1] = a₁*y[t-1,1] - a₂*y[t-4,1] +                ξ₁*rand(N)
    y[t,2] = b₁*y[t-1,2] - b₂*y[t-4,2] + C₁₂*y[t-1,1] + ξ₂*rand(N)
    y[t,3] = c₁*y[t-1,3] - c₂*y[t-3,3] + C₂₃*y[t-1,2] + ξ₃*rand(N)
end

x, y, z = [y[:, i] for i = 1:3]
labs = "x", "y", "z"

plot(1:npts, x, c = :black, label = "x", lw = .5,
    xlabel = "time", ylabel = "", size = (700, 400))
plot!(1:npts, y, c = :red, label = "y", lw = .5)
plot!(1:npts, z, c = :blue, label = "z", lw = .5)

```

## Predictive asymmetry test for causality

Now, we'll try to see if we can recover the unidirectional relationships using the predictive asymmetry test. To compute the predictive asymmetry, we'll use the transfer operator grid (Diego et al., 2019) transfer entropy test. To understand the parameters of the test, we need some background on transfer entropy:

### Transfer entropy

Let's say we have a source time series $S$ and a target time series $T$. For convenience, define

$$ \begin{align} T_f^{(k)} &= \{(T(t+\eta_k), \ldots, T(t+\eta_2), T(t+\eta_1))\}, \label{eq:Tf} \\ T_{pp}^{(l)} &= \{ (T(t), T(t-\tau_1), T(t-\tau_2), \ldots, T(t - \tau_{l - 1})) \\ S_{pp}^{(m)} &= \{(S(t), S(t-\tau_1), S(t-\tau_2), \ldots, S(t-\tau_{m - 1}))\} \end{align} $$

Combining into a generalised delay reconstruction:

$$ \begin{align} \mathbb{E} = (T_f^{(k)}, T_{pp}^{(l)}, S_{pp}^{(m)}) \end{align} $$

With that, we have an embedding that we can use to compute transfer entropy:

$$ \begin{align} TE_{S \rightarrow T} = \int_{\mathbb{E}} P(T_f, T_{pp}, S_{pp}) \log_{2}{\left(\frac{P(T_f | T_{pp}, S_{pp})}{P(T_f | T_{pp})}\right)} \end{align} $$.

### Deciding on test parameters

To organise our test, we can create an instance of a `TransferOperatorGridTest`, which has the following signature:

```julia
TransferOperatorGridTest(k::Int = 1, l::Int = 1, m::Int = 1, n::Int = 1,
    τ::Int = 1, b = 2, estimator::TransferOperatorGrid = TransferOperatorGrid(),
    binning_summary_statistic::Function = StatsBase.mean,
    binning::RectangularBinning, ηs)
```

To get the full documentation, type `?TransferOperatorGridTest`.

#### Parameter list

What's important for us are

- `k`, `l`, and `m`, which dictate the dimension of the delay reconstruction.
- The `binning`, which is a `RectangularBinning` instance.
- `ηs` is/are the prediction lag(s).
- `b` is the base of the logarithm.
- The `τ` parameter is the reconstruction delay, which only matters for `k + l + m > 3`.

With that in mind, let's set up a very simple test.

```julia
# Use a three-dimensional delay reconstruction
k, l, m = 1, 1, 1

# Use a rectangular partition where the number of bin elements scale
# with the number of points (following Krakovska et al., 2018).
n_subdivisions = floor(Int, npts^(1/(k+l+m+1)))
binning = RectangularBinning(n_subdivisions)

# Compute the predictive asymmetry for η ranging from -8 to 8.
ηs = -8:8

te_test = TransferOperatorGridTest(k = k, l = l, m = m, binning = binning, ηs = ηs)
```

    TransferOperatorGridTest(k = 1, l = 1, m = 1, n = 1, τ = 1, b = 2, estimator = TransferOperatorGrid(), binning_summary_statistic = mean, binning = RectangularBinning(4), ηs = -8:8)

Now, we can use this transfer entropy test as input to a `PredictiveAsymmetryTest` instance.

```julia
pa_test = PredictiveAsymmetryTest(predictive_test = te_test)
```

    PredictiveAsymmetryTest{TransferOperatorGridTest}(predictive_test = TransferOperatorGridTest(k = 1, l = 1, m = 1, n = 1, τ = 1, b = 2, estimator = TransferOperatorGrid(), binning_summary_statistic = mean, binning = RectangularBinning(4), ηs = -8:8))

## Applying the test

```julia
pa_xy = causality(x, y, pa_test)
pa_yx = causality(y, x, pa_test)
pa_yz = causality(y, z, pa_test)
pa_zy = causality(z, y, pa_test)
pa_xz = causality(x, z, pa_test)
pa_zx = causality(z, x, pa_test);
```

```julia
p_xy = plot(xlabel = "Prediction lag", ylabel = L"\mathbb{A} (bits)",
    ylims = [-0.8, 0.8])
plot!(1:maximum(ηs), pa_xy, label = L"x \to y", c = :black)
plot!(1:maximum(ηs), pa_yx, label = L"y \to x", c = :red)
hline!([0], lw = 2, ls = :dot, α = 0.5, c = :black, label = "")

p_yz = plot(xlabel = "Prediction lag", ylabel = L"\mathbb{A} (bits)",
    ylims = [-0.8, 0.8])
plot!(1:maximum(ηs), pa_yz, label = L"y \to z", c = :red)
plot!(1:maximum(ηs), pa_zy, label = L"z \to y", c = :blue)
hline!([0], lw = 2, ls = :dot, α = 0.5, c = :black, label = "")

p_xz = plot(xlabel = "Prediction lag", ylabel = L"\mathbb{A} (bits)",
    ylims = [-0.8, 0.8])
plot!(1:maximum(ηs), pa_xz, label = L"x \to z", c = :black)
plot!(1:maximum(ηs), pa_zx, label = L"z \to x", c = :blue)
hline!([0], lw = 2, ls = :dot, α = 0.5, c = :black, label = "")

plot(p_xy, p_yz, p_xz, layout = grid(3, 1), size = (600, 600), legend = :topleft)
```

The direct influences $\mathbb{A}_{x \to y}$ and $\mathbb{A}_{y \to z}$ are both positive and of equal strength, while the indirect influence $\mathbb{A}_{x \to z}$ is weaker. In the non-causal direction, $\mathbb{A}_{y \to x}$, $\mathbb{A}_{z \to y}$ and $\mathbb{A}_{z \to x}$ are all negative. We thus recover the underlying directionality and relative strength of couplings.

```julia

```


```python
import numpy as np
import sympy as sp
import matplotlib.pyplot as plt
import Euler as p1

```

# Classwork 6
### Michael Seaman, Taylor Patti, Austin Ayers, Chinmai Raman, Andrew Malfavon
#### 3/10/16

Euler's method is an algorithm that can be used to approximate an ODE that otherwise can't be solved.

Euler's method approximates function f based on f'. We can do this by approximating the f(x+dx) point with:

given: $$ f'(x) \leftrightharpoons a \in x $$
$$ f(a + dx) = f(a) + dx \cdot f'(a) $$

# Exercise C.1 Solving a nonhomogenous linear ODE
## solves u' = 2u - 1 using both Euler's method and symbolically, using sympy's dsolve.

```python
p1.run()
```

```python

```