# ***Introduction to Radar Using Python and MATLAB***
## Andy Harrison - Copyright (C) 2019 Artech House
<br/>

# Frank Code Ambiguity Function
***

Referring to Section 8.7.2,  Frank codes are polyphase codes representing a quadratic phase shift in a discrete manner.  To generate a Frank code, a single pulse is divided into $N$ groups.  Next, each group is divided into $N$ subpulses.  Therefore, the length of Frank codes are perfect squares, $L = N^2$.  Frank codes may be represented as a matrix of the form (Equation 8.72)

\begin{equation}
F_N = \frac{2\pi}{N}
\begin{bmatrix}
    0 & 0 & 0 & \dots  & 0 \\
    0 & 1 & 2 & \dots  & N-1 \\
    0 & 2 & 4 & \dots  & 2(N-1) \\
    \vdots & \vdots & \vdots & \ddots & \vdots \\
    0 & N-1 & 2(N-1) & \dots  & (N-1)^2
\end{bmatrix}\hspace{0.5in} \text{(rad)},
\end{equation}

where each row is the phase shift associated with the subpulses in that group.  Frank codes may also be expressed more compactly as (Equation 8.73)

\begin{equation}
    F_N = \frac{2\pi}{N}(m - 1)(n - 1) \hspace{0.2in} \text{for} \hspace{0.2in} m,n = 1 \dots N \hspace{0.5in} \text{(rad)}.
\end{equation}

For example, the Frank code for $N=3$ is (Equation 8.74)

\begin{equation}
    F_3 = \frac{2\pi}{3}[\;0\;\; 0\;\;  0\;\; 0\;\; 1\;\; 2\;\; 0\;\; 2\;\; 4\;] = \left[\;0\;\; 0\;\;  0\;\; 0\;\; \frac{2\pi}{3}\;\; \frac{4\pi}{3}\;\; 0\;\; \frac{4\pi}{3}\;\; \frac{2\pi}{3}\;\right].
\end{equation}

For phase shifts larger than $2\pi$, the phase wraps and the modulo with $2\pi$ is used.  The phase shift associated with each subpulse for Frank code $F_3$ is illustrated in Figure 8.21.

The ambiguity function of Frank code waveforms is similar to that of LFM waveforms.  This is expected as the Frank code is a discrete version of a quadratic phase shift.  For very long Frank codes, the phase shift between subpulses becomes small.  This can be problematic as phase instability of the system degrades the phase shift associated with each subpulse and reduces the performance of the waveform.  Other discrete polyphase waveforms have been developed to approximate quadratic phase shifts.
***

Begin by getting the library path

```python
import lib_path
```

Set the N-phase code and the chip width (s)

```python
n_phase = 3

chip_width = 0.1
```

Generate the N-phase Frank code

```python
from Libs.ambiguity.frank_code import n_phase_code

code = n_phase_code(n_phase)
```

Calculate the ambiguity function for the Frank coded waveform

```python
from Libs.ambiguity.ambiguity_function import phase_coded_wf

ambiguity, time_delay, doppler_frequency = phase_coded_wf(code, chip_width)
```

Display the zero-Doppler cut, the zero-range cut, and the 2D contour plot using the `matplotlib` routines

```python
from matplotlib import pyplot as plt

# Set the figure size

plt.rcParams["figure.figsize"] = (15, 10)

# Plot the ambiguity function

plt.plot(time_delay, ambiguity[round(len(doppler_frequency) / 2)], '')

# Set the time axis limits

plt.xlim(-len(code) * chip_width, len(code) * chip_width)

# Set the x and y axis labels

plt.xlabel("Time (s)", size=12)

plt.ylabel("Relative Amplitude", size=12)

# Turn on the grid

plt.grid(linestyle=':', linewidth=0.5)

# Set the plot title and labels

plt.title('Frank Code Ambiguity Function', size=14)

# Set the tick label size

plt.tick_params(labelsize=12)
```

Create the zero-range cut

```python
 # Plot the ambiguity function

plt.plot(doppler_frequency,  ambiguity[:, round(len(time_delay) / 2)], '')

# Set the x and y axis labels

plt.xlabel("Doppler (Hz)", size=12)

plt.ylabel("Relative Amplitude", size=12)

# Turn on the grid

plt.grid(linestyle=':', linewidth=0.5)

# Set the plot title and labels

plt.title('Frank Code Ambiguity Function', size=14)

# Set the tick label size

plt.tick_params(labelsize=12)
```

Create the two-dimensional contour plot

```python
from numpy import meshgrid

# Create the grid

t, f = meshgrid(time_delay, doppler_frequency)

# Plot the ambiguity function

plt.contour(t, f, ambiguity, 30, cmap='jet', vmin=-0.2, vmax=1.0)

# Set the time axis limits

plt.xlim(-len(code) * chip_width, len(code) * chip_width)

# Set the x and y axis labels

plt.xlabel("Time (s)", size=12)

plt.ylabel("Doppler (Hz)", size=12)

# Turn on the grid

plt.grid(linestyle=':', linewidth=0.5)

# Set the plot title and labels

plt.title('Frank Code Ambiguity Function', size=14)

# Set the tick label size

plt.tick_params(labelsize=12)
```

```python

```


# Insant Sympy Starter

import sympy as sy
from sympy import init_printing
init_printing()

```python
x,y,z=sy.symbols('x y z')
```

```python
(sy.exp(x)/sy.factorial(y))**z
```

By this point, you should have a working environment ready to make full use of Sympy. You are encouraged to play around in the notebook and discover more.

```python

```


# Homework 1
*This notebook includes both coding and written questions. Please hand in this notebook file with all the outputs and your answers to the written questions.*

This assignment covers linear filters, convolution and correlation.

```python
# Setup
from __future__ import print_function
import numpy as np
import matplotlib.pyplot as plt
from time import time
from skimage import io

%matplotlib inline
plt.rcParams['figure.figsize'] = (10.0, 8.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'

# for auto-reloading extenrnal modules
%load_ext autoreload
%autoreload 2
```

    The autoreload extension is already loaded. To reload it, use:
      %reload_ext autoreload

## Part 1: Convolutions
### 1.1 Commutative Property (5 points)
Recall that the convolution of an image $f:\mathbb{R}^2\rightarrow \mathbb{R}$ and a kernel $h:\mathbb{R}^2\rightarrow\mathbb{R}$ is defined as follows:
$$(f*h)[m,n]=\sum_{i=-\infty}^\infty\sum_{j=-\infty}^\infty f[i,j]\cdot h[m-i,n-j]$$

Or equivalently,
\begin{align}
(f*h)[m,n] &= \sum_{i=-\infty}^\infty\sum_{j=-\infty}^\infty h[i,j]\cdot f[m-i,n-j]\\
&= (h*f)[m,n]
\end{align}

Show that this is true (i.e. prove that the convolution operator is commutative: $f*h = h*f$).

**Your Answer:** *Write your solution in this markdown cell. Please write your equations in [LaTex equations](http://jupyter-notebook.readthedocs.io/en/latest/examples/Notebook/Typesetting%20Equations.html).*

### 1.2 Shift Invariance (5 points)
Let $f$ be a function $\mathbb{R}^2\rightarrow\mathbb{R}$. Consider a system $f\xrightarrow{s}g$, where $g=(f*h)$ with some kernel $h:\mathbb{R}^2\rightarrow\mathbb{R}$. Also consider functions $f'(m,n) = f(m-m_0, n-n_0)$ and $g'(m,n) = g(m-m_0, n-n_0)$.

Show that $S$ defined by any kernel $h$ is a Linear Shift Invariant (LSI) system by showing that $g' = (f'*h)$.

