# Precisione dei numeri `floats`
A seguito di un paio di domande fatte a lezione, vediamo la precisione dei numeri "*reali*" in Python.

I `float` in python corrispondono ai `double` in C e quindi sono numeri in doppio precisione, e occupano in memoria 64 bits. Questo comporta un errore di precisione che deve essere considerato in quei ambiti in cui è richiesta un'alta precisione numerica. Per esempio, si dovrebbe ricordare che:

```python
0.1+0.1+0.1-0.3
```

ovvero la differenza non è nulla. Questo va considerato soprattutto quando si effetuano dei confronti tra due numeri `float`:

```python
0.1 + 0.1 + 0.1 == 0.3
```

Se viene richiesta una precionse più alta si possono utilizzare due librerie diverse:

* [**decimal** — Decimal fixed point and floating point arithmetic](https://docs.python.org/3/library/decimal.html)
* [**fractions** — Rational numbers](https://docs.python.org/3/library/fractions.html)

Vediamo un esempio per la seconda libreria.

```python
from fractions import Fraction
a = Fraction(1,10)
b = Fraction(1,10)
c = Fraction(1,10)
d = Fraction(3,10)
a + b + c - d == 0
```

# Divisione tra numeri interi
In python esistono due operatori per effettuare la divisione tra due numeri $a$ e $b$:

1. `\` effettua la divisione come una "normale" calcolatrice (a meno della precisione numerica)
2. `\\` viene chiamato il [floor division operator](https://docs.python.org/release/2.2.3/whatsnew/node7.html) ed effettua la divisione "intera", ovvero $\lfloor \frac{a}{b} \rfloor$

```python
a = 2.41
b = 2.42
print(a/b)
print(a//b)
print(b//a)
```

# Sympy: Matematica simbolica
Sympy è una libreria Python per effettuare dei calcoli matematici simbolici. Per un approfondimento su come è stata sviluppata questa libreria si consiglia di leggere l'articolo [SymPy: symbolic computing in Python](https://peerj.com/articles/cs-103/) o direttamente la [documentazione della libreria](http://www.sympy.org/en/index.html).

Seguono un paio di esempi per rendere l'idea di cosa si può calcolare con Sympy.

```python
from sympy import *
x,y = symbols('x y')
init_printing(use_unicode=True)
```

```python
diff(sin(x)*exp(-x**2), x)
```

```python
integrate(cos(x), (x,0,3))
```

# Accesso al file system
Nel notebook è sempre possibile accedere al file system usando i commandi linux.

**ESEMPIO:**

```python
ls
```

     Volume in drive E is DATA
     Volume Serial Number is D299-FD79

     Directory of E:\GitHub\Programmazione2

    16/03/2017  10:54    <DIR>          .
    16/03/2017  10:54    <DIR>          ..
    16/03/2017  10:38               163 .gitignore
    14/03/2017  08:36    <DIR>          .ipynb_checkpoints
    09/03/2017  17:26             9,610 Appunti vari.ipynb
    16/03/2017  09:43    <DIR>          Assignments
    16/03/2017  10:14    <DIR>          data
    09/03/2017  17:08            19,558 Introduzione a Python - Parte seconda.ipynb
    16/03/2017  10:54            20,950 Introduzione a Python - Parte terza.ipynb
    09/03/2017  13:32            17,140 Introduzione a Python - Prima parte.ipynb
    14/03/2017  08:23            10,319 Lab 1 - Esercizi su semplici algoritmi numerici.ipynb
    09/03/2017  13:32             1,088 LICENSE
    09/03/2017  13:32               271 README.md
                   8 File(s)         79,099 bytes
                   5 Dir(s)  288,014,188,544 bytes free

```python
cd ..
```

    E:\GitHub

```python
cd Programmazione2/
```

    E:\GitHub\Programmazione2

```python

```


# Hough Transform

In this exercise, we will implement a Hough transform in order to detect parametric curves, such as lines or circles.
In the following, we shortly review the motivation for this technique.

Consider the point $p=(\mathtt{x},\mathtt{y})$ and the equation for a line $y = mx+c$. What are the lines that could pass through $p$?
The answer is simple: all the lines for which $m$ and $c$ satisfy $\mathtt{y} = m\mathtt{x}+c$.
Regarding $(\mathtt{x},\mathtt{y})$ as fixed, the last equation is that of a line in $(m,c)$-space.
Repeating this reasoning, a second point $p'=(\mathtt{x}',\mathtt{y}')$ will also have an associated line in parameter space, and the two lines will intersect at the point $(\tilde{m},\tilde{c})$, which corresponds to the line connecting $p$ and $p'$.

In order to find lines in the input image, we can thus pursue the following approach.
We start with an empty accumulator array quantizing the parameter space for $m$ and $c$.
For each edge pixel in the input image, we then draw a line in the accumulator array and increment the corresponding cells.
Edge pixels on the same line in the input image will produce intersecting lines in $(m,c)$-space and will thus reinforce the intersection point.
Maxima in this array thus correspond to lines in the input image that many edge pixels agree on.

In practice, the parametrization in terms of $m$ and $c$ is problematic, since the slope $m$ may become infinite.
Instead, we use the following parametrization in polar coordinates:
\begin{equation}
	\mathtt{x}\cos\theta + \mathtt{y}\sin\theta = \rho \label{eq:hough_line}
\end{equation}
This produces a sinusoidal curve in $(\rho,\theta)$-space, but otherwise the procedure is unchanged.

The following sub-questions will guide you through the steps of building a Hough transform.

```python
%%html
<!-- Run this cell to add heading letters per subtask (like a, b, c) -->
<style>
body {counter-reset: section;}
h2:before {counter-increment: section;
           content: counter(section, lower-alpha) ") ";}
</style>
```

```python
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
import imageio
import cv2
```

## Hough Voting
Build up an accumulator array ``acc`` for the parameter space $(\rho, \theta)$. $\theta$ ranges from $-\pi/2$ to $\pi/2$, and $\rho$ ranges from $-D$ to $D$, where $D$ denotes the length of the image diagonal.
Use ``n_bins_rho`` and ``n_bins_theta`` as the number of bins in each direction.
Initially, the array should be filled with zeros.

For each edge pixel in the input image, create the corresponding curve in $(\rho, \theta)$ space by evaluating above line equation for all values of $\theta$ and incrementing the corresponding cells of the accumulator array.

```python
def hough_transform(edge_image, n_bins_rho, n_bins_theta):
    # Vote accumulator
    votes = np.zeros((n_bins_rho, n_bins_theta), dtype=np.int)

    # Create bins
    diag = np.linalg.norm(edge_image.shape)  # Length of image diagonal
    theta_bins = np.linspace(-np.pi / 2, np.pi / 2, n_bins_theta)
    rho_bins = np.linspace(-diag, diag, n_bins_rho)

    # YOUR CODE HERE
    raise NotImplementedError()
    return votes, rho_bins, theta_bins
```

Now we can test the implementation on an example image and visualize the resulting Hough space by displaying it as a 2D image.

```python
color_im = imageio.imread('gantrycrane.png')
gray_im = cv2.cvtColor(color_im, cv2.COLOR_RGB2GRAY)

# Get edges using Canny
blurred = cv2.GaussianBlur(gray_im, None, sigmaX=2.5)
edges = cv2.Canny(blurred, threshold1=30, threshold2=50)

hough_space, rho_bins, theta_bins = hough_transform(edges, n_bins_rho=300, n_bins_theta=300)

fig, axes = plt.subplots(1, 3, figsize=(12, 4))
axes[0].imshow(color_im)
axes[0].set_title('Image')
axes[1].imshow(edges, cmap='gray')
axes[1].set_title('Edges')
axes[2].imshow(hough_space)
axes[2].set_title('Hough space')
axes[2].set_xlabel('theta (index)')
axes[2].set_ylabel('rho (index)')
fig.tight_layout()
```

## Non-Maximum Suppression and Finding Peaks
Write a function ``nms2d`` which suppresses all points in the Hough space that are not local maxima.
This can be achieved by looking at the 8 direct neighbors of each pixel and keeping only pixels whose value is greater than all its neighbors.

This function is simpler than the non-maximum suppression from the Canny Edge Detector exercise, since it does not take into account local gradients.

Tip: think about what to do at the borders of the Hough array. What happens if we simply ignore the border areas? How should we treat it to be precise? (Remember that theta represents an angle that wraps around back to itself).

```python
def nms2d(hough_array):
    hough_array_out = np.zeros_like(hough_array)

    # YOUR CODE HERE
    raise NotImplementedError()

    return hough_array_out
```

Write a function ``find_hough_peaks`` that takes the result of ``hough_transform`` as an argument, finds the extrema in Hough space using ``nms2d`` and returns the index of all points $(\rho_i, \theta_i)$ for which the corresponding Hough value is greater than ``threshold``.

```python
def find_hough_peaks(hough_space, threshold):
    # YOUR CODE HERE
    raise NotImplementedError()
```

What would happen if we swapped the order of the thresholding and the NMS operations?

YOUR ANSWER HERE

Now, let's plot the results of your implementation.

```python
def plot_hough_lines(image, rho, theta):
    # compute start and ending point of the line x*cos(theta)+y*sin(theta)=rho
    x0, x1 = 0, image.shape[1] - 1
    y0 = rho / np.sin(theta)
    y1 = (rho - x1 * np.cos(theta)) / np.sin(theta)

    # Check out this page for more drawing function in OpenCV:
    # https://docs.opencv.org/3.1.0/dc/da5/tutorial_py_drawing_functions.html
    for yy0, yy1 in zip(y0, y1):
        cv2.line(image, (x0, int(yy0)), (x1, int(yy1)), color=(255, 0, 0), thickness=1)

    return image

# Find maximum
rho_max_idx, theta_max_idx = find_hough_peaks(hough_space, 200)
print(f'gantrycrane.png: found {len(rho_max_idx)} lines in the image.')
rho_max, theta_max = rho_bins[rho_max_idx], theta_bins[theta_max_idx]

color_image = imageio.imread('gantrycrane.png')
image_with_lines = plot_hough_lines(color_image, rho_max, theta_max)

# Plot
fig, ax = plt.subplots(figsize=(8, 4))
ax.imshow(image_with_lines)
```

Is the result correct? Are there any undetected lines or wrong lines returned? If yes, why are those lines missing and why are there false positives? Can you think of ways to avoid those mistakes?

YOUR ANSWER HERE

```python
# Try another image
im = imageio.imread('circuit.png')

blurred = cv2.GaussianBlur(im, None, sigmaX=2)
edge = cv2.Canny(blurred, threshold1=30, threshold2=90)
hough_space, rho_bins, theta_bins = hough_transform(edge, n_bins_rho=300, n_bins_theta=300)

# Find maximum
rho_max_idx, theta_max_idx = find_hough_peaks(hough_space, 100)
print(f'circuit.png: found {len(rho_max_idx)} lines in the image.')
rho_max, theta_max = rho_bins[rho_max_idx], theta_bins[theta_max_idx]
color_image = cv2.cvtColor(im, cv2.COLOR_GRAY2RGB)
image_with_lines = plot_hough_lines(color_image, rho_max, theta_max)

# Plot
fig, ax = plt.subplots(figsize=(8, 4))
ax.imshow(image_with_lines)
fig.tight_layout()
```

## [BONUS] Circle Hough Transform for Circles

The Hough transform is a general technique that can not only be applied to lines, but also to other parametric curves, such as circles.
In the following, we will show how the implementation can be extended to finding circles.

A circle can be parameterized by the following equation:
$$
    (\mathtt{x}-a)^2 + (\mathtt{y}-b)^2 = r^2. \label{eq:hough_circle}
$$

Unfortunately, the computation and memory requirements for the Hough transform increase exponentially with the number of parameters.
While a 3D search space is still just feasible, we can dramatically reduce the amount of computation by integrating the gradient direction in the algorithm.

Without gradient information, all values $a, b$ lying on the cone given by above equation are incremented.
With the gradient information, we only need to increment points on an arc centered at $(a, b)$:
$$
\begin{eqnarray}
	a &=& x + r\cos\phi\\
	b &=& y + r\sin\phi,
\end{eqnarray}
$$
where $\phi$ is the gradient angle returned by the edge operator.

Create a function ``hough_circle`` which implements the Hough transform for circles.
Try your implementation for a practical application of counting coins in an image.
You can use the images ``coins1.png`` and ``coins2.png`` for testing.

```python
# YOUR CODE HERE
raise NotImplementedError()
```

## [BONUS] Line Hough Transform with Gradient Information
The same trick (as in **Part c**) of using the image gradient can be used for lines.
Modify the code from **Part a** to only vote for one line per edge pixel, instead of all the lines running through this pixel.
Can you think of any disadvantages of this approach?

```python
# YOUR CODE HERE
raise NotImplementedError()
```

## [BONUS] Counting Coins

Can you build a demo application that takes a photo of several different Euro coins lying on a table as input and tells you how much money it is in total in Euros (i.e. the output is one number)?

How well does it work when the coins start to overlap or lie on top of each other?

Use the functions you wrote in the previous questions.
(Hint: you may need to include a reference shape in the picture in order to obtain the absolute scale, i. e. to convert from pixels to millimeters).

```python
# YOUR CODE HERE
raise NotImplementedError()
```


# Naive Bayes Classifier

Naive Bayes classifier assumes that the effect of a particular feature in a class is independent of other features.

$
\begin{align}
\ P(h|D) = \frac{P(D|h) P(h)}{P(D)}
\end{align}
$

- P(h): the probability of hypothesis h being true (regardless of the data). This is known as the prior probability of h
- P(D): the probability of the data (regardless of the hypothesis). This is known as the prior probability
- P(h|D): the probability of hypothesis h given the data D. This is known as posterior probability
- P(D|h): the probability of data d given that the hypothesis h was true. This is known as posterior probability

```python
import pickle as pkl

with open('../data/titanic_tansformed.pkl', 'rb') as f:
    df_data = pkl.load(f)
```

```python
df_data.head()
```

      Survived
      Age
      SibSp
      Parch
      Fare
      1
      2
      3
      female
      male
      C
      Q
      S

      0
      0
      22.0
      1
      0
      7.2500
      0
      0
      1
      0
      1
      0
      0
      1

      1
      1
      38.0
      1
      0
      71.2833
      1
      0
      0
      1
      0
      1
      0
      0

      2
      1
      26.0
      0
      0
      7.9250
      0
      0
      1
      1
      0
      0
      0
      1

      3
      1
      35.0
      1
      0
      53.1000
      1
      0
      0
      1
      0
      0
      0
      1

      4
      0
      35.0
      0
      0
      8.0500
      0
      0
      1
      0
      1
      0
      0
      1

```python
df_data.shape
```

    (889, 13)

```python
data = df_data.drop("Survived",axis=1)
label = df_data["Survived"]
```

```python
from sklearn.model_selection import train_test_split
data_train, data_test, label_train, label_test = train_test_split(data, label, test_size = 0.2, random_state = 101)
```

```python
from sklearn.naive_bayes import GaussianNB
import time

tic = time.time()
nb_cla = GaussianNB()
nb_cla.fit(data_train,label_train)
print('Time taken for training Naive Bayes', (time.time()-tic), 'secs')

predictions = nb_cla.predict(data_test)
print('Accuracy', nb_cla.score(data_test, label_test))

from sklearn.metrics import classification_report, confusion_matrix
print(confusion_matrix(label_test, predictions))
print(classification_report(label_test, predictions))
```

    Time taken for training Naive Bayes 0.0014061927795410156 secs
    Accuracy 0.8202247191011236
    [[95 12]
     [20 51]]
                 precision    recall  f1-score   support

              0       0.83      0.89      0.86       107
              1       0.81      0.72      0.76        71

    avg / total       0.82      0.82      0.82       178

## Multinomial Naive-Bayes
- Used when the values are discrete

```python
from sklearn.naive_bayes import MultinomialNB
import time

tic = time.time()
nb_cla = MultinomialNB()
nb_cla.fit(data_train,label_train)
print('Time taken for training Naive Bayes', (time.time()-tic), 'secs')

predictions = nb_cla.predict(data_test)
print('Accuracy', nb_cla.score(data_test, label_test))
```

    Time taken for training Naive Bayes 0.12916111946105957 secs
    Accuracy 0.7303370786516854

## Bernoulli Naive-Bayes
- Used when the values of all the features are binary

```python
from sklearn.naive_bayes import BernoulliNB
import time

tic = time.time()
nb_cla = BernoulliNB()
nb_cla.fit(data_train,label_train)
print('Time taken for training Naive Bayes', (time.time()-tic), 'secs')

predictions = nb_cla.predict(data_test)
print('Accuracy', nb_cla.score(data_test, label_test))
```

    Time taken for training Naive Bayes 0.0020270347595214844 secs
    Accuracy 0.7865168539325843

```python

```

classification/notebooks/08 - Naive Bayes.ipynb


JuPOT - Tutorial
================

```julia
push!(LOAD_PATH, "$(homedir())/desktop/financial-opt-tools/src")

using JuPOT
# Generate synthetic data sets for Demonstration
############
# Assets
############
n = 10 # No. Of Assets
returns = rand(n)*0.4
covariance = let
    S = randn(n, n)
    S'S + eye(n)
end
names = [randstring(3) for i in 1:n] # List of asset names

# Assets data structure containing, names, expected returns, covarariance
assets = AssetsCollection(names, returns, covariance)
```

    10x2 DataFrames.DataFrame
    | Row | A     | B         |
    |-----|-------|-----------|
    | 1   | "fqU" | 0.145521  |
    | 2   | "nbG" | 0.0794849 |
    | 3   | "yVw" | 0.205992  |
    | 4   | "nlr" | 0.117006  |
    | 5   | "eZU" | 0.230518  |
    | 6   | "NO1" | 0.0615766 |
    | 7   | "Bnh" | 0.292437  |
    | 8   | "M22" | 0.235276  |
    | 9   | "mUe" | 0.0722919 |
    | 10  | "aZe" | 0.0120013 |

## Simple MVO

\begin{align}
&\text{minimize} && w^\top\Sigma w \\
&\text{subject to} && \mu^\top w\geq r_{\min} \\
&       && \mathbf{1}^\top w = 1 \\
&       && w \succeq 0 \\
\end{align}

```julia
######################################################
################### SIMPLE MVO #######################
######################################################

# Example 1: Basic
target_return = 0.2
# refer to model definition for keyword arguments, etc.
mvo = SimpleMVO(assets, target_return; short_sale=false)
```

     Sense: Min

     Variables:
    w[1:10] >= 0

     Objective Function:
      dot(w,10x10 Array{Float64,2}:
     23.7016     9.98336   -0.553158  …   0.733098  -0.814721  -0.56417
      9.98336   17.9551     2.96608      -0.121899   0.172077   1.30404
     -0.553158   2.96608   10.1192       -3.80496    0.209278   0.126627
     -0.570444   1.48801   -0.296499      1.93211   -1.10089    1.20087
      2.06455   -0.231737  -2.93694      -1.33384   -0.421206   0.919909
      3.19321   -1.29136    4.48853   …  -0.792987   1.35509   -0.088859
     -2.3449     3.35075    0.865592     -0.983744   2.56856    1.105
      0.733098  -0.121899  -3.80496       5.67463   -0.1951    -0.615585
     -0.814721   0.172077   0.209278     -0.1951     8.57526   -2.73014
     -0.56417    1.30404    0.126627     -0.615585  -2.73014    6.77963  * w)

     Constraints:
    0x2 DataFrames.DataFrame

    2x2 DataFrames.DataFrame
    | Row | Default   |
    |-----|-----------|
    | 1   | "default" |
    | 2   | "default" |

    | Row | Constraint                                                                                |
    |-----|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
    | 1   | :(dot([0.5479590591553458,0.8124768275985059,0.38122815802239063,0.8908056131630402,0.21612177093254403,0.021889817591750127,0.18684295493730074,0.7584938923953053,0.23944112136715145,0.7859992194071441],w) ≥ 0.2) |
    | 2   | :(dot(ones(10),w) == 1)                                                                                |

     Assets:
     10x2 DataFrames.DataFrame
    | Row | A     | B         |
    |-----|-------|-----------|
    | 1   | "xN2" | 0.547959  |
    | 2   | "7NE" | 0.812477  |
    | 3   | "k3A" | 0.381228  |
    | 4   | "ji3" | 0.890806  |
    | 5   | "TRc" | 0.216122  |
    | 6   | "vy1" | 0.0218898 |
    | 7   | "Hqy" | 0.186843  |
    | 8   | "DOO" | 0.758494  |
    | 9   | "Gi9" | 0.239441  |
    | 10  | "lyg" | 0.785999  |

```julia
optimize(mvo)
```

    10-element Array{Float64,1}:
     0.00709497
     5.81211e-14
     0.221814
     5.78534e-10
     0.235785
     6.99527e-14
     0.0849516
     0.310166
     0.066205
     0.0739836

Constraints
-----------

\begin{align}
&\text{minimize} && w^\top\Sigma w \\
&\text{subject to} && \mu^\top w\geq r_{\min} \\
&       && \mathbf{1}^\top w = 1 \\
&       && w \succeq 0 \\
&       && I_{tech}^\top w \leq 0.3 \\
&       && I_{fin}^\top w \leq 0.5 \\
\end{align}

```julia
##################################
# Example 2: Adding a Constraint #
##################################
function genTechIndicator()
    [0,0,1,1,0,1,0,1,1,0]
end

# Adding a simple weight constraint
constraints = Dict((:techClassWeightConstraint => :(dot(w,tech) <= tech_thresh)),
                    (:finClassWeightConstraint => :(dot(w,fin) <= fin_thresh)))

parameters = Dict(:tech=>genTechIndicator(),
                  :tech_thresh => 0.3,
                  :fin=> [1,1,0,0,1,0,1,0,0,0],
                  :fin_thresh => 0.5)
# refer to model definition for keyword arguments, etc.
mvo = SimpleMVO(assets, target_return, constraints; short_sale=false)
w = optimize(mvo, parameters)

```

    10-element Array{Float64,1}:
     0.0499577
     4.4887e-12
     0.114043
     0.000115883
     0.256528
     5.46294e-12
     0.193514
     0.171354
     0.0144872
     0.2

```julia
# Constraint Checks
print("Tech Class Constraint: \n")
techClassWeight = dot(w, parameters[:tech])
print(techClassWeight, ", ", techClassWeight <= parameters[:tech_thresh])

print("\n")

print("Fin Class Constraint: \n")
finClassWeight = dot(w, parameters[:fin])
print(finClassWeight, ", ", finClassWeight <= parameters[:fin_thresh])
```

    Tech Class Constraint:
    0.29999999999897375, true
    Fin Class Constraint:
    0.49999999999262246, true

Updated Constraint
------------------

\begin{align}
&\text{minimize} && w^\top\Sigma w \\
&\text{subject to} && \mu^\top w\geq r_{\min} \\
&       && \mathbf{1}^\top w = 1 \\
&       && w \succeq 0 \\
&       && I_{tech}^\top w \leq 0.04 \\
&       && I_{fin}^\top w \leq 0.5 \\
\end{align}

```julia
#######################################################
# Example 3: Changing a Constraint's parameter values #
#######################################################

# Changing values of an entered constraint
parameters[:tech_thresh] = 0.04

# refer to model definition for keyword arguments, etc.
w = optimize(mvo, parameters)
```

    10-element Array{Float64,1}:
     0.0854787
     5.03612e-11
     1.24508e-10
     4.85935e-11
     0.204448
     1.53004e-11
     0.210073
     0.0122023
     0.0277977
     0.46

```julia
# Constraint Checks
print("Tech Class Constraint: \n")
techClassWeight = dot(w, parameters[:tech])
print(techClassWeight, ", ", techClassWeight <= parameters[:tech_thresh])

print("\n")

print("Fin Class Constraint: \n")
finClassWeight = dot(w, parameters[:fin])
print(finClassWeight, ", ", finClassWeight <= parameters[:fin_thresh])
```

    Tech Class Constraint:
    0.03999999999938704, true
    Fin Class Constraint:
    0.4999999999983238, true

Constraints
-----------
New Model

\begin{align}
&\text{minimize} && w^\top\Sigma w \\
&\text{subject to} && \mu^\top w\geq r_{\min} \\
&       && \mathbf{1}^\top w = 1 \\
&       && w \succeq 0 \\
&       && I_{fin}^\top w \leq 0.5 \\
\end{align}

```julia
####################################
# Example 4: Deleting a Constraint #
####################################

# Removing a previously defined constraint
delete!(constraints, :techClassWeightConstraint)

# refer to model definition for keyword arguments, etc.
mvo = SimpleMVO(assets, target_return, constraints; short_sale=false)
w = optimize(mvo, parameters)
```

    10-element Array{Float64,1}:
     0.00709497
     8.75224e-14
     0.221814
     1.10185e-9
     0.235785
     1.04763e-13
     0.0849516
     0.310166
     0.066205
     0.0739836

```julia
#Display Current Constraint Container
constraints
```

    Dict{Symbol,Expr} with 1 entry:
      :finClassWeightConstrai… => :(dot(w,fin) <= fin_thresh)

```julia
# Constraint Checks
print("Tech Class Constraint: \n")
techClassWeight = dot(w, parameters[:tech])
print(techClassWeight, ", ", techClassWeight <= parameters[:tech_thresh])

print("\n")

print("Fin Class Constraint: \n")
finClassWeight = dot(w, parameters[:fin])
print(finClassWeight, ", ", finClassWeight <= parameters[:fin_thresh])
```

    Tech Class Constraint:
    0.5981848381137542, false
    Fin Class Constraint:
    0.3278316105611047, true

\begin{align}
&\text{minimize} && w^\top\Sigma w \\
&\text{subject to} && \mu^\top w\geq r_{\min} \\
&       && \mathbf{1}^\top w = 1 \\
&       && w \succeq 0 \\
&       && I_{fin}^\top w \leq 0.5 \\
&       && {w_i} \leq 0.5 &&  \forall i \in \{1, 2, 3\} \\
&       && {w_i} \leq 0.01 \text{ }  && \forall i \in \{4, 5, 6\}\\
\end{align}

```julia
##########################################
# Example 5: Adding multiple Constraints #
##########################################

# Adding a multiple weight constraints
# Adding a simple weight constraint
assetClassThresholds = [0.5, 0.5, 0.5, 0.01, 0.01, 0.01]
assetsWeightConstraints = [symbol("assetWeightConstraint$i") => :(w[$i] <= $(assetClassThresholds[i])) for i=1:6]

# Different constraint sets can be merged to form new ones
new_constraints = merge(constraints, assetsWeightConstraints)

# refer to model definition for keyword arguments, etc.
mvo = SimpleMVO(assets, target_return, new_constraints; short_sale=false)
w = optimize(mvo, parameters)
```

    10-element Array{Float64,1}:
     0.046634
     3.84734e-11
     0.191747
     0.01
     0.01
     0.00735835
     0.0797681
     0.312535
     0.145346
     0.196611

```julia
print("Fin Class Constraint: \n")
finClassWeight = dot(w, parameters[:fin])
print(finClassWeight, ", ", finClassWeight <= parameters[:fin_thresh])

print("\n")

print("Weights 1 - 3 Constraint: \n")
for i=1:3
    print(w[i], ", ", w[i] <= 0.5)
    print("\n")
end

print("Weights 4 - 6 Constraint: \n")
for i=4:6
    print(w[i], ", ", w[i] <= 0.01)
    print("\n")
end
```

    Fin Class Constraint:
    0.13640211660280685, true
    Weights 1 - 3 Constraint:
    0.046634024632747455, true
    3.847338889024689e-11, true
    0.19174749057889884, true
    Weights 4 - 6 Constraint:
    0.009999999980661997, true
    0.009999999994074223, true
    0.007358350255935825, true

```julia

#####################################
# Example 6: Using Different Assets #
#####################################

############
# Asset #2 #
############
n = 10 # No. Of Assets
returns_new = rand(n)
covariance_new = let
    S = randn(n, n)
    S'S + eye(n)
end
names_new = [randstring(3) for i in 1:n]
# Assets data structure containing, names, expected returns, covarariance
assets_new = AssetsCollection(names_new, returns_new, covariance_new)

# Using the same previously defined constraints we can run the model on a different set of assets effortlessly
mvo = SimpleMVO(assets_new, target_return; short_sale=false)
optimize(mvo, parameters)
```

    10-element Array{Float64,1}:
     1.35231e-11
     0.294902
     1.10843e-11
     1.90023e-8
     0.185996
     2.80191e-11
     0.294275
     7.3748e-10
     0.0180918
     0.206734

```julia
# Forgot what constraints and parameters were defined for initial constraints? No Problem!
constraints # Prints the constraints
```

    Dict{Symbol,Expr} with 1 entry:
      :finClassWeightConstrai… => :(dot(w,fin) <= fin_thresh)

```julia
parameters # prints the parameters
```

    Dict{Symbol,Any} with 4 entries:
      :tech        => [0,0,1,1,0,1,0,1,1,0]
      :tech_thresh => 0.04
      :fin_thresh  => 0.5
      :fin         => [1,1,0,0,1,0,1,0,0,0]

```julia
# It's good practice to remove unnecessary parameters
delete!(parameters, :tech)
delete!(parameters, :tech_thresh)

parameters
```

    Dict{Symbol,Any} with 2 entries:
      :fin_thresh => 0.5
      :fin        => [1,1,0,0,1,0,1,0,0,0]

## Robust MVO

\begin{align}
&\text{minimize} && w^\top\Sigma w \\
&\text{subject to} && \big\lVert \Theta^{\frac{1}{2}}w \big\rVert \leq \epsilon \\
&       && \mu^\top w\geq r_{\min} \\
&       && \mathbf{1}^\top w = 1 \\
&       && w \succeq 0 \\
\end{align}

```julia
##############################
#  Example 7 Using Robust MVO#
##############################

# refer to model definition for keyword arguments, etc
# If no uncertainty matrix is entered the model defaults
# to the ellipse whose axes are proportional to the
# individual variances of each asset

rmvo = RobustMVO(assets, target_return; short_sale=true)
optimize(rmvo, parameters)
```

```julia
#################################################################
#  Example 8 Creating Custom Functions (e.g efficient frontier) #
#################################################################
n = 20
variance = Array(Float32,n)
returns = Array(Float32,n)
target_returns = linspace(0,0.4,20)
for i in 1:n
    target_ret = target_returns[i]
    mvo = SimpleMVO(assets, target_ret; short_sale=true)
    w = optimize(mvo, parameters)
    variance[i] = mvo.objVal
    returns[i] = dot(w, JuPOT.getReturns(assets))
end
```

```julia
variance
```

    20-element Array{Float32,1}:
     0.806573
     0.806573
     0.806573
     0.806573
     0.806573
     0.806573
     0.806573
     0.806573
     0.806573
     0.808813
     0.833964
     0.887046
     0.968058
     1.077
     1.21387
     1.37868
     1.57141
     1.79208
     2.04067
     2.3172

```julia
returns
```

    20-element Array{Float32,1}:
     0.181042
     0.181042
     0.181042
     0.181042
     0.181042
     0.181042
     0.181042
     0.181042
     0.181042
     0.189474
     0.210526
     0.231579
     0.252632
     0.273684
     0.294737
     0.315789
     0.336842
     0.357895
     0.378947
     0.4

```julia
mean(JuPOT.getReturns(assets))
```

    0.4841258434570479

```julia
mean(rand(10)*0.4)
```

    0.15125274010964593

```julia

```


Solving the Lego Planning Problem with Pyomo and Glpk by Stefano Gualandi is licensed under a Creative Commons Attribution 4.0 International License. Based on a project at https://github.com/mathcoding/opt4ds.

# 2. Solving the Lego Planning Problem

In this notebook, we explain how to solve **Linear Programming** problem that we have written to model the Lego Planning problem (see the slides on KIRO):

$$
\begin{align}
\max \quad & 8 c + 11 t  \\
\mbox{s.t. } \quad & 2c + 2t \leq 24 \\
& c + 2 t \leq 18\\
& c \geq 0\\
& t \geq 0
\end{align}
$$

We show below how to use Pyomo to define the **variables**, the **objective function**, and the **constraints**.

First, we need to install the [Pyomo](http://www.pyomo.org/) modeling language and the [GLPK](https://www.gnu.org/software/glpk/) open source solver, as explained in the following section.

## 2.1 Software Installation
If you are running this notebook in a Colab, you don't need to install anything else on your computer.

Otherwise, if you have installed the recommended Anaconda Python distribution, you have to run the following two commands:

1. To install the [Pyomo](http://www.pyomo.org/) optimization modeling language:

```
conda install -c conda-forge pyomo
```

2. To install the open source [GLPK](https://www.gnu.org/software/glpk/) solver:

```
conda install -c conda-forge glpk
```

3. (Optional) You can install some extra packages of Pyomo using the following command:

```
conda install -c conda-forge pyomo.extras
```

For details about the Pyomo installation, we refer to the official [Pyomo Documentation](https://pyomo.readthedocs.io/en/stable/).

The following lines are for running this notebook in a Google Colab:

```python
import shutil
import sys
import os.path

if not shutil.which("pyomo"):
    !pip install -q pyomo
    assert(shutil.which("pyomo"))

if not (shutil.which("glpk") or os.path.isfile("glpk")):
    if "google.colab" in sys.modules:
        !apt-get install -y -qq glpk-utils
    else:
        try:
            !conda install -c conda-forge glpk
        except:
            pass
```

## 2.2 Define the model entities
To build the Linear Programming model with Pyomo, we need first to import the Pyomo library:

```python
from pyomo.environ import *
```

At this point, we first declare a global object that refer to our model:

```python
model = ConcreteModel()
```

Notice the `ConcreteModel` is a python class, and we are initializing an object called `model` of type `ConcreteModel`.

Then we star by declaring the two non/negative variables:

```python
# declare decision variables
model.c = Var(domain=NonNegativeReals)
model.t = Var(domain=NonNegativeReals)
```

Here, we add variable `c` and `t` to the model. The two variables are of type `Var`, and we are passing as input to the constructor the parameter `NonNegativeReals`. That is, we are declaring the two variables $c \geq 0, t \geq 0$.

Next, we declare the objective function.

```python
# Declare objective
model.cost = Objective(
    expr =  8*model.c + 11*model.t,
    sense = maximize)
```

In this case, we are adding the variable `cost`  to our model, that is an instance of an object of type `Objective`. The constructor is called with a linear expression `8*model.c + 11*model.t`, and we denote the sense of the objective function as `maximize`.

Next step is to introduce the two linear constraints:

```python
# Declare constraints
model.cnstr1 = Constraint(expr = 2*model.c + 2*model.t <= 24)
model.cnstr2 = Constraint(expr = 1*model.c + 2*model.t <= 18)
```

We have used here a third type of objects, namely the `Constraint` class, giving in input the two linear relation.

Notice that we are **declaring** the model, without programming any algorithm to actually solve this model. To find the optimal solution of this LP, we are going to use the Integer Linear Programming solver GLPK.

## 2.3 Solve the model
We have use the type of objects of the Pyomo library to *declare* our Linear Programming model. Next, we have to use a solver to find the optima value for the decision variables.

With Pyomo, we are not constrained to use a specific solver. In this notebook, for the sake of simplicity, we use the GLPK solver as follows, where we set the parameter `tee=True`, in order to see the logs produced by the solver.

```python
# Solve the LP model
sol = SolverFactory('glpk').solve(model, tee=True)
```

Every time we invoke a solver, it is very good practice to check the status of the solver, since it may have stop its execution for several different reasons:

```python
# Basic info about the solution process
for info in sol['Solver']:
    print(info)
```

Whenever the status of the solver is OK, we can query the solver to get the optimal value of the objective function and of the decision variables.

```python
# Report solution value
print("Optimal solution value: z =", model.cost())
print("Decision variables:")
print("\tProduction of chairs:", model.c())
print("\tProduction of tables:", model.t())
```

As we found manually during the *Active Learning* session, the optimal production plan is to build 6 chairs and 6 tables, to get a profit of 114 kroner.

It is also possible to check the file temporary file produced by Pyomo and passed as input to the Glpk solver, with the following command:

```python
model.write('lego1.lp')
```

## 2.4 Complete Script
We report below the complete script.

```python
# Main model object
model = ConcreteModel()

# Declare decision variables
model.c = Var(domain=NonNegativeReals)
model.t = Var(domain=NonNegativeReals)

# Declare objective
model.cost = Objective(
    expr =  8*model.c + 11*model.t,
    sense = maximize)

# Declare constraints
model.cnstr1 = Constraint(expr = 2*model.c + 2*model.t <= 24)
model.cnstr2 = Constraint(expr = 1*model.c + 2*model.t <= 18)

# Solve the LP model
sol = SolverFactory('glpk').solve(model, tee=True)

# Basic info about the solution process
for info in sol['Solver']:
    print(info)

# Report solution value
print("Optimal solution value: z =", model.cost())
print("Decision variables:")
print("\tProduction of chairs:", model.c())
print("\tProduction of tables:", model.t())
```

## 2.5 Exercise: three variables
As an exercise, you can try to modify the previous script to solve the second version of the Lego Planning problem:

$$
\begin{align}
\max \quad & 8 c + 11 t + 15 s \\
\mbox{s.t. } \quad & 2c + 2t +2s \leq 24 \\
& c + 2 t +3s \leq 18\\
& c \geq 0\\
& t \geq 0\\
& s \geq 0
\end{align}
$$

Basically, you have to add a third variable to the model, to modify the objective function, and the two constraints. Later, you call the solver again.

```python
# TO BE COMPLETED ...
```


# LabX1: Supplementary Practice Problems

These are similar to programming problems you may encounter in the mid-terms. They are not graded but we will review them in lab sessions.

**1**. (10 points) The logistic map is defined by the following simple function

$$
f(x) = rx(1-x)
$$

For $x_0 = 0.1$ and $r = 4.0$, store the first 10 values of the iterated logistic map $x_{i+1} = rx_i(1-x_i)$ in a list. The first value in the list should be $x_0$.

```python

```

**2**. (10 points) Write a function to find the greatest common divisor (GCD) of 2 numbers using Euclid's algorithm.:

\begin{align}
\gcd(a,0) &= a \\
\gcd(a, b) &= \gcd(b, a \mod b)
\end{align}

Find the GCD of 5797 and 190978.

Now write a function to find the GCD given a collection of numbers.

Find the GCD of (24, 48, 60, 120, 8).

```python

```

**3**. (10 points) Find the least squares linear solution to the following data

```
y = [1,2,3,4]
x1 = [1,2,3,4]
x2 = [2,3,4,5]
```

That is, find the "best" intercept and slope for the variables `x1` and `x2`.

```python

```

**4**. (10 points) Read the `mtcars` data frame from R to a `pandas` DataFrame. Find the mean `wt` and `mpg` for all cars grouped by the number of `gear`s.

```python

```

**5**. (10 points) Read the `iris` data frame from R to a `pandas` DataFrame. Make a `seaborn` plot showing a linear regression of `Petal.Length` (y) against `Sepal.Length` (x). Make a separate regression line for each `Species`.

```python

```

**6**. (10 points) Write a function that can flatten a nested list of arbitrary depth. Check that

```python
flatten([1,[2,3],[4,[5,[6,7],8],9],10,[11,12]])
```

returns

```python
[1,2,3,4,5,6,7,8,9,10,11,12]
```

For simplicity, assume that the only data structure you will encounter is a list. You can check if an item is a list by using

```python
isinstance(item, list)
```

```python

```

**7**. (10 points) Create the following table

```python
array([[  1,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
       [  1,   1,   0,   0,   0,   0,   0,   0,   0,   0,   0],
       [  1,   2,   1,   0,   0,   0,   0,   0,   0,   0,   0],
       [  1,   3,   3,   1,   0,   0,   0,   0,   0,   0,   0],
       [  1,   4,   6,   4,   1,   0,   0,   0,   0,   0,   0],
       [  1,   5,  10,  10,   5,   1,   0,   0,   0,   0,   0],
       [  1,   6,  15,  20,  15,   6,   1,   0,   0,   0,   0],
       [  1,   7,  21,  35,  35,  21,   7,   1,   0,   0,   0],
       [  1,   8,  28,  56,  70,  56,  28,   8,   1,   0,   0],
       [  1,   9,  36,  84, 126, 126,  84,  36,   9,   1,   0],
       [  1,  10,  45, 120, 210, 252, 210, 120,  45,  10,   1]])
```

Start with the first row

```
[  1,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0]
```

and build the subsequent rows using a simple rule that only depends on the previous row.

```python

```

**8**. (10 points) Read the following data sets into DataFrames.

- url1 = "https://raw.github.com/vincentarelbundock/Rdatasets/master/csv/DAAG/hills.csv"
- url2 = "https://raw.github.com/vincentarelbundock/Rdatasets/master/csv/DAAG/hills2000.csv"

Create a new DataFraem only containing the names present in both DataFrames. Drop the `timef` column and have a single column for `dist` , `climb` and `time` that shows the average value of the two DataFrames. The final DtataFrame will thus have 4 columns (name, dist, climb, time).

```python

```


# Periodic Signals

*This Jupyter notebook is part of a [collection of notebooks](../index.ipynb) in the bachelors module Signals and Systems, Comunications Engineering, Universität Rostock. Please direct questions and suggestions to [Sascha.Spors@uni-rostock.de](mailto:Sascha.Spors@uni-rostock.de).*

## Spectrum

Periodic signals are an import class of signals. Many practical signals can be approximated reasonably well as periodic functions. The latter holds often when considering only a limited time-interval. Examples for periodic signals are a superposition of harmonic signals, signals captured from vibrating structures or rotating machinery, as well as speech signals or signals from musical instruments. The spectrum of a periodic signal exhibits specific properties which are derived in the following.

### Representation

A [periodic signal](https://en.wikipedia.org/wiki/Periodic_function) $x(t)$ is a signal that repeats its values in regular periods. It has to fulfill

\begin{equation}
x(t) = x(t + n \cdot T_\text{p})
\end{equation}

for $n \in \mathbb{Z}$ where its period is denoted by $T_\text{p} > 0$. A signal is termed *aperiodic* if is not periodic. One period $x_0(t)$ of a periodic signal is given as

\begin{equation}
x_0(t) = \begin{cases}
x(t) & \text{for } 0 \leq t < T_\text{p} \\
0 & \text{otherwise}
\end{cases}
\end{equation}

A periodic signal can be represented by [periodic summation](https://en.wikipedia.org/wiki/Periodic_summation) of one period $x_0(t)$

\begin{equation}
x(t) = \sum_{\mu = - \infty}^{\infty} x_0(t - \mu T_\text{p})
\end{equation}

which can be rewritten as convolution

\begin{equation}
x(t) = \sum_{\mu = - \infty}^{\infty} x_0(t) * \delta(t - \mu T_\text{p}) = x_0(t) * \sum_{\mu = - \infty}^{\infty} \delta(t - \mu T_\text{p})
\end{equation}

using the sifting property of the Dirac impulse. It can be concluded that a periodic signal can be represented by one period $x_0(t)$ of the signal convolved with a series of Dirac impulses.

**Example**

The cosine signal $x(t) = \cos (\omega_0 t)$ has a periodicity of $T_\text{p} = \frac{2 \pi}{\omega_0}$. One period is given as

\begin{equation}
x_0(t) = \cos (\omega_0 t) \cdot \text{rect} \left( \frac{t}{T_\text{p}} - \frac{T_\text{p}}{2} \right)
\end{equation}

Introduced into above representation of a periodic signal yields

\begin{align}
x(t) &= \cos (\omega_0 t) \cdot \text{rect} \left( \frac{t}{T_\text{p}} - \frac{T_\text{p}}{2} \right) * \sum_{\mu = - \infty}^{\infty} \delta(t - \mu T_\text{p}) \\
&= \cos (\omega_0 t) \sum_{\mu = - \infty}^{\infty} \text{rect} \left( \frac{t}{T_\text{p}} - \frac{T_\text{p}}{2} - \mu T_\text{p} \right) \\
&= \cos (\omega_0 t)
\end{align}

since the sum over the shifted rectangular signals is equal to one.

### The Dirac Comb

The sum of shifted Dirac impulses, as used above to represent a periodic signal, is known as [*Dirac comb*](https://en.wikipedia.org/wiki/Dirac_comb). The Dirac comb is defined as

\begin{equation}
{\bot \!\! \bot \!\! \bot}(t) = \sum_{\mu = - \infty}^{\infty} \delta(t - \mu)
\end{equation}

It is used for the representation of periodic signals and for the modeling of ideal sampling. In order to compute the spectrum of a periodic signal, the Fourier transform of the Dirac comb $\mathcal{F} \{ {\bot \!\! \bot \!\! \bot}(t) \}$ is derived in the following.

Fourier transformation of the left- and right-hand side of above definition yields

\begin{equation}
\mathcal{F} \{ {\bot \!\! \bot \!\! \bot}(t) \} = \sum_{\mu = - \infty}^{\infty} e^{-j \mu \omega}
\end{equation}

The exponential function $e^{-j \mu \omega}$ for $\mu \in \mathbb{Z}$ is periodic with a period of $2 \pi$. Hence, the Fourier transform of the Dirac comb is also periodic with a period of $2 \pi$.

Convolving a [rectangular signal](../notebooks/continuous_signals/standard_signals.ipynb#Rectangular-Signal) with the Dirac comb results in

\begin{equation}
{\bot \!\! \bot \!\! \bot}(t) * \text{rect}(t) = 1
\end{equation}

Fourier transform of the left- and right-hand side yields

\begin{equation}
\mathcal{F} \{ {\bot \!\! \bot \!\! \bot}(t) \} \cdot \text{sinc}\left(\frac{\omega}{2}\right) = 2 \pi \delta(\omega)
\end{equation}

For $\text{sinc}( \frac{\omega}{2} ) \neq 0$, which is equal to $\omega \neq 2 n \cdot \pi$ with $n \in \mathbb{Z} \setminus \{0\}$, this can be rearranged as

\begin{equation}
\mathcal{F} \{ {\bot \!\! \bot \!\! \bot}(t) \} = 2 \pi \, \delta(\omega) \cdot \frac{1}{\text{sinc}\left(\frac{\omega}{2}\right)} = 2 \pi \, \delta(\omega)
\end{equation}

Note that the [multiplication property](../continuous_signals/standard_signals.ipynb#Dirac-Impulse) of the Dirac impulse and $\text{sinc}(0) = 1$ has been used to derive the last equality. The Fourier transform is now known for the interval $-2 \pi < \omega < 2 \pi$. It has already been concluded that the Fourier transform is periodic with a period of $2 \pi$. Hence, the Fourier transformation of the Dirac comb can be derived by periodic continuation as

\begin{equation}
\mathcal{F} \{ {\bot \!\! \bot \!\! \bot}(t) \} = \sum_{\mu = - \infty}^{\infty} 2 \pi \, \delta(\omega - 2 \pi \mu) = \sum_{\mu = - \infty}^{\infty} 2 \pi \, \left( \frac{\omega}{2 \pi} - \mu \right)
\end{equation}

The last equality follows from the scaling property of the Dirac impulse. The Fourier transform can now be rewritten in terms of the Dirac comb

\begin{equation}
\mathcal{F} \{ {\bot \!\! \bot \!\! \bot}(t) \} = {\bot \!\! \bot \!\! \bot} \left( \frac{\omega}{2 \pi} \right)
\end{equation}

The Fourier transform of a Dirac comb with unit distance between the Dirac impulses is a Dirac comb with a distance of $2 \pi$ between the Dirac impulses which are weighted by $2 \pi$.

**Example**

The following example computes the truncated series

\begin{equation}
X(j \omega) = \sum_{\mu = -M}^{M} e^{-j \mu \omega}
\end{equation}

as approximation of the Fourier transform $\mathcal{F} \{ {\bot \!\! \bot \!\! \bot}(t) \}$ of the Dirac comb. For this purpose the sum is defined and plotted in `SymPy`.

```python
%matplotlib inline
import sympy as sym
sym.init_printing()

mu = sym.symbols('mu', integer=True)
w = sym.symbols('omega', real=True)
M = 20

X = sym.Sum(sym.exp(-sym.I*mu*w), (mu, -M, M)).doit()
sym.plot(X, xlabel='$\omega$', ylabel='$X(j \omega)$', adaptive=False, nb_of_points=1000);
```

**Exercise**

* Change the summation limit $M$. How does the approximation change? Note: Increasing $M$ above a certain threshold may lead to numerical instabilities.

### Fourier-Transform

In order to derive the Fourier transform $X(j \omega) = \mathcal{F} \{ x(t) \}$ of a periodic signal $x(t)$ with period $T_\text{p}$, the signal is represented by one period $x_0(t)$ and the Dirac comb. Rewriting above representation of a periodic signal in terms of a sum of Dirac impulses by noting that $\delta(t - \mu T_\text{p}) = \frac{1}{T_\text{p}} \delta(\frac{t}{T_\text{p}} - \mu)$ yields

\begin{equation}
x(t) = x_0(t) * \frac{1}{T_\text{p}} {\bot \!\! \bot \!\! \bot} \left( \frac{t}{T_\text{p}} \right)
\end{equation}

The Fourier transform is derived by application of the [convolution theorem](../fourier_transform/theorems.ipynb#Convolution-Theorem)

\begin{align}
X(j \omega) &= X_0(j \omega) \cdot {\bot \!\! \bot \!\! \bot} \left( \frac{\omega T_\text{p}}{2 \pi} \right) \\
&= \frac{2 \pi}{T_\text{p}} \sum_{\mu = - \infty}^{\infty} X_0 \left( j \, \mu \frac{2 \pi}{T_\text{p}} \right) \cdot
\delta \left( \omega - \mu \frac{2 \pi}{T_\text{p}} \right)
\end{align}

where $X_0(j \omega) = \mathcal{F} \{ x_0(t) \}$ denotes the Fourier transform of one period of the periodic signal. From the last equality it can be concluded that the Fourier transform of a periodic signal consists of a series of weighted Dirac impulses. These Dirac impulse are equally distributed on the frequency axis $\omega$ at an interval of $\frac{2 \pi}{T_\text{p}}$. The weights of the Dirac impulse are given by the values of the spectrum $X_0(j \omega)$ of one period at the locations $\omega = \mu \frac{2 \pi}{T_\text{p}}$. Such a spectrum is termed *line spectrum*.

### Parseval's Theorem

[Parseval's theorem](../fourier_transform/theorems.ipynb#Parseval%27s-Theorem) relates the energy of a signal in the time domain to its spectrum. The energy of a periodic signal is in general not defined. This is due to the fact that its energy is unlimited, if the energy of one period is non-zero. As alternative, the average power of a periodic signal $x(t)$ is used. It is defined as

\begin{equation}
P = \frac{1}{T_\text{p}} \int_{0}^{T_\text{p}} |x(t)|^2 \; dt
\end{equation}

Introducing the Fourier transform of a periodic signal into [Parseval's theorem](../fourier_transform/theorems.ipynb#Parseval%27s-Theorem) yields

\begin{equation}
\frac{1}{T_\text{p}} \int_{0}^{T_\text{p}} |x(t)|^2 \; dt = \frac{1}{T_\text{p}} \sum_{\mu = - \infty}^{\infty} \left| X_0 \left( j \, \mu \frac{2 \pi}{T_\text{p}} \right) \right|^2
\end{equation}

The average power of a periodic signal can be calculated in the time-domain by integrating over the squared magnitude of one period or in the frequency domain by summing up the squared magnitude weights of the coefficients of the Dirac impulses of its Fourier transform.

### Fourier Transform of the Pulse Train

The [pulse train](https://en.wikipedia.org/wiki/Pulse_wave) is commonly used for power control using [pulse-width modulation (PWM)](https://en.wikipedia.org/wiki/Pulse-width_modulation). It is constructed from a periodic summation of a rectangular signal $x_0(t) = \text{rect} (\frac{t}{T} - \frac{T}{2})$

\begin{equation}
x(t) = \text{rect} \left( \frac{t}{T} - \frac{T}{2} \right) * \frac{1}{T_\text{p}} {\bot \!\! \bot \!\! \bot} \left( \frac{t}{T_\text{p}} \right)
\end{equation}

where $0 < T < T_\text{p}$ denotes the width of the pulse and $T_\text{p}$ its periodicity. Its usage for power control becomes evident when calculating the average power of the pulse train

\begin{equation}
P = \frac{1}{T_\text{p}} \int_{0}^{T_\text{p}} | x(t) |^2 dt = \frac{T}{T_\text{p}}
\end{equation}

The Fourier transform of one period $X_0(j \omega) = \mathcal{F} \{ x_0(t) \}$ is derived by applying the scaling and shift theorem of the Fourier transform to the [Fourier transform of the retangular signal](../fourier_transform/definition.ipynb#Transformation-of-the-Rectangular-Signal)

\begin{equation}
X_0(j \omega) = e^{-j \omega \frac{T}{2}} \cdot T \, \text{sinc} \left( \frac{\omega T}{2} \right)
\end{equation}

from which the spectrum of the pulse train follows by application of above formula for the Fourier transform of a periodic signal

\begin{equation}
X(j \omega) = 2 \pi \frac{1}{T_\text{p}} \sum_{\mu = - \infty}^{\infty} e^{-j \mu \pi \frac{T}{T_\text{p}}} \cdot T \, \text{sinc} \left( \mu \pi \frac{T}{T_\text{p}} \right) \cdot \delta \left( \omega - \mu \frac{2 \pi}{T_\text{p}} \right)
\end{equation}

The weights of the Dirac impulses are defined in `SymPy` for fixed values $T$ and $T_\text{p}$

```python
mu = sym.symbols('mu', integer=True)
T = 2
Tp = 5

X_mu = sym.exp(-sym.I * mu * sym.pi * T/Tp) * T * sym.sinc(mu * sym.pi * T/Tp)
X_mu
```

The weights of the Dirac impulses are plotted with [`matplotlib`](http://matplotlib.org/index.html), a Python plotting library. The library expects the values of the function to be plotted at a series of sampling points. In order to create these, the function [`sympy.lambdify`](http://docs.sympy.org/latest/modules/utilities/lambdify.html?highlight=lambdify#sympy.utilities.lambdify) is used which numerically evaluates a symbolic function at given sampling points. The resulting plot illustrates the positions and weights of the Dirac impulses.

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

Xn = sym.lambdify(mu, sym.Abs(X_mu), 'numpy')

n = np.arange(-15, 15)
plt.stem(n*2*np.pi/Tp, Xn(n))
plt.xlabel('$\omega$')
plt.ylabel('$|X(j \omega)|$');
```

**Exercise**

* Change the ratio $\frac{T}{T_\text{p}}$. How does the spectrum of the pulse train change?
* Can you derive the periodicity $T_\text{p}$ of the signal from its spectrum?
* Calculate the average power of the pulse train in the frequency domain by applying Parseval's theorem.

**Copyright**

The notebooks are provided as [Open Educational Resource](https://de.wikipedia.org/wiki/Open_Educational_Resources). Feel free to use the notebooks for your own educational 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: *Lecture Notes on Signals and Systems* by Sascha Spors.


# Problem:
Find the moviment equations of the 2 DoF Scara arm.

## Frames and Variables
Initialy, we need to define our reference frames and the symbolic variables.The first **Reference Frame** `B0`, is located in the same high as the arm and pointing in the rotating joint. The next frame,`B1`, is a **Moving Frame**. It follows the rotations of the first frame, therefora, related by a rotations of $\theta_1$ related to `B0`. Finally, the last frame rotates $\theta_2$ regarding `B1`.

```python
# Funções e Bibliotecas Utilizadas
from sympy import symbols, pprint, simplify, Eq, diff
from sympy.physics.mechanics import *
from sympy.physics.mechanics.functions import inertia
init_vprinting()

# Variáveis Simbólicas
theta_1, theta_2 = dynamicsymbols('theta_1 theta_2')
dtheta_1, dtheta_2 = dynamicsymbols('theta_1 theta_2', 1)
tau_1, tau_2 = symbols('tau_1 tau_2')
l_1, l_2 = symbols('l_1 l_2', positive = True)
r_1, r_2 = symbols('r_1 r_2', positive = True)
m_1, m_2, g = symbols('m_1 m_2 g')
I_1_zz, I_2_zz = symbols('I_{1zz}, I_{2zz}')

# Referenciais
B0 = ReferenceFrame('B0')                 # Referencial Inercial
B1 = ReferenceFrame('B1')
B1.orient(B0, 'Axis', [theta_1, B0.z])    # Referencial móvel: theta_1 em relação a B0.z
B2 = ReferenceFrame('B2')
B2.orient(B1, 'Axis', [theta_2, B1.z])    # Referencial móvel: theta_2 em relação a B1.z

```

## Points e Rigid Bodies
Now that the frames are defined, we can start to place the fixed points.The points `CM_1` and `CM_2` represent the **Center of Mass ** of the arm's links. Each of this links have their own moment, `I_1` e `I_2`. Furthermore, points O and A represent the fixed joints.

```python
# Pontos e Centros de Massa
O = Point('O')
O.set_vel(B0, 0)
A = Point('A')
A.set_pos(O, l_1 * B1.x)
A.v2pt_theory(O, B0, B1)
CM_1 = Point('CM_1')
CM_1.set_pos(O, r_1 * B1.x)
CM_1.v2pt_theory(O, B0, B1)
CM_2 = Point('CM_2')
CM_2.set_pos(A, r_2 * B2.x)
CM_2.v2pt_theory(O, B0, B2)

# Corpos Rígidos
I_1 = inertia(B1, 0, 0, I_1_zz)
E_1 = RigidBody('Elo_1', CM_1, B1, m_1, (I_1, CM_1)) # Elo 1
I_2 = inertia(B1, 0, 0, I_1_zz)
E_2 = RigidBody('Elo_2', CM_2, B2, m_2, (I_2, CM_2)) # Elo 2
```

 ## Potencial Energy and Generalized Forces
**Sympy** needs explicit definition of external potencial energies in the beodies.As the arm is only acting in the $(x,y)$ plane, the gravity force will not perfom any influence in the moviment equations.

```python
# Energia Potencial
P_1 = -m_1 * g * B0.z
r_1_CM = CM_1.pos_from(O).express(B0)
E_1.potential_energy = r_1_CM.dot(P_1)
P_2 = -m_2 * g * B0.z
r_2_CM = CM_2.pos_from(O).express(B0).simplify()
E_2.potential_energy = r_2_CM.dot(P_2)

# Forças/Momentos Generalizados
FL = [(B1, tau_1 * B0.z),(B2, tau_2 * B0.z)]

(E_2.potential_energy, E_1.potential_energy)
```

## Lagrangian e Moviment Equations
Finally, we can solve the langrangian and movimento equations of the system.In **Sympy**, we call the `Lagrangian` method andthe  `LagrangesMethod` class to solve the system.

```python
# Método de Lagrange
L = Lagrangian(B0, E_1, E_2)
L = L.simplify()
LM = LagrangesMethod(L, [theta_1, theta_2], frame=B0, forcelist=FL)

# Equações do Movimento
L_eq = LM.form_lagranges_equations()
L_eq
```

We still can decouple the **Moviment Equations** using the `rhs` method on the previous class.

```python
# Equações Prontas para Solução Numérica
rhs = LM.rhs()
rhs
```

```python

```


# Exercise session nº 2
---
# Sonic Hedgehog Signaling Gradient Readout in the Vertebrate Neural Tube

__*Sacha Ichbiah, 31/01/22, ENS Paris*__

This subject is extracted from :
> N. Balaskas et Al., *Gene Regulatory Logic for Reading the Sonic Hedgehog Signaling Gradient in the Vertebrate Neural Tube*, Cell, 2012.
> https://doi.org/10.1016/j.cell.2011.10.047

Secreted signals, known as morphogens, provide the positional information that organizes gene expression and cellular differentiation in many developing tissues. Several morphogens gradients can be observed on biological systems during development. However, the way the cells integrate the signal is often complicated and organism dependent. A simple model introduced by __Lewis Wolpert__, called the __French flag model__, postulates that the different cell fates adopted is the result of the crossing of thresholds of morphogens concentrations.

In the vertebrate neural tube, Sonic Hedgehog (Shh) acts as a morphogen to control the pattern of neuronal subtype specification. However, it was not clear how Shh gradient were interpreted by the cell to induce different cell fates. This article shows that a spatially and temporally changing gradient of Shh signaling is interpreted by the regulatory logic of a downstream transcriptional network. The design of the network, which links three transcription factors to Shh signaling, is responsible for differential spatial and temporal gene expression. In addition, the network renders cells insensitive to fluctuations in signaling and confers hysteresis - memory of the signal. The morphogen interpretation is an emergent property of the architecture of a transcriptional network that provides robustness and reliability to tissue patterning.

During this session, we will model the gene regulation network using differential equations and observe the behaviour induced by the architecture of this network.

---

## I - Temporal evolution of the GRN :

The relations between these proteins can be modeled as such :

$
\begin{align}
\frac{dP}{dt} &=\frac{\alpha}{1 + (\frac{N}{N_{critP}})^{h_1} +  (\frac{O}{O_{critP}})^{h_2}  } - k_1P \newline
\frac{dO}{dt} &=\frac{\beta G}{1 + G} \frac{1}{1 + (\frac{N}{N_{critO}})^{h_3}  } - k_2O \newline
\frac{dN}{dt} &=\frac{\gamma G}{1 + G} \frac{1}{1 + (\frac{O}{O_{critN}})^{h_4} + (\frac{P}{P_{critN}})^{h_5}  } - k_3N \newline
\end{align}
$

#### **Question 1 :**
> Discretize these differential equations with a forward euler-scheme

#### **Question 2 :**
> Integrate these equations for the given parameters for $t \in [0,50]$, starting with $P(0)=3, O(0)=N(0)=0$. Try with G $\in [1,2,3,4,5]$ What do you observe ?

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

alpha = 3
beta = 5
gamma = 5
h1 = 6
h2 = 2
h3 = 5
h4 = 1
h5 = 1
k1 = 1
k2 = 1
k3 = 1
OcritP = 1
NcritP = 1
NcritO = 1
PcritN = 1

tfinal = 50
npoints = 1000
timepoints = np.linspace(0,tfinal,npoints)
```

## II - Phase diagram of the GRN :
#### **Question 3 :**
> Plot the phase diagram of the steady states values of P,O and D for G $\in G_{vals}$. Then plot the phase diagram for $\alpha = 0, \beta=0 \text{ and } (\alpha,\beta)=0$

```python
n_gvals = 100
Gvals = np.linspace(0,5,n_gvals)
```

## III - Noise buffering by the GRN :
#### **Question 4 :**
> Compare the temporal evolution with $G = 5$ to the one with $G \mapsto \mathcal{N}(5,1)$, a normal law of mean 5 and of variance 1.  What are the effects of the noise on the proteins concentrations ?

```python
alpha = 3
beta = 5
gamma = 5
h1 = 6
h2 = 2
h3 = 5
h4 = 1
h5 = 1
k1 = 1
k2 = 1
k3 = 1
OcritP = 1
NcritP = 1
NcritO = 1
PcritN = 1
G_mean = 5
G_std = 1
```

## IV - Hysteresis
#### **Question 5 :**
> From the steady state at G = 0, make G evolve slowly such that the steady state is reached at each variation of G. Then when G = 5, make G decrease back to zero. what do you observe ?

# Conclusion

The proposed model provides evidence that Shh morphogen interpretation in the neural tube is a property of the downstream GNN. Cells transform the extracellular gradient of Shh into a dynamic profile of intracellular Gli activity that engages a transcriptional circuit, the regulatory logic of which is responsible for the generation of the characteristic temporal and spatial patterns of gene expression. This mechanism offers a powerful strategy to achieve the characteristic precision and robustness of morphogen-mediated pattern formation.

In the following of the article, the authors introduce another model, extending the preceding one by taking into account a forth protein, Gli. It allows them to reproduce the behaviours observed in their in-vivo experiments. On top of explaining the variable spatial patterns of gene expression, the networks confers both robustness and hysteresis of protein production. The insensitivity of the circuit to transient changes in the level of signaling provides a way to achieve reliable patterning in spite of the inherent noisiness of development. The study highlights the information-processing power of transcriptional networks and the simplicity and adaptability of this mechanism suggest that it is likely to be relevant for the control of patterning of tissues other than the neural tube.

The gene regulatory networks encountered in biological systems are often too complicated to construct them heuristically. Eric H. Davidson pionnered the use of bioinformatical tools to automatically infer gene regulation networks from genomic and transcriptomic data. It gives an engineer view of the genes relationships during developments, showing the complexity of the mechanisms involved.

> Gene regulation network for endomesoderm specification made with biotapestry : http://www.biotapestry.org. Don't try to understand this graph !

_Additional references :_
> Michael Akam, *Making stripes ineleganty*, Nature, 1989. https://doi.org/10.1038/341282a0

> Eric H. Davidson, *Emerging properties of animal gene regulatory networks*, Nature, 2010. https://doi.org/10.1038/nature09645

> Eric H. Davidson, Samuel Levine *Properties of developmental gene regulatory networks*, PNAS, 2008 https://doi.org/10.1073/pnas.0806007105

> Lewis Wolpert, *Positional information and the spatial pattern of cellular differentiation*, Journal of Theoretical Biology, 1969. https://doi.org/10.1016/S0022-5193(69)80016-0


# 表达式操作

符号操作系统最有用的特征之一是对表达式的展开/化简等简化数学表达式的能力.SymPy有几十个功能来执行各种简化.

还有一个名为`simplify()`的通用函数可以以智能方式应用所有这些函数,以获得最简单形式的表达式.这里有些例子

```python
from sympy import init_printing
init_printing(use_unicode=True)
```

## 通用的启发式化简`simplify()`

这个函数可以隐式的化简数学表达式

```python
from sympy import symbols,simplify,sin,cos,gamma

x, y, z = symbols('x y z')
```

```python
simplify(sin(x)**2 + cos(x)**2)
```

```python
simplify((x**3 + x**2 - x - 1)/(x**2 + 2*x + 1))
```

```python
simplify(gamma(x)/gamma(x - 2))
```

**ps: gamma(x)是伽马函数 $\Gamma x$**

需要注意的是:

我们看到`simplify()`能够处理大多数表达式,但是`simplify()`有一个缺陷--它只是应用SymPy中的所有主要的简化操作,并隐式的确定最简单的结果.这个`最简单`不是一个定义明确的术语.因此有时候我们还是得手动操作

例如，假设我们想简化$ x^2+ 2x+1 $到$(x + 1)^2$,但实际上结果却不对

```python
simplify(x**2 + 2*x + 1)
```

我们没有得到我们想要的.有一个函数来执行这个简化,称为`factor()`因式分解.

```python
from sympy import factor
```

```python
factor(x**2 + 2*x + 1)
```

此外simplify函数速度比较慢,因此,条件允许的话最好使用特定的函数处理简化问题而不是使用通用的`simplify()`函数.

除上面的优点外,使用特定函数简化还有以下优点:

+ 特定函数对其输出的形式具有明确形式保证

    例如:当对具有有理系数的多项式进行调用时,`factor()`保证将多项式因子代入不可约因子;`simplify()`没有这一保证.

+ 在交互式使用时,当你只想将表达式简化为更简单的形式时,`simplify()`是个好选择.但如果要更加精确的结果,simplify就不合适了

## 多项式/有理函数的展开和因式分解

***将几个多项式的积构建成变量的每一次都只有一个系数的形式***被称为多项式的展开;

***把一个多项式在一个范围(如实数范围内分解,即所有项均为实数)化为几个整式的积的形式***被称作这个多项式的因式分解;

SymPy支持这些操作

### `expand()`函数用来展开多项式

```python
from sympy import expand
expand((x + 1)**2)
```

```python
expand((x + 2)*(x - 3))
```

### `factor()`函数用来将多项式因式分解

```python
from sympy import factor
factor(expand(x**2 + 2*x + 1))
```

### `collect()`函数聚合表达式中公因子

`collect()`可以将多元多项式中的一个指定变量作为变量,其他作为常量

```python
from sympy import collect
expr = x*y + x - 3 + 2*x**2 - z*x**2 + x**3
expr
```

```python
collected_expr = collect(expr, x)
collected_expr
```

### `cancel()`将取任何有理函数,并将其放入标准规范形式$ \frac p q$

其中`p`和`q`是没有公因子的扩展多项式,`p`和`q`的前导系数没有分母(即是整数)

```python
from sympy import cancel
expr = (x**2 + 2*x + 1)/(x**2 + x)
expr
```

```python
cancel(expr)
```

```python
expr = 1/x + (3*x/2 - 2)/(x - 4)
expr
```

```python
cancel(expr)
```

```python
expr = (x*y**2 - 2*x*y*z + x*z**2 + y**2 - 2*y*z + z**2)/(x**2 - 1)
expr
```

```python
cancel(expr)
```

需要注意的是cancel不会自己因式分解,依然需要使用factor函数来做

```python
factor(expr)
```

### `apart()`对有理函数执行部分分数分解

```python
from sympy import apart
```

```python
expr = (4*x**3 + 21*x**2 + 10*x + 12)/(x**4 + 5*x**3 + 5*x**2 + 4*x)
expr
```

```python
apart(expr)
```

## 三角函数分解

### 反三角函数

SymPy遵循Python对反三角函数的命名约定,即将一个a附加到函数名称的前面.例如:反余弦或反余弦称为`acos()`

```python
from sympy import sin,cos,tan,sec,asin,acos,atan,cosh,sinh,tanh
```

### `trigsimp()`使用三角标识简化表达式

这个方法它同样可以对双曲三角函数有效

```python
from sympy import trigsimp
```

```python
expr = sin(x)**2 + cos(x)**2
expr
```

```python
trigsimp(expr)
```

```python
expr = sin(x)**4 - 2*cos(x)**2*sin(x)**2 + cos(x)**4
expr
```

```python
trigsimp(expr)
```

```python
expr = sin(x)*tan(x)/sec(x)
expr
```

```python
trigsimp(expr)
```

```python
expr = cosh(x)**2 + sinh(x)**2
expr
```

```python
trigsimp(expr)
```

```python
expr = sinh(x)/tanh(x)
expr
```

```python
trigsimp(expr)
```

### `expand_trig()`展开三角函数

```python
from sympy import expand_trig
```

```python
expr = sin(x + y)
expr
```

```python
expand_trig(expr)
```

```python
expr = tan(2*x)
expr
```

```python
expand_trig(expr)
```

## 指数简化

### `powsimp()`从上到下,从左到右的合并指数

```python
from sympy import symbols,powsimp,sqrt
```

```python
a, b = symbols('a b', real=True)
z, t, c, n,m,k = symbols('z t c n m k')
```

```python
expr = x**a*x**b
expr
```

```python
powsimp(expr)
```

```python
expr = x**a*y**a
expr
```

```python
powsimp(expr)
```

可以使用参数force=true将底数合并

```python
expr = t**c*z**c
expr
```

```python
powsimp(expr)
```

```python
powsimp(expr,force=True)
```

指数合并同样可以用在开根上,毕竟$ \sqrt 2 = 2^{\frac 1 2}$

```python
(z*t)**2
```

```python
sqrt(x*y)
```

### `expand_power_exp()` 和 `expand_power_base()` 展开指数或者底

```python
from sympy import expand_power_exp,expand_power_base
```

```python
expr = x**(a + b)
expr
```

```python
expand_power_exp(expr)
```

```python
expr = (x*y)**a
expr
```

```python
expand_power_base(expr)
```

### `powdenest()`将指数叠高

```python
from sympy import powdenest
```

```python
expr = (x**a)**b
expr
```

```python
powdenest(expr)
```

```python
expr = (z**a)**b
expr
```

```python
powdenest(expr)
```

```python
powdenest(expr, force=True)
```

## 对数分解

### `expand_log()` 对数展开

```python
from sympy import expand_log,log
```

```python
expand_log(log(x*y))
```

```python
expand_log(log(x/y))
```

```python
expand_log(log(x**2))
```

```python
expand_log(log(x**n))
```

```python
expand_log(log(z*t))
```

它同样可以使用force=True参数

```python
expand_log(log(z**2))
```

```python
expand_log(log(z**2), force=True)
```

### `logcombine()`将对数求和整合

```python
from sympy import logcombine
```

```python
logcombine(log(x) + log(y))
```

```python
logcombine(n*log(x))
```

```python
logcombine(n*log(z))
```

同样也可以用force=True

```python
logcombine(n*log(z), force=True)
```

## 特殊函数

### 阶乘

```python
from sympy import factorial
```

```python
factorial(n)
```

### 二项式系数函数 binomial coefficient

```python
from sympy import binomial
```

```python
binomial(n, k)
```

### 伽马函数

阶乘函数与伽马函数伽马密切相关.

对于正整数z,$(z-1)!$与伽马函数$ \Gamma (z)= \int_0^\infty t^{z−1}e^{−t}dt$相同

```python
from sympy import gamma
```

```python
gamma(z)
```

### `hyper([a_1, ..., a_p], [b_1, ..., b_q], z)`广义超几何函数和其展开`hyperexpand()`

```python
from sympy import hyper,hyperexpand,meijerg
```

```python
hyper([1, 2], [3], z)
```

$${{}_{2}F_{1}\left(\begin{matrix} 1, 2 \\ 3 \end{matrix}\middle| {z} \right)}$$

```python
hyperexpand(hyper([1, 2], [3], z))
```

`hyperexpand()`也适用于更通用的`Meijer G`函数

```python
expr = meijerg([[1],[1]], [[1],[]], -z)
expr
```

$${G_{2, 1}^{1, 1}\left(\begin{matrix} 1 & 1 \\1 &  \end{matrix} \middle| {- z} \right)}$$

```python
hyperexpand(expr)
```

### rewrite方法

处理特殊功能的常见方法是将它们彼此重写.这适用于SymPy中的任何函数,而不只是特殊函数.要根据函数重写表达式,这有点像静态类型语言中的强制类型转换的意思.一个简单的例子就是三角函数中`tan(x)`可以转化为`sin(x)`的表达式

```python
tan(x).rewrite(sin)
```

```python
factorial(x).rewrite(gamma)
```

### expand_func函数

要根据某些标识扩展特殊函数可以使用`expand_func`方法

```python
from sympy import expand_func
```

```python
expand_func(gamma(x + 3))
```

### `combsimp()`简化组合表达式

```python
from sympy import combsimp
```

```python
combsimp(factorial(n)/factorial(n - 3))
```

```python
combsimp(binomial(n+1, k+1)/binomial(n, k))
```

```python
combsimp(gamma(x)*gamma(1 - x))
```

## 例子: 连续分数化简

$ a_0 + \frac 1 {a_1 + \frac 1 {a_2 + \frac 1 {... + \frac 1 a_n}}}$

```python
from sympy import Integer
```

这是一个典型的递归函数,我们可以如此定义

```python
def list_to_frac(l):
    expr = Integer(0)
    for i in reversed(l[1:]):
        expr += i
        expr = 1/expr
    return l[0] + expr
```

```python
list_to_frac([x, y, z])
```

```python
list_to_frac([1, 2, 3, 4])
```

每个有限连续分数是一个有理数,但我们对符号感兴趣,所以让我们创建一个符号的连续子符号.我们使用的symbols()函数来创建这些符号

```python
syms = symbols('a0:5')
```

```python
frac = list_to_frac(syms)
```

```python
frac
```

这种形式对于理解连续的分数是有用的,可以更方便的编程,但我们需要将其化简为标准的有理函数形式.

```python
frac = cancel(frac)
frac
```

假设我们知道它可以重写为连续分数.

我们如何用SymPy做到这一点?

连续分数递归地为$c + \frac 1 f$,其中c是整数,f是(较小的)连续分数.

如果我们可以用这种形式写出表达式,我们可以递归地拉出每个c,并将它添加到列表中.
然后我们可以使用我们的`list_to_frac()`函数获得连续分数

```python
a0,a1,a2,a3,a4 = syms
```

```python
l = []
frac = apart(frac, a0)
frac
```

```python
l.append(a0)
frac = 1/(frac - a0)
frac
```

我们可以重复这一过程

```python
frac = apart(frac, a1)
frac
```

```python
l.append(a1)
frac = 1/(frac - a1)
frac = apart(frac, a2)
frac
```

```python
l.append(a2)
frac = 1/(frac - a2)
frac = apart(frac, a3)
frac
```

```python
l.append(a3)
frac = 1/(frac - a3)
frac = apart(frac, a4)
frac
```

```python
l.append(a4)
list_to_frac(l)
```


# Solving Differential Equations

This notebook is less a project for you to work on, much rather a show case of multiple packages, which allow you to solve partial differential equations in Julia. Based on "classic" example in the respective fields I invite you to start playing by yourself. Resources for the respective packages are given.

## Using ModelingToolkit and OrdinaryDiffEq to solve an ODE system

Consider the Lorenz attractor:

$$
\begin{align}
    \dot{x} &= \sigma y - \sigma x\\
    \dot{y} &= \rho x - xz - y\\
    \dot{z} &= xy - \beta z
\end{align}
$$

```julia
using ModelingToolkit
using OrdinaryDiffEq
using Plots

# Define the system to be solved
@parameters t σ ρ β
@variables x(t) y(t) z(t)
D = Differential(t)

eqs = [D(x) ~ σ*y - σ*x,
       D(y) ~ x*ρ - x*z - y,
       D(z) ~ x*y - β*z]
@named lorenz = ODESystem(eqs)

# Define the inital conditions
u0 = [x => 1.0,
      y => 1.0,
      z => 1.0]

# Define the values of the parameters
p  = [σ => 10.0,
      ρ => 28.0,
      β => 8/3]

# Timespan for propagation
tspan = (0.0, 30.0)

# Solve it
prob  = ODEProblem(lorenz, u0, tspan, p, jac=true)
sol   = OrdinaryDiffEq.solve(prob, Tsit5())

# and plot solution
plot(sol, vars=(x,y))
```

##### More details
- https://github.com/SciML/OrdinaryDiffEq.jl
- https://github.com/SciML/DifferentialEquations.jl
- https://github.com/SciML/ModelingToolkit.jl

## Solving PDEs using the finite-element method

Classic example:

$$
\begin{align}
    - \Delta u &= f &&\text{in $\Omega$} \\
    u &= g &&\text{on $\partial \Omega$}
\end{align}
$$

with $f(x) = 1.0, g(x) = 2.0$

```julia
using Gridap

# Create cartesian mesh of 10 regularly-spaced cells
L = 1.0
domain = (0.0, L, 0.0, L, 0.0, L)
n = 10
partition = (n, n, n)
model = CartesianDiscreteModel(domain, partition)

# Dirichlet BCs everywhere
labels = get_face_labeling(model)
add_tag_from_tags!(labels, "dirichlet", collect(1:8))

# Test space built from linear Lagrangian elements
order = 1
reffe = ReferenceFE(lagrangian, Float64, order)
V0    = TestFESpace(model, reffe; conformity=:H1, dirichlet_tags="dirichlet")

# Trial space
g(x) = 2.0
Ug = TrialFESpace(V0, g)

# Integration
degree = 2
Ω  = Triangulation(model)
dΩ = Measure(Ω, degree)

# Weak form
f(x)    = 1.0
h(x)    = 3.0
a(u, v) = ∫( ∇(v)⋅∇(u) )*dΩ
b(v)    = ∫( v*f )*dΩ

# Build and solve FE problem
op = AffineFEOperator(a, b, Ug, V0)
ls = LUSolver()
solver = LinearFESolver(ls)
uh = Gridap.solve(solver, op)

# Write to VTK, to be able to look at the function from Paraview
writevtk(Ω, "results", cellfields=["uh" => uh])
```

##### More details
- https://github.com/gridap/Gridap.jl
- https://gridap.github.io/Gridap.jl/dev/

## Solving PDEs using plane-wave discretisations

Consider solving the Gross-Pitaevskii equation

$$
H ψ = \left(-\frac12 Δ + V + 2 C |ψ|^2\right) ψ = μ ψ \qquad \|ψ\|_{L^2} = 1
$$

with $C = 1$ in a periodic 1D "box" of length $10$ with confining potential $V(x) = (x - 5)^2$.

This is a common mean-field model for Bose-Einstein condensates, where $\psi$ is the quantum-mechanical state of condensation and $\rho = |\psi|^2$ is the particle density.

```julia
using DFTK
using Plots

# Periodic boundary condition ... 1D problem
lattice = 10 .* [[1 0 0.];
                 [0 0 0.];
                 [0 0 0.]]

# Setup the model to be solved
V(x) = (x - 5)^2
C = 1
terms = [Kinetic(),                         # -½Δ (Kinetic energy operator in quantum physics)
         ExternalFromReal(r -> V(r[1])),
         LocalNonlinearity(ρ -> 2C * ρ^2),  # 2 * |ψ|^2
]
model = Model(lattice; n_electrons=1, terms=terms,
              spin_polarization=:spinless)  # use "spinless electrons"

# Discretise problem in a plane-wave basis
basis = PlaneWaveBasis(model; Ecut=500, kgrid=(1, 1, 1))
```

```julia
# Solve it using a constrained preconditioned LBFGS:
scfres = direct_minimization(basis, tol=1e-8);
```

```julia
# Plot the resulting particle density |ψ|^2
x = 10vec(first.(DFTK.r_vectors(basis)))
plot(x, scfres.ρ[:, 1, 1], label="ρ")
plot!(x, V.(x) / 10, label="V")
```

##### More details
- https://dftk.org
- https://docs.dftk.org


```python
%matplotlib inline
import matplotlib.pyplot as plt
import scipy.integrate
import pandas as pd
import numpy as np
import sympy
```

$\frac{dn_{H}}{dt} = k_2n_{H^+}n_{e^-} - k_1n_{H}$

$\frac{dn_{H^+}}{dt} = k_1n_{H} - k_2n_{H^+}n_{e^-}$

```python
k1, k2, nHI, nHII, ne = sympy.sympify('k1, k2, nHI, nHII, ne')
```

```python
eq1 = k2 * nHII * ne - k1 * nHI
eq2 = k1 * nHI - k2*nHII*ne
```

```python
sympy.solve([eq1, eq2], [nHI])
```

    {nHI: k2*nHII*ne/k1}

$\frac{dn_{HeI}}{dt} = k_6 n_{HeII} n_{e} - k_3 n_{HeI}$

$\frac{dn_{HeII}}{dt} = k_3 n_{HeI} + k_5 n_{HeIII} n_{e} - k_4 n_{HeII} - k_6 n_{HeII} n_{e}$

$\frac{dn_{HeIII}}{dt} = k_4 n_{HeII} - k_5 n_{HeIII} n_{e}$

```python

```


```python
from sympy import *
from sympy.abc import x
init_printing(use_unicode=True)

```

```python
F, m, a, = symbols("F m a")

newtons_2_law = Eq(F, m*a)

display(newtons_2_law)
```

```python
f  = Function("f")(x)
f_ = Derivative(f, x)
f_
```

```python
C = symbols("C")
Eq(ln(abs(y)), (-x**2/2+C))
```

```python
f = Function("f")(x)
g = Function("g")(x)
y_ = Derivative(y, x)
eq1 = Eq(y_, f*g)
display(eq1)
```

```python
(1/-y).diff(y)
```

```python
t, lam = symbols('t lambda')
y = Function('y')(t)
dydt = y.diff(t)
expr = Eq(dydt, -lam*y)
display(expr)
dsolve(expr)
```

```python
# eq4 = Eq(Derivative(y), 4*x*y)
# display(eq4)
# dsolve(eq4)

```

```python
y = Function('y')(x)
dydx = y.diff(x)
eq3 = Eq(dydx, y)
display(eq3)
dsolve(eq3)
```

```python
y = Function('y')
der = Derivative(y, x)
der
```

```python
0
```

```python

```