**Your Answer:** *Write your solution in this markdown cell. Please write your equations in [LaTex equations](http://jupyter-notebook.readthedocs.io/en/latest/examples/Notebook/Typesetting%20Equations.html).*

### 1.3 Linear Invariance (10 points)

Recall that a system S is considered a linear system if and only if it satisfies the superposition property. In mathematical terms, a (function) S is a Linear Invariant system iff it satisfies:

$S[\alpha f_i[n,m] + \beta f_j[k,l]] = \alpha S[f_i[n,m]] + \beta S[f_j[k,l]]$

Let $f_i$ and $f_j$ be functions $\mathbb{R}^2\rightarrow\mathbb{R}$. Consider a system $f\xrightarrow{s}g$, where $g=(f*h)$ with some kernel $h:\mathbb{R}^2\rightarrow\mathbb{R}$.

Show that $S$ defined by any kernel $h$ is a Linear Invariant (LSI) system by showing that the superposition property holds for S.

**Your Answer:** *Write your solution in this markdown cell. Please write your equations in [LaTex equations](http://jupyter-notebook.readthedocs.io/en/latest/examples/Notebook/Typesetting%20Equations.html).*

### 1.4 Implementation (30 points)

In this section, you will implement two versions of convolution:
- `conv_nested`
- `conv_fast`

First, run the code cell below to load the image to work with.

```python
# Open image as grayscale
img = io.imread('dog.jpg', as_gray=True)

# Show image
plt.imshow(img)
plt.axis('off')
plt.title("Isn't he cute?")
plt.show()
```

Now, implement the function **`conv_nested`** in **`filters.py`**. This is a naive implementation of convolution which uses 4 nested for-loops. It takes an image $f$ and a kernel $h$ as inputs and outputs the convolved image $(f*h)$ that has the same shape as the input image. This implementation should take a few seconds to run.

*- Hint: It may be easier to implement $(h*f)$*

We'll first test your `conv_nested` function on a simple input.

```python
from filters import conv_nested

# Simple convolution kernel.
kernel = np.array(
[
    [1,0,1],
    [0,0,0],
    [1,0,0]
])

print(kernel[0, 2])

# Create a test image: a white square in the middle
test_img = np.zeros((9, 9))
test_img[3:6, 3:6] = 1

# Run your conv_nested function on the test image
test_output = conv_nested(test_img, kernel)

# Build the expected output
expected_output = np.zeros((9, 9))
expected_output[2:7, 2:7] = 1
expected_output[5:, 5:] = 0
expected_output[4, 2:5] = 2
expected_output[2:5, 4] = 2
expected_output[4, 4] = 3

# Plot the test image
plt.subplot(1,3,1)
plt.imshow(test_img)
plt.title('Test image')
plt.axis('off')

# Plot your convolved image
plt.subplot(1,3,2)
plt.imshow(test_output)
plt.title('Convolution')
plt.axis('off')

# Plot the exepected output
plt.subplot(1,3,3)
plt.imshow(expected_output)
plt.title('Exepected output')
plt.axis('off')
plt.show()

# Test if the output matches expected output
assert np.max(test_output - expected_output) < 1e-10, "Your solution is not correct."
```

Now let's test your `conv_nested` function on a real image.

```python
from filters import conv_nested

# Simple convolution kernel.
# Feel free to change the kernel to see different outputs.
kernel = np.array(
[
    [1,0,-1],
    [2,0,-2],
    [1,0,-1]
])

out = conv_nested(img, kernel)

# Plot original image
plt.subplot(2,2,1)
plt.imshow(img)
plt.title('Original')
plt.axis('off')

# Plot your convolved image
plt.subplot(2,2,3)
plt.imshow(out)
plt.title('Convolution')
plt.axis('off')

# Plot what you should get
solution_img = io.imread('convoluted_dog.jpg', as_gray=True)
plt.subplot(2,2,4)
plt.imshow(solution_img)
plt.title('What you should get')
plt.axis('off')

plt.show()
```

Let us implement a more efficient version of convolution using array operations in numpy. As shown in the lecture, a convolution can be considered as a sliding window that computes sum of the pixel values weighted by the flipped kernel. The faster version will i) zero-pad an image, ii) flip the kernel horizontally and vertically, and iii) compute weighted sum of the neighborhood at each pixel.

First, implement the function **`zero_pad`** in **`filters.py`**.

```python
from filters import zero_pad

pad_width = 20 # width of the padding on the left and right
pad_height = 40 # height of the padding on the top and bottom

padded_img = zero_pad(img, pad_height, pad_width)

# Plot your padded dog
plt.subplot(1,2,1)
plt.imshow(padded_img)
plt.title('Padded dog')
plt.axis('off')

# Plot what you should get
solution_img = io.imread('padded_dog.jpg', as_gray=True)
plt.subplot(1,2,2)
plt.imshow(solution_img)
plt.title('What you should get')
plt.axis('off')

plt.show()
```

Next, complete the function **`conv_fast`** in **`filters.py`** using `zero_pad`. Run the code below to compare the outputs by the two implementations. `conv_fast` should run significantly faster than `conv_nested`.
Depending on your implementation and computer, `conv_nested` should take a few seconds and `conv_fast` should be around 5 times faster.

```python
from filters import conv_fast

t0 = time()
out_fast = conv_fast(img, kernel)
t1 = time()
out_nested = conv_nested(img, kernel)
t2 = time()

# Compare the running time of the two implementations
print("conv_nested: took %f seconds." % (t2 - t1))
print("conv_fast: took %f seconds." % (t1 - t0))

# Plot conv_nested output
plt.subplot(1,2,1)
plt.imshow(out_nested)
plt.title('conv_nested')
plt.axis('off')

# Plot conv_fast output
plt.subplot(1,2,2)
plt.imshow(out_fast)
plt.title('conv_fast')
plt.axis('off')

# Make sure that the two outputs are the same
if not (np.max(out_fast - out_nested) < 1e-10):
    print("Different outputs! Check your implementation.")
```

### Extra Credit 1 (10 points)
Devise a faster version of convolution and implement **`conv_faster`** in **`filters.py`**. You will earn extra credit only if the `conv_faster` runs faster (by a fair margin) than `conv_fast` **and** outputs the same result.

```python
from filters import conv_faster

t0 = time()
out_fast = conv_fast(img, kernel)
t1 = time()
out_faster = conv_faster(img, kernel)
t2 = time()

# Compare the running time of the two implementations
print("conv_fast: took %f seconds." % (t1 - t0))
print("conv_faster: took %f seconds." % (t2 - t1))

# Plot conv_nested output
plt.subplot(1,2,1)
plt.imshow(out_fast)
plt.title('conv_fast')
plt.axis('off')

# Plot conv_fast output
plt.subplot(1,2,2)
plt.imshow(out_faster)
plt.title('conv_faster')
plt.axis('off')

# Make sure that the two outputs are the same
if not (np.max(out_fast - out_faster) < 1e-10):
    print("Different outputs! Check your implementation.")
```

---
## Part 2: Cross-correlation

Cross-correlation of two 2D signals $f$ and $g$ is defined as follows:
$$(f\star{g})[m,n]=\sum_{i=-\infty}^\infty\sum_{j=-\infty}^\infty f[i,j]\cdot g[m-i,n-j]$$

### 2.1 Template Matching with Cross-correlation (12 points)
Suppose that you are a clerk at a grocery store. One of your responsibilites is to check the shelves periodically and stock them up whenever there are sold-out items. You got tired of this laborious task and decided to build a computer vision system that keeps track of the items on the shelf.

Luckily, you have learned in CS131 that cross-correlation can be used for template matching: a template $g$ is multiplied with regions of a larger image $f$ to measure how similar each region is to the template.

The template of a product (`template.jpg`) and the image of shelf (`shelf.jpg`) is provided. We will use cross-correlation to find the product in the shelf.

Implement **`cross_correlation`** function in **`filters.py`** and run the code below.

*- Hint: you may use the `conv_fast` function you implemented in the previous question.*

```python
from filters import cross_correlation

# Load template and image in grayscale
img = io.imread('shelf.jpg')
img_grey = io.imread('shelf.jpg', as_gray=True)
temp = io.imread('template.jpg')
temp_grey = io.imread('template.jpg', as_gray=True)

# Perform cross-correlation between the image and the template
out = cross_correlation(img_grey, temp_grey)

# Find the location with maximum similarity
y,x = (np.unravel_index(out.argmax(), out.shape))

# Display product template
plt.figure(figsize=(25,20))
plt.subplot(3, 1, 1)
plt.imshow(temp)
plt.title('Template')
plt.axis('off')

# Display cross-correlation output
plt.subplot(3, 1, 2)
plt.imshow(out)
plt.title('Cross-correlation (white means more correlated)')
plt.axis('off')

# Display image
plt.subplot(3, 1, 3)
plt.imshow(img)
plt.title('Result (blue marker on the detected location)')
plt.axis('off')

# Draw marker at detected location
plt.plot(x, y, 'bx', ms=40, mew=10)
plt.show()
```

#### Interpretation
How does the output of cross-correlation filter look? Was it able to detect the product correctly? Explain what problems there might be with using a raw template as a filter.

**Your Answer:** *Write your solution in this markdown cell.*

---
### 2.2 Zero-mean cross-correlation (6 points)
A solution to this problem is to subtract the mean value of the template so that it has zero mean.

Implement **`zero_mean_cross_correlation`** function in **`filters.py`** and run the code below.

**If your implementation is correct, you should see the blue cross centered over the correct cereal box**

```python
from filters import zero_mean_cross_correlation

# Perform cross-correlation between the image and the template
out = zero_mean_cross_correlation(img_grey, temp_grey)

# Find the location with maximum similarity
y,x = (np.unravel_index(out.argmax(), out.shape))

# Display product template
plt.figure(figsize=(30,20))
plt.subplot(3, 1, 1)
plt.imshow(temp)
plt.title('Template')
plt.axis('off')

# Display cross-correlation output
plt.subplot(3, 1, 2)
plt.imshow(out)
plt.title('Cross-correlation (white means more correlated)')
plt.axis('off')

# Display image
plt.subplot(3, 1, 3)
plt.imshow(img)
plt.title('Result (blue marker on the detected location)')
plt.axis('off')

# Draw marker at detcted location
plt.plot(x, y, 'bx', ms=40, mew=10)
plt.show()

```

You can also determine whether the product is present with appropriate scaling and thresholding.

```python
def check_product_on_shelf(shelf, product):
    out = zero_mean_cross_correlation(shelf, product)

    # Scale output by the size of the template
    out = out / float(product.shape[0]*product.shape[1])

    # Threshold output (this is arbitrary, you would need to tune the threshold for a real application)
    out = out > 0.025

    if np.sum(out) > 0:
        print('The product is on the shelf')
    else:
        print('The product is not on the shelf')

# Load image of the shelf without the product
img2 = io.imread('shelf_soldout.jpg')
img2_grey = io.imread('shelf_soldout.jpg', as_gray=True)

plt.imshow(img)
plt.axis('off')
plt.show()
check_product_on_shelf(img_grey, temp_grey)

plt.imshow(img2)
plt.axis('off')
plt.show()
check_product_on_shelf(img2_grey, temp_grey)
```

---
### 2.3 Normalized Cross-correlation (12 points)
One day the light near the shelf goes out and the product tracker starts to malfunction. The `zero_mean_cross_correlation` is not robust to change in lighting condition. The code below demonstrates this.

```python
from filters import normalized_cross_correlation

# Load image
img = io.imread('shelf_dark.jpg')
img_grey = io.imread('shelf_dark.jpg', as_gray=True)

# Perform cross-correlation between the image and the template
out = zero_mean_cross_correlation(img_grey, temp_grey)

# Find the location with maximum similarity
y,x = (np.unravel_index(out.argmax(), out.shape))

# Display image
plt.imshow(img)
plt.title('Result (red marker on the detected location)')
plt.axis('off')

# Draw marker at detcted location
plt.plot(x, y, 'rx', ms=25, mew=5)
plt.show()
```

A solution is to normalize the pixels of the image and template at every step before comparing them. This is called **normalized cross-correlation**.

The mathematical definition for normalized cross-correlation of $f$ and template $g$ is:
$$(f\star{g})[m,n]=\sum_{i,j} \frac{f[i,j]-\overline{f_{m,n}}}{\sigma_{f_{m,n}}} \cdot \frac{g[i-m,j-n]-\overline{g}}{\sigma_g}$$

where:
- $f_{m,n}$ is the patch image at position $(m,n)$
- $\overline{f_{m,n}}$ is the mean of the patch image $f_{m,n}$
- $\sigma_{f_{m,n}}$ is the standard deviation of the patch image $f_{m,n}$
- $\overline{g}$ is the mean of the template $g$
- $\sigma_g$ is the standard deviation of the template $g$

Implement **`normalized_cross_correlation`** function in **`filters.py`** and run the code below.

```python
from filters import normalized_cross_correlation

# Perform normalized cross-correlation between the image and the template
out = normalized_cross_correlation(img_grey, temp_grey)

# Find the location with maximum similarity
y,x = (np.unravel_index(out.argmax(), out.shape))

# Display image
plt.imshow(img)
plt.title('Result (red marker on the detected location)')
plt.axis('off')

# Draw marker at detcted location
plt.plot(x, y, 'rx', ms=25, mew=5)
plt.show()
```

## Part 3: Separable Filters

### 3.1 Theory (10 points)
Consider an $M_1\times{N_1}$ image $I$ and an $M_2\times{N_2}$ filter $F$. A filter $F$ is **separable** if it can be written as a product of two 1D filters: $F=F_1F_2$.

For example,
$$F=
\begin{bmatrix}
1 & -1 \\
1 & -1
\end{bmatrix}
$$
can be written as a matrix product of
$$F_1=
\begin{bmatrix}
1  \\
1
\end{bmatrix},
F_2=
\begin{bmatrix}
1 & -1
\end{bmatrix}
$$
Therefore $F$ is a separable filter.

Prove that for any separable filter $F=F_1F_2$,
$$I*F=(I*F_1)*F_2$$

**Your Answer:** *Write your solution in this markdown cell. Please write your equations in [LaTex equations](http://jupyter-notebook.readthedocs.io/en/latest/examples/Notebook/Typesetting%20Equations.html).*

### 3.2 Complexity comparison (10 points)
Consider an $M_1\times{N_1}$ image $I$ and an $M_2\times{N_2}$ filter $F$ that is separable (i.e. $F=F_1F_2$).

(i) How many multiplication operations do you need to do a direct 2D convolution (i.e. $I*F$)?

(ii) How many multiplication operations do you need to do 1D convolutions on rows and columns (i.e. $(I*F_1)*F_2$)?

(iii) Use Big-O notation to argue which one is more efficient in general: direct 2D convolution or two successive 1D convolutions?

**Your Answer:** *Write your solution in this markdown cell. Please write your equations in [LaTex equations](http://jupyter-notebook.readthedocs.io/en/latest/examples/Notebook/Typesetting%20Equations.html).*

Now, we will empirically compare the running time of a separable 2D convolution and its equivalent two 1D convolutions. The Gaussian kernel, widely used for blurring images, is one example of a separable filter. Run the code below to see its effect.

```python
# Load image
img = io.imread('dog.jpg', as_grey=True)

# 5x5 Gaussian blur
kernel = np.array(
[
    [1,4,6,4,1],
    [4,16,24,16,4],
    [6,24,36,24,6],
    [4,16,24,16,4],
    [1,4,6,4,1]
])

t0 = time()
out = conv_nested(img, kernel)
t1 = time()
t_normal = t1 - t0

# Plot original image
plt.subplot(1,2,1)
plt.imshow(img)
plt.title('Original')
plt.axis('off')

# Plot convolved image
plt.subplot(1,2,2)
plt.imshow(out)
plt.title('Blurred')
plt.axis('off')

plt.show()
```

In the below code cell, define the two 1D arrays (`k1` and `k2`) whose product is equal to the Gaussian kernel.

```python
# The kernel can be written as outer product of two 1D filters
k1 = None  # shape (5, 1)
k2 = None  # shape (1, 5)

### YOUR CODE HERE
pass
### END YOUR CODE

# Check if kernel is product of k1 and k2
if not  np.all(k1 * k2 == kernel):
    print('k1 * k2 is not equal to kernel')

assert k1.shape == (5, 1), "k1 should have shape (5, 1)"
assert k2.shape == (1, 5), "k2 should have shape (1, 5)"
```

We now apply the two versions of convolution to the same image, and compare their running time. Note that the outputs of the two convolutions must be the same.

```python
# Perform two convolutions using k1 and k2
t0 = time()
out_separable = conv_nested(img, k1)
out_separable = conv_nested(out_separable, k2)
t1 = time()
t_separable = t1 - t0

# Plot normal convolution image
plt.subplot(1,2,1)
plt.imshow(out)
plt.title('Normal convolution')
plt.axis('off')

# Plot separable convolution image
plt.subplot(1,2,2)
plt.imshow(out_separable)
plt.title('Separable convolution')
plt.axis('off')

plt.show()

print("Normal convolution: took %f seconds." % (t_normal))
print("Separable convolution: took %f seconds." % (t_separable))
```

```python
# Check if the two outputs are equal
assert np.max(out_separable - out) < 1e-10
```


Construir o gráfico e encontrar o foco e uma equação da diretriz.

4. $x^2 + y = 0$

Arrumando a equação

$x^2 = -y$

$2p = -1$,logo

$p = -\frac{1}{2}$

Calculando o foco

$F = -\frac{p}{2}$

$F = \frac{-\frac{1}{2}}{2}$

$F = -\frac{1}{2}\cdot \frac{1}{2}$

$F = -\frac{1}{4}$

$F(0,-\frac{1}{4})$

Calculando a diretriz

$d = -\frac{p}{2}$

$d = -(-\frac{1}{4})$

$d : y = \frac{1}{4}$

$V(0,0)$

$F(0,-\frac{1}{4})$

Gráfico da parábola

```python
from sympy import *
from sympy.plotting import plot_implicit
x, y = symbols("x y")
plot_implicit(Eq((x-0)**2, -1*(y+0)), (x,-3,3), (y,-3,3),
title=u'Gráfico da parábola', xlabel='x', ylabel='y');
```

Problemas Propostos. Pag. 172 - 175/04.ipynb


#  Estimation of Orbital Parameters of Extra-solar Planets from Radial Velocities using Maximum Likelihood Estimation (MLE)

## Maximum Likelihood Estimation

MLE is used to determine the parameters of a probability distribution by maximizing a likelihood function, so that under the assumed statistical model the observed data is most probable. It provides a framework for predictive modeling where finding model parameters can be framed as an optimization problem. Maximum likelihood estimation is a completely analytic maximization procedure. It is an unbiased minimum variance estimators for a large sample size. This method does not try to minimize the difference to the data, but tries to maximize the likelihood that the data could have been generated by the model.

## Log Likelihood Function

Let $\mathrm{Y_1, Y_2, Y_3,...Y_n}$ be the independent and identically distributed (i.i.d.) random variables with a joint probability density denoted by

\begin{equation} f_\Theta(y_1, y_2, y_3...y_n) = f(y_1, y_2, y_3...y_n\;|\;\Theta)\mathrm{\tag{1}}\end{equation}

where $y_1, y_2, y_3...y_n$ are the observed values. The likelihood of the parameter $\Theta$ is the function

\begin{equation} L(\Theta) = f(y_1, y_2, y_3...y_n\;|\;\Theta)\mathrm{\tag{2}}\end{equation}

It is defined as the probability of observing the given data as a function of $\Theta$. The ML estimate of $\Theta$ is that value of $\Theta$ that maximises the likelihood function and makes the observed data the most probable. As $\mathrm{Y_1, Y_2, Y_3,...Y_n}$ are i.i.d., then the likelihood simplifies to

\begin{equation} L(\Theta) = \prod_{i=1}^{n}f(y_i\;|\;\Theta)\mathrm{\tag{3}}\end{equation}

For different values of parameters, the likelihood of the data is different. For the correct parameter values, the data is more probable than than the data with incorrect parameters. For this reason, the likelihood is written as a function of the parameter set, $\Theta$.

## Maximization Process

The ultimate aim is to chose values of our parameters ($\Theta$) that maximizesthe likelihood function. Let $\widehat{\Theta}$ represents the best choice of values for our parameters. Therefore, in other words:

\begin{equation} \widehat{\Theta} = \max_{\Theta}L(\Theta)\mathrm{\tag{4}}\end{equation}

The maximum argument of a function is the value of the domain at which the value of the function is maximized. Since log ia a monotonic function, maximizing a function or the log of that function is same.   Thus, for MLE the Log Likelihood function (LL) is written as:

\begin{equation} LL(\Theta) = log\; L(\Theta) = log\; \prod_{i=1}^{n}f(y_i\;|\;\Theta) = \sum_{i=1}^{n}log\; f(y_i\;|\;\Theta) \mathrm{\tag{5}}\end{equation}

## The Data

We shall use the ML estimate to know more about characteristics of the planetary system, 51 Pegasi (HD 217014). The radial velocity (RV) data was obtained from NASA Exoplanet Archive which is maintained by the [NASA Exoplanet Science](https://exoplanetarchive.ipac.caltech.edu/). Let the data be denoted by $y_i$ of which we are interested to make inferences.

## Fitting the Exoplanet's Radial Velocity

The RV data of 51 Pegasi can be downloaded from the archive. We are interested in the RV values with thier errors as a function of the Julian Date. For representation purpose, we shall phase-fold the time axis.

```python
#!/usr/bin/env python3
import numpy as np
import scipy as sp
from scipy.optimize import minimize
import pandas as pd
import matplotlib.pyplot as plt
import math
import emcee
import corner
import pickle
import tkinter as tk
from tkinter import simpledialog
from tkinter.simpledialog import askinteger
import easygui
from IPython.display import display, Math
from numpy.linalg import inv
from sympy import symbols, diff, pi, cos, sin
import time
from time import perf_counter
from tqdm import tqdm
from traits.api import HasTraits, Instance, Array, \
    on_trait_change
from traitsui.api import View, Item, HGroup, Group

from tvtk.api import tvtk
from tvtk.pyface.scene import Scene

from mayavi import mlab
from mayavi.core.api import PipelineBase, Source
from mayavi.core.ui.api import SceneEditor, MayaviScene, \
                                MlabSceneModel

plt.rcParams['figure.figsize'] = [12,7]
```

```python
Data = pd.read_table('UID_0113357_RVC_005.dat',sep = '\s+',skiprows=22, header = None, index_col = None)
print(Data)
RV_Data = np.array(Data)
HJD = RV_Data[:,[0]]
Rad_Vel = RV_Data[:,[1]]
RV_Error = RV_Data[:,[2]]
plt.errorbar(HJD, Rad_Vel, RV_Error, color='black', fmt='o', capsize=5, capthick=1, ecolor='black')
plt.xlabel('Heliocentric Julian Date (Time)')
plt.ylabel('Radial Velocity')
plt.grid()
plt.show(block=False)
len_HJD = len(HJD)
Phase_Folded = np.empty((len_HJD, len_HJD))
fig = plt.figure()
```

```python
for i in range(0,len_HJD):
    t = HJD
    T0 = HJD[i]
    P = 4.230785
    PF = ((t-T0)/P)-np.floor((t-T0)/P)
    Phase_Folded[:,[i]] = PF

root =tk.Tk()
canvas1 = tk.Canvas(root, width = 400, height = 200,  relief = 'raised')
canvas1.pack()

label1 = tk.Label(root, text='Select the Reference Time (Fiducial)')
label1.config(font=('helvetica', 14))
canvas1.create_window(200, 25, window=label1)

label2 = tk.Label(root, text='Input the n\u1D57\u02B0 HJD to be taken as the reference \n\n(n\u2208Z\u207a; 1 \u2264 n \u2264 202):')
label2.config(font=('helvetica', 10))
canvas1.create_window(200, 95, window=label2)
mystring =tk.IntVar(root)
def close_window():
    root.destroy()
e1 = tk.Entry(root,textvariable = mystring,width=25,fg="black",bd=3,selectbackground='gray').place(x=100, y=120)
button1 = tk.Button(root,
                text='Submit',
                fg='White',
                bg= '#000000',height = 1, width = 10,command=close_window).place(x=150, y=170)
root.mainloop()
j = mystring.get()
j = j-1
plt.errorbar(2*np.pi*Phase_Folded[:,[j]], Rad_Vel, RV_Error, color='black', fmt='o', capsize=5, capthick=1, ecolor='black')
plt.xlabel('Phase Folded (Time)')
plt.ylabel('Radial Velocity')
plt.grid()
plt.show(block=False)
x_data = Phase_Folded[:,[j]]
x_model = np.linspace(0, 1, 5000)
data = (x_data,Rad_Vel,RV_Error)
```

The underlying fit appears to be a sinusoid. So, we shall use the following function to model the data:

\begin{equation} y = y_0 + A_0\; cos(2\, \pi\, x + \Phi_0) \mathrm{\tag{6}}\end{equation}

where y_0, A_0,and \Phi_0 are the parameters. Let there be $N$ points to fit. For a data point, the probability distribution function (PDF) is

\begin{equation} P\,(y_i\, |\, x_i, \Delta\, y_i, y_0, A_0, \Phi_0) = \frac{1}{\Delta\, y_i \sqrt{2 \pi}}~\exp \left( - \frac{\{y_i - [y_0 + A_0\; cos(2\, \pi\, x + \Phi_0)]\}^2}
{2 (\Delta\, y_i)^2} \right) \mathrm{\tag{7}}\end{equation}

For all the data points,

\begin{equation} P\,(y\, |\, x, \Delta\, y, y_0, A_0, \Phi_0) = \prod_{i=1}^{n}\frac{1}{\Delta\, y_i \sqrt{2 \pi}}~\exp \left( - \frac{\{y_i - [y_0 + A_0\; cos(2\, \pi\, x + \Phi_0)]\}^2}
{2 (\Delta\, y_i)^2} \right) \mathrm{\tag{8}}\end{equation}

\begin{equation} P\,(y\, |\, x, \Delta\, y, y_0, A_0, \Phi_0) = \frac{1}{{(2 \pi)}^{\frac{N}{2}}}\prod_{i=1}^{n}\frac{1}{\Delta\, y_i}~\exp \left( - \frac{\{y_i - [y_0 + A_0\; cos(2\, \pi\, x + \Phi_0)]\}^2}
{2 (\Delta\, y_i)^2} \right) \mathrm{\tag{9}}\end{equation}

\begin{equation} ln\, P = ln\,\left(\frac{1}{{(2 \pi)}^{\frac{N}{2}}}\right)\:-\:ln\:\prod_{i=1}^{n}\frac{1}{\Delta\, y_i}~\exp \left( - \frac{\{y_i - [y_0 + A_0\; cos(2\, \pi\, x + \Phi_0)]\}^2}
{2 (\Delta\, y_i)^2} \right) \mathrm{\tag{10}}\end{equation}

\begin{equation} = ln\:(2\, \pi\, \Delta y_i^2)+ \left(\frac{\{y_i - [y_0 + A_0\; cos(2\, \pi\, x + \Phi_0)]\}^2}
{2 (\Delta\, y_i)^2} \right) \mathrm{\tag{11}}\end{equation}

## Fisher Information Matrix $(I)$

The Fisher information is a way of measuring the amount of information that an observable random variable $Y$ carries about an unknown parameter $\Theta$ of a distribution that models $Y$. It is used to calculate the covariance matrices associated with maximum-likelihood estimates. It is given by

\begin{equation} I(\Theta) = \textbf{E}\left[-\frac{\partial^2}{\partial \,\Theta^2}\,ln\; f(Y\;|\;\Theta)\right]\mathrm{\tag{12}}\end{equation}

The Fisher information for the parameter $A_0$ is

\begin{equation} I(A_0) = -\textbf{E}\left[\frac{\partial^2}{\partial \,A_0^2}\,ln\, P\right] = \sum_{i=1}^{N}\frac{cos^2\, (2\, \pi\, x + \Phi_0)}{\Delta y_i^2}\mathrm{\tag{13}}\end{equation}

For the parameter $y_0$

\begin{equation} I(y_0) = -\textbf{E}\left[\frac{\partial^2}{\partial \,y_0^2}\,ln\, P\right] = -\textbf{E}\left[-\sum_{i=1}^{N}\frac{1}{\Delta y_i^2}\right] = \sum_{i=1}^{N}\frac{1}{\Delta y_i^2}\mathrm{\tag{14}}\end{equation}

For the parameter $\Phi_0$

\begin{equation} I(\Phi_0) = -\textbf{E}\left[\frac{\partial^2}{\partial \,\Phi_0^2}\,ln\, P\right]\mathrm{\tag{15}}\end{equation}

\begin{equation} \frac{\partial^2}{\partial \,\Phi_0^2}\,ln\, P = \sum_{i=1}^{N}\, \frac{A_0}{\Delta y_i^2} \{A_0\; cos(4\, \pi\, x + 2\, \Phi_0) + (y_0 - y_i)\; cos(2\, \pi\, x + \Phi_0)\}\mathrm{\tag{16}}\end{equation}

Therefore,

\begin{equation} I(\Phi_0) = -\sum_{i=1}^{N}\, \frac{A_0}{\Delta y_i^2} \{A_0\; cos(4\, \pi\, x + 2\, \Phi_0) + (y_0 - y_i)\; cos(2\, \pi\, x + \Phi_0)\}\mathrm{\tag{17}}\end{equation}

We shall use this information to model our data.

```python
#
# Model (y0 + A0*cos(2πx+Φ0))
#
def rvmodel_ml(Parameters,x=x_model):
    y0, A0, Phi0 = Parameters
    return y0 + (A0*np.cos((2*np.pi*x)+Phi0))
```

```python
#
# Likelihood Function
#
def lnlike(Parameters,x,y,yerror):
    return -0.5*(np.sum((np.log(2*np.pi*(yerror**2)))+(((y-rvmodel_ml(Parameters,x))/yerror)**2)))
```

From the above plot, a rough estimate of the priors can be made.

```python
#
# Initial Parameters, Lower and Upper Bounds
#
Param0 = np.array([-2.0, 100.0, 3.0])
bounds = ((-50.0,50.0),(0.0,125.0),(0.0,2*np.pi))
```

```python
#
# Maximum Likelihood Fit
#
ln = lambda*args: -lnlike(*args)
output = minimize(ln, Param0, args=data, bounds=bounds, tol= 1e-9)
offset_ml, amplitude_ml, phase_ml = output["x"]
ml_estimates = np.array([offset_ml, amplitude_ml, phase_ml])
ndim = len(ml_estimates)
best_fit_model_ml = rvmodel_ml(ml_estimates,x_model)
```

```python
plt.errorbar(2*np.pi*x_data,Rad_Vel, RV_Error, color='black', fmt='o', capsize=5, capthick=1, ecolor='black')
plt.plot(2*np.pi*x_model,best_fit_model_ml,color='#008080', label="Maximum Likelihood Fit")
plt.xlabel('Phase Folded (Time)')
plt.ylabel('Radial Velocity')
plt.legend(fontsize=14)
plt.grid()
plt.show()
```

Let us obtain the parameter values.

```python
a = 1/(RV_Error**2)
b = np.divide((np.cos((2*np.pi*x_data)+phase_ml))**2,RV_Error**2)
c = np.divide(((amplitude_ml*(Rad_Vel-offset_ml)*np.cos((2*np.pi*x_data)+phase_ml))-((amplitude_ml**2)*np.cos((4*np.pi*x_data)+(2*phase_ml)))),RV_Error**2)
yi, y_0, a0, x, phi, d_yi = symbols('yi y a0 x phi d_yi', real=True)
func = (-1/2)*(((yi-(y_0 + (a0*(cos((2*pi*x)+phi)))))/(d_yi))**2)
pd = diff(func, phi)
pd2 = diff(pd, phi)

Unct_y0 = np.sqrt(1/(np.sum(a)))
Unct_A0 = np.sqrt(1/(np.sum(b)))
Unct_Phi0 = np.sqrt(1/(np.sum(c)))
Uncertainties = np.array([Unct_y0, Unct_A0, Unct_Phi0],dtype = ('float64'))

print('\033[1m' + 'Parameters using Maximum Likelihood Method (Minimizing the negative Log-Likelihood)' + '\033[0m')
label = ['y₀', 'A₀', '𝛟₀']
for s in range(ndim):
    txt = "\mathrm{{{2}}} = {0:.9f}_{{-{1:.3f}}}^{{{1:.3f}}}"
    txt = txt.format(ml_estimates[s], Uncertainties[s], label[s])
    display(Math(txt))
```

    Parameters using Maximum Likelihood Method (Minimizing the negative Log-Likelihood)

$\displaystyle \mathrm{y₀} = 9.038826471_{-0.128}^{0.128}$

$\displaystyle \mathrm{A₀} = 55.019994469_{-0.182}^{0.182}$

$\displaystyle \mathrm{𝛟₀} = 0.451258134_{-0.003}^{0.003}$

We shall now create a 4-dimensional grid in order to determine the degeneracies of the parameter space.

```python
#create array to cover parameter space

from itertools import product
xi, yi, zi = np.ogrid[-75:75:50j, -200:200:100j, -6.28:6.28:50j]

m = xi.flatten()
n = yi.flatten()
o = zi.flatten()
mno = np.asarray(list(product(m, n, o)))

#calculate likelihoods

t0 = perf_counter();
L = np.array([lnlike(mno[v],x_data,Rad_Vel,RV_Error) for v in tqdm(range(len(mno)))])
t1 = perf_counter();
Execution_Time = t1-t0

```

    100%|██████████| 250000/250000 [00:14<00:00, 17660.95it/s]

```python
#t0 = time.time();
#results = []
#for i in tqdm(range(len(mno))):
#    results.append(lnlike(mno[i],x_data,Rad_Vel,RV_Error))
#LUP = np.hstack(results)
#t1 = time.time();
#Execution_Time = t1-t0

#select parameter with maximum likelihood
Likelihood_max = np.max(L)
MaxPos = mno[np.array([p for p,q in tqdm(enumerate(L)) if q==np.max(L)])]
#draw likelihood

from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.gca(projection='3d')
img = ax.scatter(mno[:,0], mno[:,1], mno[:,2], c=L, lw=0, s=20, cmap=plt.hot())
ax.set_xlabel('$y₀$', fontsize=17)
ax.set_ylabel('$A₀$', fontsize=17)
ax.set_zlabel('$𝛟₀$', fontsize=17)
fig.colorbar(img)
plt.show()

```

We shall use MayaVi to visualise the variation of the log-likelihood in the grid. It is a scientific data visualizer written in Python, which uses VTK and provides a GUI via Tkinter.

```python

#Volume Slice in Mayavi

################################################################################

L_New = L.reshape(len(m),len(n),len(o))

################################################################################
# The object implementing the dialog
class VolumeSlicer(HasTraits):
    # The data to plot
    L_New = Array()

    # The 4 views displayed
    scene3d = Instance(MlabSceneModel, ())
    scene_x = Instance(MlabSceneModel, ())
    scene_y = Instance(MlabSceneModel, ())
    scene_z = Instance(MlabSceneModel, ())

    # The data source
    L_New_src3d = Instance(Source)

    # The image plane widgets of the 3D scene
    ipw_3d_x = Instance(PipelineBase)
    ipw_3d_y = Instance(PipelineBase)
    ipw_3d_z = Instance(PipelineBase)

    _axis_names = dict(x=0, y=1, z=2)

    #---------------------------------------------------------------------------
    def __init__(self, **traits):
        super(VolumeSlicer, self).__init__(**traits)
        # Force the creation of the image_plane_widgets:
        self.ipw_3d_x
        self.ipw_3d_y
        self.ipw_3d_z

    #---------------------------------------------------------------------------
    # Default values
    #---------------------------------------------------------------------------
    def _L_New_src3d_default(self):
        return mlab.pipeline.scalar_field(self.L_New,
                            figure=self.scene3d.mayavi_scene)

    def make_ipw_3d(self, axis_name):
        ipw = mlab.pipeline.image_plane_widget(self.L_New_src3d,
                        figure=self.scene3d.mayavi_scene,
                        plane_orientation='%s_axes' % axis_name)
        return ipw

    def _ipw_3d_x_default(self):
        return self.make_ipw_3d('x')

    def _ipw_3d_y_default(self):
        return self.make_ipw_3d('y')

    def _ipw_3d_z_default(self):
        return self.make_ipw_3d('z')

    #---------------------------------------------------------------------------
    # Scene activation callbaks
    #---------------------------------------------------------------------------
    @on_trait_change('scene3d.activated')
    def display_scene3d(self):
        outline = mlab.pipeline.outline(self.L_New_src3d,
                        figure=self.scene3d.mayavi_scene,
                        )
        self.scene3d.mlab.view(40, 50)
        # Interaction properties can only be changed after the scene
        # has been created, and thus the interactor exists
        for ipw in (self.ipw_3d_x, self.ipw_3d_y, self.ipw_3d_z):
            # Turn the interaction off
            ipw.ipw.interaction = 0
        self.scene3d.scene.background = (0, 0, 0)
        # Keep the view always pointing up
        self.scene3d.scene.interactor.interactor_style = \
                                 tvtk.InteractorStyleTerrain()

    def make_side_view(self, axis_name):
        scene = getattr(self, 'scene_%s' % axis_name)

        # To avoid copying the data, we take a reference to the
        # raw VTK dataset, and pass it on to mlab. Mlab will create
        # a Mayavi source from the VTK without copying it.
        # We have to specify the figure so that the data gets
        # added on the figure we are interested in.
        outline = mlab.pipeline.outline(
                            self.L_New_src3d.mlab_source.dataset,
                            figure=scene.mayavi_scene,
                            )
        ipw = mlab.pipeline.image_plane_widget(
                            outline,
                            plane_orientation='%s_axes' % axis_name)
        setattr(self, 'ipw_%s' % axis_name, ipw)

        # Synchronize positions between the corresponding image plane
        # widgets on different views.
        ipw.ipw.sync_trait('slice_position',
                            getattr(self, 'ipw_3d_%s'% axis_name).ipw)

        # Make left-clicking create a crosshair
        ipw.ipw.left_button_action = 0
        # Add a callback on the image plane widget interaction to
        # move the others
        def move_view(obj, evt):
            position = obj.GetCurrentCursorPosition()
            for other_axis, axis_number in self._axis_names.items():
                if other_axis == axis_name:
                    continue
                ipw3d = getattr(self, 'ipw_3d_%s' % other_axis)
                ipw3d.ipw.slice_position = position[axis_number]

        ipw.ipw.add_observer('InteractionEvent', move_view)
        ipw.ipw.add_observer('StartInteractionEvent', move_view)

        # Center the image plane widget
        ipw.ipw.slice_position = 0.5*self.L_New.shape[
                    self._axis_names[axis_name]]

        # Position the view for the scene
        views = dict(x=( 0, 90),
                     y=(90, 90),
                     z=( 0,  0),
                     )
        scene.mlab.view(*views[axis_name])
        # 2D interaction: only pan and zoom
        scene.scene.interactor.interactor_style = \
                                 tvtk.InteractorStyleImage()
        scene.scene.background = (0, 0, 0)

    @on_trait_change('scene_x.activated')
    def display_scene_x(self):
        return self.make_side_view('x')

    @on_trait_change('scene_y.activated')
    def display_scene_y(self):
        return self.make_side_view('y')

    @on_trait_change('scene_z.activated')
    def display_scene_z(self):
        return self.make_side_view('z')

    #---------------------------------------------------------------------------
    # The layout of the dialog created
    #---------------------------------------------------------------------------
    view = View(HGroup(
                  Group(
                       Item('scene_y',
                            editor=SceneEditor(scene_class=Scene),
                            height=250, width=300),
                       Item('scene_z',
                            editor=SceneEditor(scene_class=Scene),
                            height=250, width=300),
                       show_labels=False,
                  ),
                  Group(
                       Item('scene_x',
                            editor=SceneEditor(scene_class=Scene),
                            height=250, width=300),
                       Item('scene3d',
                            editor=SceneEditor(scene_class=MayaviScene),
                            height=250, width=300),
                       show_labels=False,
                  ),
                ),
                resizable=True,
                title='Volume Slicer',
                )

vs_plot = VolumeSlicer(L_New=L_New)
vs_plot.configure_traits()
```

    True

The output is as follows:

**Developed and designed by [Shubhonkar Paramanick](https://github.com/Shubhonkar-Paramanick)**


# Logical Design

## Objective and Prerequisites

In this example, you’ll learn how to solve a logical design problem, which involves constructing a circuit using the minimum number of NOR gates (devices with two inputs and  one output) that will perform the logical function specified by a truth table. We’ll show you how to formulate this problem as a binary optimization problem using the Gurobi Python API and then use Gurobi Optimizer to automatically find the optimal solution.

This model is example 12 from the fifth edition of Model Building in Mathematical Programming by H. Paul Williams on pages 266-267 and 320-321.

This example is at the intermediate level, where we assume that you know Python and the Gurobi Python API and that you have some knowledge of building mathematical optimization models.

**Download the Repository** <br />
You can download the repository containing this and other examples by clicking [here](https://github.com/Gurobi/modeling-examples/archive/master.zip).

## Problem Description

Logical circuits have a given number of inputs and one output. Impulses may be applied to the inputs of a given logical circuit, and it will respond by giving either an output (signal 1) or no output (signal 0). The input impulses are of the
same kind as the outputs - 1 (positive input) or 0 (no input).

In this example, a logical circuit is to be built up of NOR gates. A NOR gate is a device with two inputs and one output. It has the property that there is positive output (signal 1) if and only if neither input is positive, that is, both inputs have the value 0. By connecting such gates together with outputs from one gate possibly being inputs into another gate, it is possible to construct a circuit to perform any desired logical function. For example, the circuit illustrated in the following figure will respond to the inputs A and B in the way indicated by the truth table.

The objective is to construct a circuit using the minimum number of NOR gates that will perform the logical function specified by the following  truth table.

| A | B | Output |
| --- | --- | --- |
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |

‘Fan-in’ and ‘fan-out’ are not permitted. That is, more than one output from a NOR gate cannot lead into one input nor can one output lead into more than one input. It may be assumed throughout that the optimal design is a ‘subnet’ of the ‘maximal’ net shown in the following figure.

## Model Formulation

### Sets and Indices

$i \in \text{Gates}=\{1,...,7\}$

$i \in \text{G47}=\{4,...,7\}$

$r \in \text{Rows}=\{1,...,4\}$

### Parameters

$\text{valueA}_{i,r} \in \{0,1 \}$: Value of the external input A in row  $r$ of the truth table for gate $i$.

$\text{valueB}_{i,r} \in \{0,1 \}$: Value of the external input B in row  $r$ of the truth table for gate $i$.

### Decision Variables

$\text{NOR}_{i} \in \{0,1 \}$: This binary variable is equal to 1, if NOR gate $i$  is selected, 0 otherwise.

$\text{inputA}_{i} \in \{0,1 \}$: This binary variable is equal to 1, if external input A is an input to NOR gate $i$ , and 0 otherwise.

$\text{inputB}_{i} \in \{0,1 \}$: This binary variable is equal to 1, if external input B is an input to NOR gate $i$ , and 0 otherwise.

$\text{output}_{i,r} \in \{0,1 \}$: This binary variable is the output from gate $i$ for the combination of external input signals specified  in row $r$  of the truth table.

### Constraints

**External input**: A NOR gate can only have an external input if it exists.

\begin{equation}
\text{NOR}_{i} \geq \text{inputA}_{i}  \quad \forall i \in \text{Gates}
\end{equation}

\begin{equation}
\text{NOR}_{i} \geq \text{inputB}_{i}  \quad \forall i \in \text{Gates}
\end{equation}

**NOR gates**:If a NOR gate has one (or two) external inputs leading into it, only one (or no) NOR gates can feed into it.

\begin{equation}
\text{NOR}_{2} + \text{NOR}_{3} + \text{inputA}_{1} + \text{inputB}_{2} \leq 2
\end{equation}

\begin{equation}
\text{NOR}_{4} + \text{NOR}_{5} + \text{inputA}_{2} + \text{inputB}_{2} \leq 2
\end{equation}

\begin{equation}
\text{NOR}_{6} + \text{NOR}_{7} + \text{inputA}_{3} + \text{inputB}_{3} \leq 2
\end{equation}

These constraints are based on the circuit shown in the figure of the ‘maximal’ net.

**Output signals**: The output signal from NOR gate $i$ must be the correct logical function (NOR) of the input signals into gate $i$, if gate $i$ exists.

\begin{equation}
\text{output}_{2,r} + \text{output}_{1,r} \leq 1 \quad \forall r \in \text{Rows}
\end{equation}

\begin{equation}
\text{output}_{3,r} + \text{output}_{1,r} \leq 1 \quad \forall r \in \text{Rows}
\end{equation}

\begin{equation}
\text{valueA}_{i,r}*\text{inputA}_{i} + \text{output}_{i,r} \leq 1 \quad \forall i \in \text{Gates}, r \in \text{Rows}
\end{equation}

\begin{equation}
\text{valueB}_{i,r}*\text{inputB}_{i} + \text{output}_{i,r} \leq 1 \quad \forall i \in \text{Gates}, r \in \text{Rows}
\end{equation}

\begin{equation}
\text{valueA}_{i,r}*\text{inputA}_{i} + \text{valueB}_{i,r}*\text{inputB}_{i} +
\text{output}_{i,r} - \text{NOR}_{i}  \geq 0 \quad \forall i \in \text{G47}, r \in \text{Rows}
\end{equation}

\begin{equation}
\text{valueA}_{1,r}*\text{inputA}_{1} + \text{valueB}_{1,r}*\text{inputB}_{1} +
\text{output}_{2,r} + \text{output}_{3,r} + \text{output}_{1,r} - \text{NOR}_{1}  \geq 0
\quad \forall r \in \text{Rows}
\end{equation}

\begin{equation}
\text{valueA}_{2,r}*\text{inputA}_{2} + \text{valueB}_{2,r}*\text{inputB}_{2} +
\text{output}_{4,r} + \text{output}_{5,r} + \text{output}_{2,r} - \text{NOR}_{2}  \geq 0
\quad \forall r \in \text{Rows}
\end{equation}

\begin{equation}
\text{valueA}_{3,r}*\text{inputA}_{3} + \text{valueB}_{3,r}*\text{inputB}_{3} +
\text{output}_{6,r} + \text{output}_{7,r} + \text{output}_{3,r} - \text{NOR}_{3}  \geq 0
\quad \forall r \in \text{Rows}
\end{equation}

**Gate 1**: For NOR gate 1, the output variables are fixed at the values specified in the truth table.

\begin{equation}
\text{output}_{1,1} = 0, \text{output}_{1,2} = 1, \text{output}_{1,3} = 1,  \text{output}_{1,4} = 0
\end{equation}

To avoid a trivial solution containing no NOR gates, it is necessary to impose a constraint that selects NOR gate 1.

\begin{equation}
\text{NOR}_{1} \geq 1
\end{equation}

**Gates and output**: If there is an output signal of 1 from a particular NOR gate for any combination of the input signals, then that gate must exist.

\begin{equation}
\text{NOR}_{i} - \text{output}_{i,r} \geq 0 \quad \forall i \in \text{Gates}, r \in \text{Rows}
\end{equation}

### Objective Function

**Number of gates**: The objective is to minimize the number of NOR gates selected.

\begin{equation}
\text{Minimize} \quad  \sum_{i \in \text{Gates}} \text{NOR}_{i}
\end{equation}

## Python Implementation

We import the Gurobi Python Module.

```python
%pip install gurobipy
```

```python
import gurobipy as gp
from gurobipy import GRB

# tested with Python 3.7.0 & Gurobi 9.0
```

## Input data

We define all the input data for the model.

```python
# List of NOR gates 1 to 7.

gates = ['1','2','3','4','5','6','7']

# List of NOR gates 4 to 7.

gates47 = ['4','5','6','7']

# List of rows of the truth-table in the range 1 to 4.

rows = ['1','2','3','4']

# Create a dictionary to capture the value of the external input A and B in the r row of the truth table, for each
# NOR gate i.

gatesRows, valueA, valueB = gp.multidict({
    ('1','1'): [0,0],
    ('1','2'): [0,1],
    ('1','3'): [1,0],
    ('1','4'): [1,1],
    ('2','1'): [0,0],
    ('2','2'): [0,1],
    ('2','3'): [1,0],
    ('2','4'): [1,1],
    ('3','1'): [0,0],
    ('3','2'): [0,1],
    ('3','3'): [1,0],
    ('3','4'): [1,1],
    ('4','1'): [0,0],
    ('4','2'): [0,1],
    ('4','3'): [1,0],
    ('4','4'): [1,1],
    ('5','1'): [0,0],
    ('5','2'): [0,1],
    ('5','3'): [1,0],
    ('5','4'): [1,1],
    ('6','1'): [0,0],
    ('6','2'): [0,1],
    ('6','3'): [1,0],
    ('6','4'): [1,1],
    ('7','1'): [0,0],
    ('7','2'): [0,1],
    ('7','3'): [1,0],
    ('7','4'): [1,1]
})

```

## Model Deployment

We create a model and the variables. The main decision is to determine the $\text{NOR}_{i}$ variables that selects the NOR gates to consider in the logical circuit. The rest of the variables ensure that the circuit generates the output of the truth table.

```python
model = gp.Model('logicalDesign')

# Decision variables to select NOR gate i.
NOR = model.addVars(gates, vtype=GRB.BINARY, name="NORgate" )

# In order to avoid a trivial solution containing no NOR gates, it is necessary to impose a constraint
# that selects NOR gate 1.

NOR['1'].lb = 1

# Variables to decide if external input A is an input to NOR gate i.
inputA = model.addVars(gates, vtype=GRB.BINARY, name="inputA")

# Variables to decide if external input B is an input to NOR gate i.
inputB = model.addVars(gates, vtype=GRB.BINARY, name="inputB")

# Output decision variables.
output = model.addVars(gatesRows, vtype=GRB.BINARY, name="output")

# For NOR gate 1, the output variables are fixed at the values specified in the truth table.

output['1','1'].ub = 0
output['1','2'].lb = 1
output['1','3'].lb = 1
output['1','4'].ub = 0
```

    Using license file c:\gurobi\gurobi.lic

A NOR gate can only have an external input if it exists.

```python
# External inputs constraints

externalInputsA = model.addConstrs( ( NOR[i] >= inputA[i]  for i in gates), name='externalInputsA')

externalInputsB = model.addConstrs( ( NOR[i] >= inputB[i]  for i in gates), name='externalInputsB')
```

If a NOR gate has one (or two) external inputs leading into it, only one (or no) NOR gates can feed into it.

```python
# NOR gates constraints

NORgate1 = model.addConstr(NOR['2'] + NOR['3'] + inputA['1'] + inputB['1'] <= 2, name='NORgate1')

NORgate2 = model.addConstr(NOR['4'] + NOR['5'] + inputA['2'] + inputB['2'] <= 2, name='NORgate2')

NORgate3 = model.addConstr(NOR['6'] + NOR['7'] + inputA['3'] + inputB['3'] <= 2, name='NORgate3')
```

The output signal from NOR gate i must be the correct logical function (NOR) of the input signals into gate i, if gate i exists.

```python
# Output signal constraint.

outputSignals1_1 = model.addConstrs( (output['2',r] + output['1',r] <= 1 for r in rows), name='outputSignals1_1' )

outputSignals1_2 = model.addConstrs( (output['3',r] + output['1',r] <= 1 for r in rows), name='outputSignals1_2' )

outputSignals2_1 = model.addConstrs( (output['4',r] + output['2',r] <= 1 for r in rows), name='outputSignals2_1' )

outputSignals2_2 = model.addConstrs( (output['5',r] + output['2',r] <= 1 for r in rows), name='outputSignals2_2' )

outputSignals3_1 = model.addConstrs( (output['6',r] + output['3',r] <= 1 for r in rows), name='outputSignals3_1' )

outputSignals3_2 = model.addConstrs( (output['7',r] + output['3',r] <= 1 for r in rows), name='outputSignals3_2' )

outputSignals4 = model.addConstrs( (valueA[i,r]*inputA[i] + output[i,r] <= 1 for i,r in gatesRows), name='outputSignals4')

outputSignals5 = model.addConstrs( (valueB[i,r]*inputB[i] + output[i,r] <= 1 for i,r in gatesRows), name='outputSignals5')

outputSignals6 = model.addConstrs( (valueA[i,r]*inputA[i] + valueB[i,r]*inputB[i] + output[i,r] - NOR[i] >= 0
                                    for i,r in gatesRows if i in gates47), name='outputSignals6')

outputSignals7 = model.addConstrs( (valueA['1',r]*inputA['1'] + valueB['1',r]*inputB['1']
                                    + output['2',r] + output['3',r] + output['1',r] - NOR['1'] >= 0
                                    for i,r in gatesRows), name='outputSignals7')

outputSignals8 = model.addConstrs( (valueA['2',r]*inputA['2'] + valueB['2',r]*inputB['2']
                                    + output['4',r] + output['5',r] + output['2',r] - NOR['2'] >= 0
                                    for i,r in gatesRows), name='outputSignals8')

outputSignals9 = model.addConstrs( (valueA['3',r]*inputA['3'] + valueB['3',r]*inputB['3']
                                    + output['6',r] + output['7',r] + output['3',r] - NOR['3'] >= 0
                                    for i,r in gatesRows), name='outputSignals9')
```

If there is an output signal of 1 from a particular NOR gate for any combination of the input signals, then that gate must exist.

```python
# Gate and output signals constraints

gateOutput = model.addConstrs( (NOR[i] - output[i,r] >= 0 for i,r in gatesRows) , name='gateOutput')
```

The objective is to minimize the number of NOR gates selected.

```python
# Objective function.

model.setObjective(NOR.sum(), GRB.MINIMIZE)
```

```python
# Verify model formulation

model.write('logicalDesign.lp')

# Run optimization engine

model.optimize()
```

    Gurobi Optimizer version 9.1.0 build v9.1.0rc0 (win64)
    Thread count: 4 physical cores, 8 logical processors, using up to 8 threads
    Optimize a model with 225 rows, 49 columns and 696 nonzeros
    Model fingerprint: 0x9adba516
    Variable types: 0 continuous, 49 integer (49 binary)
    Coefficient statistics:
      Matrix range     [1e+00, 1e+00]
      Objective range  [1e+00, 1e+00]
      Bounds range     [1e+00, 1e+00]
      RHS range        [1e+00, 2e+00]
    Presolve removed 225 rows and 49 columns
    Presolve time: 0.00s
    Presolve: All rows and columns removed

    Explored 0 nodes (0 simplex iterations) in 0.01 seconds
    Thread count was 1 (of 8 available processors)

    Solution count 1: 5

    Optimal solution found (tolerance 1.00e-04)
    Best objective 5.000000000000e+00, best bound 5.000000000000e+00, gap 0.0000%

```python
# Output reports

print("\n\n_________________________________________________________________________________")
print(f"The optimal circuit design:")
print("_________________________________________________________________________________")
for i in gates:
    if (NOR[i].x > 0.5):
        if (inputA[i].x + inputB[i].x > 0.5):
            print(f"NOR gate {i} is active, with external inputs A and B values of {inputA[i].x} and  {inputB[i].x}.")
        else:
            print(f"NOR gate {i} is active.")

```

    _________________________________________________________________________________
    The optimal circuit design:
    _________________________________________________________________________________
    NOR gate 1 is active.
    NOR gate 2 is active, with external inputs A and B values of 1.0 and  1.0.
    NOR gate 3 is active.
    NOR gate 6 is active, with external inputs A and B values of 0.0 and  1.0.
    NOR gate 7 is active, with external inputs A and B values of 1.0 and  0.0.

## References

H. Paul Williams, Model Building in Mathematical Programming, fifth edition.

Copyright © 2020 Gurobi Optimization, LLC

```python

```


```python
# Unit sphere R^3 as a submanifold of g3 in cartesian coordinates

# Make SymPy available to this program:
import sympy
from sympy import *

# Make GAlgebra available to this program:
from galgebra.ga import *
from galgebra.mv import *
from galgebra.printer import Fmt, GaPrinter, Format
    # Fmt:       sets the way that a multivector's basis expansion is output.
    # GaPrinter: makes GA output a little more readable.
    # Format:    turns on latex printer.
from galgebra.gprinter import gFormat, gprint
gFormat()
```

```python
# Unit sphere R^3 as a submanifold of g3 in cartesian coordinates

# g3: Base manifold.
g3coords = (x,y,z) = symbols('x y z', real=True)
g3 = Ga('\mathbf{e}', g=[1,1,1], coords=g3coords)
(ex, ey, ez) = g3.mv()

# sp2: Submanifold
sp2coords = (phi,th) = symbols('phi theta', real=True)
  # Parameterize unit sphere using the coordinates of g3:
sp2param = [sin(phi)*cos(th), sin(phi)*sin(th), cos(phi)]
  # Map the g3 coordinates of the sphere to its sp2 coordinates:
sp2 = g3.sm(sp2param, sp2coords, norm=True)  # "sm" is submanifold

(ephi, eth) = sp2.mv()
(rphi, rth) = sp2.mvr()

# Derivatives
grad  = sp2.grad
from galgebra.dop import *
pdphi = Pdop(phi)
pdth  = Pdop(th)
```

```python
grad = sp2.grad
grad
```

```python

```


# A model for stock return calculation

The purpose of this model is to help us estimate, what size a stock position need to have, to be considered lucrative given the boundary conditions (investment costs & market conditions).

### Annotations:
$R$: Return

$I$: Income

$E$: Expenditure

$_b$ or $_s$: buy or sell annotation

$n$: Number of stocks

$p$. Price per stock

$c$: Cost per position transaction (not per stock)

$m$: Total capital available

$r$: Maximum risk per position

$x$: Relative stock price change

$\Delta x$: Uncertainty for x

$<x>/<\Delta X>$: Average value of variable $x/\Delta x$

### Definitions

\begin{equation*}
R = I - E \tag{1}
\end{equation*}

- Assumption 1: $c_{b} \approx c_{s} = c $
\begin{equation*}
E = n_b \cdot p_{b} + c = \frac{m r - c}{p_{b}} p_{b} + c = m r \tag{2}
\end{equation*}

- Assumption 2: $p_{s} = (1+x)\cdot p_{b}$
\begin{equation*}
I = n_{b} \cdot p_{s} - c = \frac{m r - c}{p_{b}} p_{s} - c = \frac{m r - c}{p_{b}} (1+x)\cdot p_{b} - c\\ \tag{3}
I = (m r - c)\cdot(1+x) - c = (m r - c)\cdot(1+x) - c\\
\end{equation*}

This yields us the formula for the return:
\begin{equation}
R = (m r - c)\cdot(1+x) - c  - m r = \left[ (m r - c)\cdot x - 2c \right],
\end{equation}

having an uncertainty of

\begin{equation*}
\sigma^2_R = \left[ \Delta x \cdot (m r - c) \right]^2 \tag{4}
\end{equation*}

The ensemble return is given by:

$$<R_{tot}> = \sum_{i=1}^{n} R_{i} = n \cdot \left[ \left( m r - c\right) \cdot <x> - 2c \right], \tag{5}$$

- Assumption 3: Weighting relative stock price change by amount invested per position $w_i = mr - c$
$$<x> = \frac{\sum_{i=1}^{n} w_i \cdot x_i}{\sum_{i=1}^{n} w_i} = \frac{\sum_{i=1}^{n} (mr - c)\cdot x_i}{\sum_{i=1}^{n} (mr - c)} \tag{6}$$

with an uncertainty for the ensemble
\begin{equation*}
<\sigma^2_{R_{tot}}> = \sum_{i=1}^{n} \sigma^2_{R,i} = \left[ (m r - c) \cdot \sqrt{n} \cdot < \Delta x>\right] \tag{7}
\end{equation*}
$$<\Delta x> = \frac{\sum_{i=1}^{n} \Delta x_i \cdot (mr - c) }{\sum_{i=1}^{n} (mr - c)} \tag{7} $$

The risk is defined by (helps us to eliminate n or r in the above formulae):
\begin{equation*}
n = \frac{1}{r} \,\,\, , \,\, r \in{(0.005, 0.05)} \tag{8}
\end{equation*}

```python
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.colors import ListedColormap
from matplotlib.patches import Ellipse
from matplotlib.ticker import FixedLocator
%matplotlib inline
import numpy as np
```

```python
risk = 0.05 # maximum risk you are willing to take per position
n_pos = 1/risk
capital = 10000 # total amount of money
costs = 15 # transaction costs (buying, selling)
total_yield = 0.07 # yield = change in stock price + dividends
delta_total_yield = 0.01 # standard deviation on the yield

def R(capital, risk, cost, total_yield):
    """Expected return value"""
    return (capital * risk - cost) * total_yield - 2 * cost

def R2(capital, n_pos, cost, total_yield):
    """As R, independent of risk"""
    return (capital / n_pos - cost) * total_yield - 2 * cost

def dR(capital, risk, cost, delta_total_yield):
    """Expected uncertainty on the return value"""
    return (capital * risk - cost) * delta_total_yield

def dR2(capital, n_pos, cost, delta_total_yield):
    """As dR, independent of risk"""
    return (capital / n_pos - cost) * delta_total_yield

def R_tot(capital, risk, cost, total_yield_avg, n_pos):
    """Expected total return value"""
    return n_pos * ( (capital * risk - cost) * total_yield_avg - 2 * cost)

def R_tot2(capital, cost, total_yield_avg, n_pos):
    """As R_tot, independent of risk"""
    return ( (capital - cost * n_pos) * total_yield_avg - 2 * cost * n_pos)

def dR_tot(capital, risk, cost, delta_total_yield_avg, n_pos):
    """Expected uncertainty on R_tot"""
    return np.abs(np.sqrt(n_pos) * ( (capital * risk - cost
                    )*delta_total_yield_avg ))

def dR_tot2(capital, cost, delta_total_yield_avg, n_pos):
    """As dR_tot, independent of risk"""
    return np.abs(( (capital / np.sqrt(n_pos) - cost* np.sqrt(n_pos)
                    )*delta_total_yield_avg ))
```

```python
# Example values

print('ROI: {:.2f} - Delta ROI: {:.2f}'.format(
    R(capital, risk, costs, total_yield
       ), dR(capital, risk, costs, delta_total_yield)
))

print('Total ROI: {:.2f} - Delta Total ROI: {:.2f}'.format(
    R_tot(capital, risk, costs, total_yield, n_pos
         ), dR_tot(capital, risk, costs,
                            delta_total_yield, n_pos)))
```

    ROI: 3.95 - Delta ROI: 4.85
    Total ROI: 79.00 - Delta Total ROI: 21.69

# ROI

```python
risks, total_yields = np.meshgrid(
                     np.arange(0.01, 0.1, 0.001),
                     np.arange(-0.15, 0.15, 0.001) )
returns = R(capital, risks, costs, total_yields)

def r_to_c(r, capital=capital):
    return capital * r

def c_to_r(r, capital=capital):
    return r / capital

# We find, that the rule-of-thumb to invest up to 0.02 per position given the boundary conditions,
# does not become profitable, even under exceptionally good market conditions (changes > 7% p.a.)

fig, ax = plt.subplots(figsize=(18, 10))
seismic = cm.get_cmap('jet', 25)
newcolors = seismic(np.linspace(0, 1, 25))
newcolors[:15, :] = np.array([0.5, .5, .5, 0.2])
newcmp = ListedColormap(newcolors)
cs = ax.contour(risks, total_yields, returns, levels=25,
                cmap=cm.get_cmap('binary', 1))
cs = ax.contourf(risks, total_yields, returns, levels=25, cmap=newcmp,
                )
ax.add_patch(Ellipse([0.02, 0.07], 0.02, 0.14, edgecolor='k',
                     facecolor='black', fill=True, alpha=1))
ax.add_patch(Ellipse([0.02, 0.07], 0.018, 0.13, edgecolor='k',
                     facecolor='green', fill=True, alpha=1))

ax.text(0.015, 0.05, 'Benchmark', fontsize=15)

cbar = fig.colorbar(cs)
ax.set_xlabel('Risk per position')
ax.set_ylabel('Total yield')
ax.set_title('Return as function of risk and yield')
ax.set_xlim([0.01, 0.10])
ax.set_ylim([-0.15, 0.15])

secax = ax.secondary_xaxis('top',
        functions=(r_to_c, r_to_c), )
secax.get_xaxis().set_major_locator(FixedLocator(
    np.arange(0, 10000, 100)))
secax.set_xlabel('Investment per position')
```

# Delta ROI

```python
risks, delta_total_yields = np.meshgrid(
                     np.arange(0.01, 0.1, 0.001),
                     np.arange(0.01, 0.15, 0.001) )
delta_returns = dR(capital, risks, costs, delta_total_yields)

fig, ax = plt.subplots(figsize=(18, 10))
cs = ax.contour(risks, delta_total_yields, delta_returns,
                levels=25, cmap='gist_gray',
                )
cs = ax.contourf(risks, delta_total_yields, delta_returns,
                 levels=25, cmap='jet',
                )
cbar = fig.colorbar(cs)
ax.set_xlabel('Risk per position')
ax.set_ylabel('Delta total yield')
ax.set_title('Delta ROI')

secax = ax.secondary_xaxis('top',
        functions=(r_to_c, c_to_r), )
secax.get_xaxis().set_major_locator(FixedLocator(
    np.arange(0, 1100, 100)))
secax.set_xlabel('Investment per position')
```

# Total ROI

```python
ns, total_yields = np.meshgrid(
                     np.arange(10, 50, 1),
                     np.arange(0.0, 0.15, 0.01) )

returns_tot = R_tot2(capital, costs, total_yields, ns)

def n_to_c(x, capital=capital):
    return capital / x

def c_to_n(x, capital=capital):
    return x * capital

fig, ax = plt.subplots(figsize=(18, 10))

seismic = cm.get_cmap('jet', 25)
newcolors = seismic(np.linspace(0, 1, 25))
newcolors[:14, :] = np.array([0.5, .5, .5, 0.2])
newcmp = ListedColormap(newcolors)

cs = ax.contour(ns, total_yields, returns_tot,
                levels=25, cmap='gray',
                )
cs = ax.contourf(ns, total_yields, returns_tot,
                 levels=25, cmap=newcmp,
                )
cbar = fig.colorbar(cs)
ax.set_xlabel('No. of positions')
ax.set_ylabel('Total yield')
ax.set_title('Total ROIs')

secax = ax.secondary_xaxis('top',
        functions=(n_to_c, n_to_c)) # the inverse function does not work properly here
secax.get_xaxis().set_major_locator(FixedLocator(
    np.arange(0, 1100, 100)))
secax.set_xlabel('Investment per position')
```

# Delta Total ROI

```python
ns, delta_total_yields = np.meshgrid(
                     np.arange(10, 50, 1),
                     np.arange(0.01, 0.07, 0.001) )
delta_tot_returns = dR_tot2(capital, costs, delta_total_yields,
                            ns)

fig, ax = plt.subplots(figsize=(18, 10))
cs = ax.contour(ns, delta_total_yields, delta_tot_returns,
                levels=25, cmap='gist_gray',
                )
cs = ax.contourf(ns, delta_total_yields, delta_tot_returns,
                 levels=25, cmap='jet',
                )
cbar = fig.colorbar(cs)
ax.set_xlabel('No. of positions')
ax.set_ylabel('Average delta total yield')
ax.set_title('Delta total ROI')

secax = ax.secondary_xaxis('top',
        functions=(n_to_c, n_to_c), )
secax.get_xaxis().set_major_locator(FixedLocator(
    np.arange(0, 1100, 100)))
secax.set_xlabel('Investment per position')
```