Text provided under a Creative Commons Attribution license, CC-BY. Code under MIT license.
(c)2014 Lorena A. Barba, Olivier Mesnard. Thanks: NSF for support via CAREER award #1149784.
[@LorenaABarba](https://twitter.com/LorenaABarba)

##### Version 0.3 -- February 2015

# Vortex

Let's recap. In the first three lessons of _AeroPython_, you computed:

1. [a source-sink pair](http://nbviewer.ipython.org/github/barbagroup/AeroPython/blob/master/lessons/01_Lesson01_sourceSink.ipynb)—you learned how to make a `streamplot()` and use `scatter()` to mark the location of the singular points. If you did your challenge task, you saw that equipotential lines are perpendicular to streamlines.

2. [a source-sink pair in a free stream](http://nbviewer.ipython.org/github/barbagroup/AeroPython/blob/master/lessons/02_Lesson02_sourceSinkFreestream.ipynb), creating a _Rankine oval_. For the first time, we see that we can model the flow around an object using potential flow solutions. You also learned to define custom functions, to make Python work for you and look even more like plain English.

3. [a doublet](http://nbviewer.ipython.org/github/barbagroup/AeroPython/blob/master/lessons/03_Lesson03_doublet.ipynb)—this one is wonderful, because the stream-line pattern gives the flow around a circular cylinder (in two dimensions). You encountered the _D'Alembert paradox_ and hopefully that got you thinking. (If not, go back to that lesson and _think_!)

Did you also do the [assignment](http://nbviewer.ipython.org/github/barbagroup/AeroPython/blob/master/lessons/03_Lesson03_Assignment.ipynb)? It shows you how a *distribution of sources* can be used to model potential flow around an  airfoil. This starts looking like aerodynamics!

But what is the most important thing we want from applied aerodynamics? We want to make things fly, of course! And to fly, there must be a force of *aerodynamic lift* to counteract the weight of the object.

In this section of the course, we learn about lift. First, we will compute the flow of a potential vortex. It turns out, vortex circulation and lift are intimately related.

## What's a vortex?

This question is deeper than you might think! The simple answer is that a vortex is motion in circular streamlines. Imagine streamlines that are concentric circles about a given point—what's confusing is that this _does not mean_ that fluid elements are themselves rotating!

In an irrotational vortex, the tangential velocity is constant along a (circular) streamline and inversely proportional to the radius, while the radial velocity is zero. In polar coordinates:

\begin{equation}
u_\theta\left(r,\theta\right) = \frac{\text{constant}}{r}  \quad \text{,} \quad  u_r\left(r,\theta\right) = 0
\end{equation}

The vorticity is zero everywhere, except at the location of the point vortex, where the derivative of $u_\theta$ is infinite.

We introduced the concept of circulation in the first lesson ([Source & Sink](http://nbviewer.ipython.org/urls/github.com/barbagroup/AeroPython/blob/master/lessons/01_Lesson01_sourceSink.ipynb)). Let's use that. Around any circular streamline enclosing the vortex, and using the sign convention that a negative vortex circulates anti-clockwise, we have:

\begin{equation}\Gamma = -\oint \mathbf{v}\cdot d\vec{l} = -u_\theta 2 \pi r\end{equation}

Thus, the constant in the expression for $u_\theta$ in $(1)$ is equal to $\Gamma/2\pi$, and we now write:

\begin{equation}u_\theta\left(r,\theta\right) = \frac{\Gamma}{2\pi r}\end{equation}

We can get the stream function by integrating the velocity components:

\begin{equation}\psi\left(r,\theta\right) = \frac{\Gamma}{2\pi}\ln r\end{equation}

In Cartesian coordinates, the stream function is

\begin{equation}\psi\left(x,y\right) = \frac{\Gamma}{4\pi}\ln\left(x^2+y^2\right)\end{equation}

while the velocity components would be:

\begin{equation}u\left(x,y\right) = \frac{\Gamma}{2\pi}\frac{y}{x^2+y^2} \qquad v\left(x,y\right) = -\frac{\Gamma}{2\pi}\frac{x}{x^2+y^2}\end{equation}

This vortex flow is irrotational everywhere, except at the vortex center, where it is infinite. The strenth of the point vortex is equal to the circulation $\Gamma$ around it.

## Let's compute a vortex

The set-up is the same as before: we load our favorite libraries, and we create a grid of points to evaluate the velocity field.

```
import numpy
import math
from matplotlib import pyplot
```

```
N = 50                                # Number of points in each direction
x_start, x_end = -2.0, 2.0            # x-direction boundaries
y_start, y_end = -1.0, 1.0            # y-direction boundaries
x = numpy.linspace(x_start, x_end, N)    # computes a 1D-array for x
y = numpy.linspace(y_start, y_end, N)    # computes a 1D-array for y
X, Y = numpy.meshgrid(x, y)              # generates a mesh grid
```

Give your vortex a strength $\Gamma=5$ and place it at the center of your domain:

```
gamma = 5.0                      # strength of the vortex
x_vortex, y_vortex = 0.0, 0.0    # location of the vortex
```

We will define two functions,

* `get_velocity_vortex()` and
* `get_stream_function_vortex()`,

to compute the velocity components and the stream function on our Cartesian grid, given the strength and the location of the vortex. Then, we will use our custom functions to evaluate everything on the grid points. Let's write those functions first.

```
def get_velocity_vortex(strength, xv, yv, X, Y):
    """Returns the velocity field generated by a vortex.

    Arguments
    ---------
    strength -- strength of the vortex.
    xv, yv -- coordinates of the vortex.
    X, Y -- mesh grid.
    """
    u = + strength/(2*math.pi)*(Y-yv)/((X-xv)**2+(Y-yv)**2)
    v = - strength/(2*math.pi)*(X-xv)/((X-xv)**2+(Y-yv)**2)

    return u, v
```

```
def get_stream_function_vortex(strength, xv, yv, X, Y):
    """Returns the stream-function generated by a vortex.

    Arguments
    ---------
    strength -- strength of the vortex.
    xv, yv -- coordinates of the vortex.
    X, Y -- mesh grid.
    """
    psi = strength/(4*math.pi)*numpy.log((X-xv)**2+(Y-yv)**2)

    return psi
```

An now, call the functions with the vortex strength and position, plus the coordinates of the evaluation grid, to get the velocity and streamfunction of the vortex.

```
# computes the velocity field on the mesh grid
u_vortex, v_vortex = get_velocity_vortex(gamma, x_vortex, y_vortex, X, Y)

# computes the stream-function on the mesh grid
psi_vortex = get_stream_function_vortex(gamma, x_vortex, y_vortex, X, Y)
```

We are now able to visualize the streamlines of a vortex, and they look like concentric circles around the vortex center, as expected.

```
# plots the streamlines
%matplotlib inline

size = 10
pyplot.figure(figsize=(size, (y_end-y_start)/(x_end-x_start)*size))
pyplot.xlabel('x', fontsize=16)
pyplot.ylabel('y', fontsize=16)
pyplot.xlim(x_start, x_end)
pyplot.ylim(y_start, y_end)
pyplot.streamplot(X, Y, u_vortex, v_vortex, density=2, linewidth=1, arrowsize=1, arrowstyle='->')
pyplot.scatter(x_vortex, y_vortex, color='#CD2305', s=80, marker='o');
```

## Vortex & Sink

For fun, let's use our superposition powers. Add a vortex to a sink, using two new functions to compute the velocity components and the stream function of the sink, and adding to those of a vortex (remember that the sink can be easily replaced by a source by just changing the sign of the strength).

```
strength_sink = -1.0            # strength of the sink
x_sink, y_sink = 0.0, 0.0       # location of the sink
```

```
def get_velocity_sink(strength, xs, ys, X, Y):
    """Returns the velocity field generated by a sink.

    Arguments
    ---------
    strength -- strength of the sink.
    xs, ys -- coordinates of the sink.
    X, Y -- mesh grid.
    """
    u = strength/(2*math.pi)*(X-xs)/((X-xs)**2+(Y-ys)**2)
    v = strength/(2*math.pi)*(Y-ys)/((X-xs)**2+(Y-ys)**2)

    return u, v
```

```
def get_stream_function_sink(strength, xs, ys, X, Y):
    """Returns the stream-function generated by a sink.

    Arguments
    ---------
    strength -- strength of the sink.
    xs, ys -- coordinates of the sink.
    X, Y -- mesh grid.
    """
    psi = strength/(2*math.pi)*numpy.arctan2((Y-ys), (X-xs))

    return psi
```

```
# computes the velocity field on the mesh grid
u_sink, v_sink = get_velocity_sink(strength_sink, x_sink, y_sink, X, Y)

# computes the stream-function on the mesh grid
psi_sink = get_stream_function_sink(strength_sink, x_sink, y_sink, X, Y)
```

Now, let's visualize the streamlines of the vortex-sink, and admire our artistic creation:

```
# superposition of the sink and the vortex
u = u_vortex + u_sink
v = v_vortex + v_sink
psi = psi_vortex + psi_sink

# plots the streamlines
size = 10
pyplot.figure(figsize=(size, (y_end-y_start)/(x_end-x_start)*size))
pyplot.xlabel('x', fontsize=16)
pyplot.ylabel('y', fontsize=16)
pyplot.xlim(x_start, x_end)
pyplot.ylim(y_start, y_end)
pyplot.streamplot(X, Y, u, v, density=2, linewidth=1, arrowsize=1, arrowstyle='->')
pyplot.scatter(x_vortex, y_vortex, color='#CD2305', s=80, marker='o');
```

Very cool op-art. (And a good model for your typical bath-tub vortex.) But is all this useful? Yes!

First, you will take your training wheels off and—on your own—compute the flow of an [infinite row of vortices](http://nbviewer.ipython.org/github/barbagroup/AeroPython/blob/master/lessons/05_Lesson05_InfiniteRowOfVortices.ipynb). Your mission, should you choose to accept it, is in the next notebook.

After that, we will learn about the connection between a vortex, and the force of lift. This turns out to be very important in aerodynamics!

## What's this "irrotational" vortex thing?

I know what you are thinking.

What does it mean that the vortex is *irrotational*? Surely if there's a vortex, there's rotation!

You are not crazy. It's natural to think this way, but the potential vortex is a flow where streamlines are circular, yet fluid elements do not rotate around themselves—they just go around the circular path.

This classic video will help you understand ... just watch the 25 seconds of video after time 4m 25s, and see a "vorticity meter" go around a free vortex without rotating itself.

```
from IPython.display import YouTubeVideo
from datetime import timedelta

start=int(timedelta(hours=0, minutes=4, seconds=25).total_seconds())

YouTubeVideo("loCLkcYEWD4", start=start)
```

Remember: vorticity measures the local angular velocity of each fluid element. If the fluid elements go around a circular path, but do not spin themselves, there is no vorticity!

This animation from [Wikipedia](http://en.wikipedia.org/wiki/Vortex#Irrotational_vortices) helps further illustrate what happens in an irrotational vortex: the orange markers with a line across them are going around in circles, but they are not themselves rotating (notice the white lines keep their orientation).

## Next ...

The next IPython Notebook of the *AeroPython* series presents the exercise ["Infinite row of vortices,"](http://nbviewer.ipython.org/github/barbagroup/AeroPython/blob/master/lessons/05_Lesson05_InfiniteRowOfVortices.ipynb) as independent student work.

---
Please ignore the cell below. It just loads our style for the notebook.

```
from IPython.core.display import HTML
def css_styling():
    styles = open('../styles/custom.css', 'r').read()
    return HTML(styles)
css_styling()
```

<link href='http://fonts.googleapis.com/css?family=Fenix' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Source+Code+Pro:300,400' rel='stylesheet' type='text/css'>
<style>
    @font-face {
        font-family: "Computer Modern";
        src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
    }
    div.cell{
        width:800px;
        margin-left:16% !important;
        margin-right:auto;
    }
    h1 {
        font-family: 'Alegreya Sans', sans-serif;
    }
    h2 {
        font-family: 'Fenix', serif;
    }
    h3{
		font-family: 'Fenix', serif;
        margin-top:12px;
        margin-bottom: 3px;
       }
	h4{
		font-family: 'Fenix', serif;
       }
    h5 {
        font-family: 'Alegreya Sans', sans-serif;
    }
    div.text_cell_render{
        font-family: 'Alegreya Sans',Computer Modern, "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
        line-height: 135%;
        font-size: 120%;
        width:600px;
        margin-left:auto;
        margin-right:auto;
    }
    .CodeMirror{
            font-family: "Source Code Pro";
			font-size: 90%;
    }
/*    .prompt{
        display: None;
    }*/
    .text_cell_render h1 {
        font-weight: 200;
        font-size: 50pt;
		line-height: 100%;
        color:#CD2305;
        margin-bottom: 0.5em;
        margin-top: 0.5em;
        display: block;
    }
    .text_cell_render h5 {
        font-weight: 300;
        font-size: 16pt;
        color: #CD2305;
        font-style: italic;
        margin-bottom: .5em;
        margin-top: 0.5em;
        display: block;
    }

    .warning{
        color: rgb( 240, 20, 20 )
        }
</style>


# Slopes

```python
from IPython.core.display import HTML
css_file = 'https://raw.githubusercontent.com/ngcm/training-public/master/ipython_notebook_styles/ngcmstyle.css'
HTML(url=css_file)
```

<link href='http://fonts.googleapis.com/css?family=Open+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Nixie+One' rel='stylesheet' type='text/css'>
<style>

@font-face {
    font-family: "Computer Modern";
    src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
}

#notebook_panel { /* main background */
    background: rgb(245,245,245);
}

div.cell { /* set cell width */
    width: 1000px;
}

div #notebook { /* centre the content */
    background: #fff; /* white background for content */
    width: 1200px;
    margin: auto;
    padding-left: 0em;
}

#notebook li { /* More space between bullet points */
margin-top:0.8em;
}

/* draw border around running cells */
div.cell.border-box-sizing.code_cell.running {
    border: 1px solid #111;
}

/* Put a solid color box around each cell and its output, visually linking them*/
div.cell.code_cell {
    background-color: rgb(256,256,256);
    border-radius: 0px;
    padding: 0.5em;
    margin-left:1em;
    margin-top: 1em;
}

div.text_cell_render{
    font-family: 'Open Sans' sans-serif;
    line-height: 140%;
    font-size: 125%;
    font-weight: 400;
    width:900px;
    margin-left:auto;
    margin-right:auto;
}

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Arvo', serif;
    font-style:regular;
    font-weight: 400;
    font-size: 45pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.5em;
    margin-top: 0.5em;
    display: block;
}
.text_cell_render h2 {
    font-family: 'Arvo', serif;
    font-weight: 400;
    font-size: 30pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.1em;
    margin-top: 0.3em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'Arvo', serif;
    margin-top:16px;
	font-size: 22pt;
    font-weight: 600;
    margin-bottom: 3px;
    font-style: regular;
    color: rgb(102,102,0);
}

.text_cell_render h4 {    /*Use this for captions*/
    font-family: 'Arvo', serif;
    font-size: 14pt;
    text-align: center;
    margin-top: 0em;
    margin-bottom: 2em;
    font-style: regular;
}

.text_cell_render h5 {  /*Use this for small titles*/
    font-family: 'Arvo', sans-serif;
    font-weight: 400;
    font-size: 16pt;
    color: rgb(163,0,0);
    font-style: italic;
    margin-bottom: .1em;
    margin-top: 0.8em;
    display: block;
}

.text_cell_render h6 { /*use this for copyright note*/
    font-family: 'PT Mono', sans-serif;
    font-weight: 300;
    font-size: 9pt;
    line-height: 100%;
    color: grey;
    margin-bottom: 1px;
    margin-top: 1px;
}

.CodeMirror{
        font-family: "PT Mono";
        font-size: 90%;
}

</style>

$$
\newcommand{\dt}{\Delta t}
\newcommand{\udt}[1]{u^{({#1})}(T)}
\newcommand{\sm}[1]{s_m^{({#1})}}
$$
Let's suppose that instead of caring about the answer $u$ we care about the algorithm behaving in the right fashion, $\udt{\dt} = u(T) + c_1 \dt^{s_e}$.

Given three pieces of data we can measure the slope:

$$
\begin{align}
  s_m & = \log_2 \left| \frac{\udt{4\dt}-\udt{2\dt}}{\udt{2\dt}-\udt{\dt}} \right| \\
  & = \log_2 \left| \frac{u(T) + c_1 4^{s_e} \dt^{s_e} - u(T) + c_1 2^{s_e} \dt^{s_e} + {\cal O}(\dt^{s_e+1})}{u(T) + c_1 2^{s_e} \dt^{s_e} - u(T) + c_1 \dt^{s_e} + {\cal O}(\dt^{s_e+1)}} \right| \\
  & = \log_2 \left| 2^{s_e} + {\cal O}(\dt) \right| \\
  & = s_e + {\cal O}(\dt).
\end{align}
$$

So now we can apply the same Richardson extrapolation tricks to our measured slope. If the measured slope is denoted $\sm{\dt}$ then we can use Richardson extrapolation as before to compute

$$
\begin{align}
  s_R &= 2\sm{\dt} - \sm{2\dt}, \\ E_R & = s_R - \sm{\dt}.
\end{align}
$$

We then state that our algorithm is *close enough* to correct if $s_e$ lies in the interval $s_R \pm E_R$.

```python
from math import sin, cos, log, ceil
import numpy
from matplotlib import pyplot
%matplotlib inline
from matplotlib import rcParams
rcParams['font.family'] = 'serif'
rcParams['font.size'] = 16
```

```python
# model parameters:
g = 9.8      # gravity in m s^{-2}
v_t = 30.0   # trim velocity in m s^{-1}
C_D = 1/40.  # drag coefficient --- or D/L if C_L=1
C_L = 1.0    # for convenience, use C_L = 1

### set initial conditions ###
v0 = v_t     # start at the trim velocity (or add a delta)
theta0 = 0.0 # initial angle of trajectory
x0 = 0.0     # horizotal position is arbitrary
y0 = 1000.0  # initial altitude
```

```python
def f(u):
    """Returns the right-hand side of the phugoid system of equations.

    Parameters
    ----------
    u : array of float
        array containing the solution at time n.

    Returns
    -------
    dudt : array of float
        array containing the RHS given u.
    """

    v = u[0]
    theta = u[1]
    x = u[2]
    y = u[3]
    return numpy.array([-g*sin(theta) - C_D/C_L*g/v_t**2*v**2,
                      -g*cos(theta)/v + g/v_t**2*v,
                      v*cos(theta),
                      v*sin(theta)])
```

```python
def euler_step(u, f, dt):
    """Returns the solution at the next time-step using Euler's method.

    Parameters
    ----------
    u : array of float
        solution at the previous time-step.
    f : function
        function to compute the right hand-side of the system of equation.
    dt : float
        time-increment.

    Returns
    -------
    u_n_plus_1 : array of float
        approximate solution at the next time step.
    """

    return u + dt * f(u)
```

```python
T = 100.0
dt_values = numpy.array([0.001*2**i for i in range(-1,3)])
v_values = numpy.zeros_like(dt_values)
for i, dt in enumerate(dt_values):
    N = int(T/dt)+1
    t = numpy.linspace(0.0, T, N)
    u = numpy.empty((N, 4))
    u[0] = numpy.array([v0, theta0, x0, y0])
    for n in range(N-1):
        u[n+1] = euler_step(u[n], f, dt)
    v_values[i] = u[-1,0]
```

```python
s_m = numpy.zeros(2)
for i in range(2):
    s_m[i] = log(abs((v_values[2+i]-v_values[1+i])/(v_values[1+i]-v_values[0+i]))) / log(2.0)
    print("Measured convergence rate (base dt {}) is {:.7g}.".format(dt_values[i], s_m[i]))
```

    Measured convergence rate (base dt 0.0005) is 1.011621.
    Measured convergence rate (base dt 0.001) is 1.023266.

We then use Richardson extrapolation applied to the measured convergence rates (at finite resolution) to get the "*actual*" convergence rate and its accuracy:

```python
s_exact = 2*s_m[0]-s_m[1]
error_e = abs(s_exact - s_m[0])
print("The convergence rate lies in the interval [{:.7g},{:.7g}]".format(
        s_exact-error_e, s_exact+error_e))
```

    The convergence rate lies in the interval [0.9883297,1.011621]

We see that $\sm{\dt} \simeq 1.023$ is *close enough* when $\sm{\dt/2} \simeq 1.012$, as the interval is consistent with $s_e=1$.

## Finding the limits

Once again, we want to know what the limiting values are. In this case we're interested in fixing $s_e$ and $\sm{\dt}$, and finding the value of $\sm{\dt/2}$ that is close enough.

So the model we'll use is that $s_m = s_e + c_1 \dt$, as above, so we need

$$
\begin{equation}
  2 \sm{\dt/2} - \sm{\dt} - | \sm{\dt/2} - \sm{\dt} | \le s_e \le 2 \sm{\dt/2} - \sm{\dt} + | \sm{\dt/2} - \sm{\dt} |.
\end{equation}
$$

In the case where $\sm{\dt/2} < \sm{\dt}$  this gives

$$
\begin{equation}
  3 \sm{\dt/2} - 2\sm{\dt} \le s_e \le \sm{\dt/2} \quad \implies \quad \sm{\dt/2} \le \tfrac{1}{3} \left( 2 \sm{\dt} + s_e \right).
\end{equation}
$$

In the case where $\sm{\dt/2} > \sm{\dt}$  this gives

$$
\begin{equation}
  \sm{\dt/2} \le s_e \le 3 \sm{\dt/2} - 2 \sm{\dt} \quad \implies \quad \sm{\dt/2} \ge \tfrac{1}{3} \left( 2 \sm{\dt} + s_e \right).
\end{equation}
$$

We can write this in terms of the *error* in the slope as

$$
\begin{equation}
  | \sm{\dt/2} - s_e | \le \frac{2}{3} | \sm{\dt} - s_e |;
\end{equation}
$$

that is, the error must go down by at least a factor of $2/3$ when the step size is cut in half.

So, if we fix $s_e=1$ (as for Euler's method), we can see the *upper* bound on $\sm{\dt/2}$ given $s_m = 1.023$ is:

```python
s_e = 1.0
s_mdthalf_max = (2.0*s_m[1] + s_e)/3.0
print("Upper bound when s_e=1 and s_m (coarse resolution)=1.023: {:.7f}.".format(s_mdthalf_max))
```

    Upper bound when s_e=1 and s_m (coarse resolution)=1.023: 1.0155107.

In general, for Euler's method, the bound varies linearly:

```python
s_m_values = numpy.linspace(0.5, 1.5)
bound = (2.0*s_m_values + 1.0)/3.0
pyplot.figure(figsize=(8,6))
pyplot.fill_between(s_m_values, numpy.ones_like(s_m_values), bound,
                    facecolor='green', alpha=0.5)
#pyplot.ylim(1.0, 2.5)
pyplot.xlim(0.5, 1.5)
pyplot.xlabel(r"$s_m^{(\Delta t)}$")
pyplot.ylabel(r"$s_m^{(\Delta t / 2)}$");
```

## Summary

If we want the algorithm to behave in the way expected of Euler's method, then measuring a single convergence rate is useful but not sufficient. Instead we must measure the convergence rate twice, once with base timestep $\dt$ and once with base timestep $\dt/2$. The difference between the measured slope $\sm{\dt}$ and the expected slope $s_e=1$ must go down by at least $2/3$ when the timestep is reduced to $\dt/2$.


_Lambda School Data Science_

# Ordinary Least Squares Regression

## What is Linear Regression?

Linear Regression is a statistical model that seeks to describe the relationship between some y variable and one or more x variables.

In the simplest case, linear regression seeks to fit a straight line through a cloud of points. This line is referred to as the "regression line" or "line of best fit." This line tries to summarize the relationship between our X and Y in a way that enables us to use the equation for that line to make predictions.

### Synonyms for "y variable"
- Dependent Variable
- Response Variable
- Outcome Variable
- Predicted Variable
- Measured Variable
- Explained Variable
- Label
- Target

### Synonyms for "x variable"
- Independent Variable
- Explanatory Variable
- Regressor
- Covariate
- Feature

# Simple Linear Regresion (bivariate)

## Making Predictions

Say that we were trying to create a model that captured the relationship between temperature outside and ice cream sales. In Machine Learning our goal is often different that of other flavors of Linear Regression Analysis, because we're trying to fit a model to this data with the intention of making **predictions** on new data (in the future) that we don't have yet.

## What are we trying to predict?

So if we had measured ice cream sales and the temprature outside on 11 different days, at the end of our modeling **what would be the thing that we would want to predict? - Ice Cream Sales or Temperature?**

We would probably want to be measuring temperature with the intention of using that to **forecast** ice cream sales. If we were able to successfully forecast ice cream sales from temperature, this might help us know beforehand how much ice cream to make or how many cones to buy or on which days to open our store, etc. Being able to make predictions accurately has a lot of business implications. This is why making accurate predictions is so valuable (And in large part is why data scientists are paid so well).

### Y Variable Intuition

We want the thing that we're trying to predict to serve as our **y** variable. This is why it's sometimes called the "predicted variable." We call it the "dependent" variable because our prediction for how much ice cream we're going to sell "depends" on the temperature outside.

### X Variable Intuition

All other variables that we use to predict our y variable (we're going to start off just using one) we call our **x** variables. These are called our "independent" variables because they don't *depend* on y, they "explain" y. Hence they are also referred to as our "explanatory" variables.

```python
%matplotlib inline
from ipywidgets import interact
from matplotlib.patches import Rectangle
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
import statsmodels.api as sm
```

```python
columns = ['Year','Incumbent Party Candidate','Other Candidate','Incumbent Party Vote Share']

data = [[1952,"Stevenson","Eisenhower",44.6],
        [1956,"Eisenhower","Stevenson",57.76],
        [1960,"Nixon","Kennedy",49.91],
        [1964,"Johnson","Goldwater",61.34],
        [1968,"Humphrey","Nixon",49.60],
        [1972,"Nixon","McGovern",61.79],
        [1976,"Ford","Carter",48.95],
        [1980,"Carter","Reagan",44.70],
        [1984,"Reagan","Mondale",59.17],
        [1988,"Bush, Sr.","Dukakis",53.94],
        [1992,"Bush, Sr.","Clinton",46.55],
        [1996,"Clinton","Dole",54.74],
        [2000,"Gore","Bush, Jr.",50.27],
        [2004,"Bush, Jr.","Kerry",51.24],
        [2008,"McCain","Obama",46.32],
        [2012,"Obama","Romney",52.00]]

df = pd.DataFrame(data=data, columns=columns)
```

```python
df
```

```python
df.plot(x='Year', y='Incumbent Party Vote Share');
```

```python
df['Incumbent Party Vote Share'].describe()
```

```python
target = 'Incumbent Party Vote Share'
df['Prediction'] = df[target].mean()
df['Error'] = df['Prediction'] - df[target]
```

```python
df
```

```python
df['Error'].sum()
```

```python
df['Absolute Error'] = df['Error'].abs()
df['Absolute Error'].sum()
```

```python
df['Absolute Error'].mean()
```

```python
mean_absolute_error(y_true=df[target], y_pred=df['Prediction'])
```

```python
r2_score(y_true=df[target], y_pred=df['Prediction'])
```

```python
columns = ['Year','Average Recent Growth in Personal Incomes']

data = [[1952,2.40],
        [1956,2.89],
        [1960, .85],
        [1964,4.21],
        [1968,3.02],
        [1972,3.62],
        [1976,1.08],
        [1980,-.39],
        [1984,3.86],
        [1988,2.27],
        [1992, .38],
        [1996,1.04],
        [2000,2.36],
        [2004,1.72],
        [2008, .10],
        [2012, .95]]

growth = pd.DataFrame(data=data, columns=columns)
```

```python
df = df.merge(growth)
df
```

```python
feature = 'Average Recent Growth in Personal Incomes'
```

```python
df.plot(x=feature, y=target, kind='scatter');
```

We can see from the scatterplot that these data points seem to follow a somewhat linear relationship. This means that we could probably summarize their relationship well by fitting a line of best fit to these points. Lets do it.

## The Equation for a Line

As we know a common equation for a line is:

\begin{align}
y = mx + b
\end{align}

Where $m$ is the slope of our line and $b$ is the y-intercept.

If we want to plot a line through our cloud of points we figure out what these two values should be. Linear Regression seeks to **estimate** the slope and intercept values that describe a line that best fits the data points.

```python
m = 4
b = 44
df['Prediction'] = m * df[feature] + b
df['Error'] = df['Prediction'] - df[target]
df['Absolute Error'] = df['Error'].abs()
df['Absolute Error'].sum()
```

```python
df['Absolute Error'].mean()
```

```python
r2_score(y_true=df[target], y_pred=df['Prediction'])
```

```python
ax = df.plot(x=feature, y=target, kind='scatter')
df.plot(x=feature, y='Prediction', kind='line', ax=ax);
```

```python
def regression(m, b):
    df['Prediction'] = m * df[feature] + b
    df['Error'] = df['Prediction'] - df[target]
    df['Absolute Error'] = df['Error'].abs()
    sum_absolute_error = df['Absolute Error'].sum()

    title = f'Sum of absolute errors: {sum_absolute_error}'
    ax = df.plot(x=feature, y=target, kind='scatter', title=title, figsize=(7, 7))
    df.plot(x=feature, y='Prediction', kind='line', ax=ax)

regression(m=4, b=48)
```

## Residual Error

The residual error is the distance between points in our dataset and our regression line.

```python
def regression(m, b):
    df['Prediction'] = m * df[feature] + b
    df['Error'] = df['Prediction'] - df[target]
    df['Absolute Error'] = df['Error'].abs()
    sum_absolute_error = df['Absolute Error'].sum()

    title = f'Sum of absolute errors: {sum_absolute_error}'
    ax = df.plot(x=feature, y=target, kind='scatter', title=title, figsize=(7, 7))
    df.plot(x=feature, y='Prediction', kind='line', ax=ax)

    for x, y1, y2 in zip(df[feature], df[target], df['Prediction']):
        ax.plot((x, x), (y1, y2), color='grey')

regression(m=3, b=46)
```

```python
interact(regression, m=(-10,10,0.5), b=(40,60,0.5));
```

```python
df
```

```python
df['Square Error'] = df['Error'] **2
```

```python
df
```

```python
def regression(m, b):
    df['Prediction'] = m * df[feature] + b
    df['Error'] = df['Prediction'] - df[target]
    df['Absolute Error'] = df['Error'].abs()
    df['Square Error'] = df['Error'] **2
    sum_square_error = df['Square Error'].sum()

    title = f'Sum of square errors: {sum_square_error}'
    ax = df.plot(x=feature, y=target, kind='scatter', title=title, figsize=(7, 7))
    df.plot(x=feature, y='Prediction', kind='line', ax=ax)

    xmin, xmax = ax.get_xlim()
    ymin, ymax = ax.get_ylim()
    scale = (xmax-xmin)/(ymax-ymin)

    for x, y1, y2 in zip(df[feature], df[target], df['Prediction']):
        bottom_left = (x, min(y1, y2))
        height = abs(y1 - y2)
        width = height * scale
        ax.add_patch(Rectangle(xy=bottom_left, width=width, height=height, alpha=0.1))
```

```python
interact(regression, m=(-10,10,0.5), b=(40,60,0.5));
```

```python
b = 46
ms = np.arange(-10,10,0.5)
sses = []

for m in ms:
    predictions = m * df[feature] + b
    errors = predictions - df[target]
    square_errors = errors ** 2
    sse = square_errors.sum()
    sses.append(sse)

hypotheses = pd.DataFrame({'Slope': ms})
hypotheses['Intercept'] = b
hypotheses['Sum of Square Errors'] = sses

hypotheses
```

```python
hypotheses.plot(x='Slope', y='Sum of Square Errors',
                title=f'Intercept={b}');
```

```python
X = df[[feature]]
y = df[target]

X.shape, y.shape
```

```python
model = LinearRegression()
model.fit(X, y)
```

```python
model.coef_, model.intercept_
```

```python
model.predict([[1]])
```

```python
model.predict([[2]])
```

```python
model.predict([[3]])
```

```python
model.predict(X)
```

```python
df['Prediction'] = model.predict(X)
```

```python
ax = df.plot(x=feature, y=target, kind='scatter', title='sklearn LinearRegression')
df.plot(x=feature, y='Prediction', kind='line', ax=ax);
```

```python
df['Error'] = df['Prediction'] - y
```

```python
df['Absolute Error'] = df['Error'].abs()
df['Square Error'] = df['Error'] ** 2
```

```python
df['Square Error'].mean()
```

```python
mean_squared_error(y_true=y, y_pred=model.predict(X))
```

```python
np.sqrt(mean_squared_error(y, model.predict(X)))
```

# R Squared:  $R^2$

One final attribute of linear regressions that we're going to talk about today is a measure of goodness of fit known as $R^2$ or R-squared. $R^2$ is a statistical measure of how close the data are fitted to our regression line. A helpful interpretation for the $R^2$ is the percentage of the dependent variable that is explained by the model.

In other words, the $R^2$ is the percentage of y that is explained by the x variables included in the model. For this reason the $R^2$ is also known as the "coefficient of determination," because it explains how much of y is explained (or determined) by our x varaibles. We won't go into the calculation of $R^2$ today, just know that a higher $R^2$ percentage is nearly always better and indicates a model that fits the data more closely.

```python
model.score(X, y)
```

```python
r2_score(y, model.predict(X))
```

### Statsmodels

https://www.statsmodels.org/dev/examples/notebooks/generated/ols.html

```python
model = sm.OLS(y, sm.add_constant(X))
results = model.fit()
print(results.summary())
```

# The Anatomy of Linear Regression

- Intercept: The $b$ value in our line equation $y=mx+b$
- Slope: The $m$ value in our line equation $y=mx+b$. These two values together define our regression line.

- $\hat{y}$ : A prediction
- Line of Best Fit (Regression Line)
- Predicted (fitted) Values: Points on our regression line
- Observed Values: Points from our dataset
- Error: The distance between predicted and observed values.

# More Formal Notation

We have talked about a line of regression being represented like a regular line $y=mx+b$ but as we get to more complicated versions we're going to need to extend this equation. So lets establish the proper terminology.

**X** - Independent Variable, predictor variable, explanatory variable, regressor, covariate

**Y** - Response variable, predicted variable, measured vairable, explained variable, outcome variable

$\beta_0$ - "Beta Naught" or "Beta Zero", the intercept value. This is how much of y would exist if X were zero. This is sometimes represented by the letter "a" but I hate that. So it's "Beta 0" during my lecture.

$\beta_1$ - "Beta One" The primary coefficient of interest. This values is the slope of the line that is estimated by "minimizing the sum of the squared errors/residuals" - We'll get to that.

$\epsilon$ - "Epsilon" The "error term", random noise, things outside of our model that affect y.

# How Does it do it?

## Minimizing the Sum of the Squared Error

The most common method of estimating our $\beta$ parameters  is what's known as "Ordinary Least Squares" (OLS). (There are different methods of arriving at a line of best fit). OLS estimates the parameters that minimize the squared distance between each point in our dataset and our line of best fit.

\begin{align}
SSE = \sum(y_i - \hat{y})^2
\end{align}

## Linear Algebra!

The same result that is found by minimizing the sum of the squared errors can be also found through a linear algebra process known as the "Least Squares Solution:"

Before we can work with this equation in its linear algebra form we have to understand how to set up the matrices that are involved in this equation.

### The $\beta$ vector

The $\beta$ vector represents all the parameters that we are trying to estimate, our $y$ vector and $X$ matrix values are full of data from our dataset. The $\beta$ vector holds the variables that we are solving for: $\beta_0$ and $\beta_1$

Now that we have all of the necessary parts we can set them up in the following equation:

\begin{align}
y = X \beta + \epsilon
\end{align}

Since our $\epsilon$ value represents **random** error we can assume that it will equal zero on average.

\begin{align}
y = X \beta
\end{align}

The objective now is to isolate the $\beta$ matrix. We can do this by pre-multiplying both sides by "X transpose" $X^{T}$.

\begin{align}
X^{T}y =  X^{T}X \beta
\end{align}

Since anything times its transpose will result in a square matrix, if that matrix is then an invertible matrix, then we should be able to multiply both sides by its inverse to remove it from the right hand side. (We'll talk tomorrow about situations that could lead to $X^{T}X$ not being invertible.)

\begin{align}
(X^{T}X)^{-1}X^{T}y =  (X^{T}X)^{-1}X^{T}X \beta
\end{align}

Since any matrix multiplied by its inverse results in the identity matrix, and anything multiplied by the identity matrix is itself, we are left with only $\beta$ on the right hand side:

\begin{align}
(X^{T}X)^{-1}X^{T}y = \hat{\beta}
\end{align}

We will now call it "beta hat" $\hat{\beta}$ because it now represents our estimated values for $\beta_0$ and $\beta_1$

### Lets calculate our $\beta$ coefficients with numpy!

```python
X = sm.add_constant(df[feature]).values
print('X')
print(X)

y = df[target].values[:, np.newaxis]
print('y')
print(y)

X_transpose = X.T
print('X Transpose')
print(X_transpose)

X_transpose_X = X_transpose @ X
print('X Transpose X')
print(X_transpose_X)

X_transpose_X_inverse = np.linalg.inv(X_transpose_X)
print('X Transpose X Inverse')
print(X_transpose_X_inverse)

X_transpose_y = X_transpose @ y
print('X Transpose y')
print(X_transpose_y)

beta_hat = X_transpose_X_inverse @ X_transpose_y
print('Beta Hat')
print(beta_hat)
```

# Multiple Regression

Simple or bivariate linear regression involves a single $x$ variable and a single $y$ variable. However, we can have many $x$ variables. A linear regression model that involves multiple x variables is known as **Multiple** Regression - NOT MULTIVARIATE!

```python
df.sort_values(by='Square Error', ascending=False)
```

```python
"""
Fatalities denotes the cumulative number of American military
fatalities per millions of US population the in Korea, Vietnam,
Iraq and Afghanistan wars during the presidential terms
preceding the 1952, 1964, 1968, 1976 and 2004, 2008 and
2012 elections.

http://www.douglas-hibbs.com/HibbsArticles/HIBBS-PRESVOTE-SLIDES-MELBOURNE-Part1-2014-02-26.pdf
"""

columns = ['Year','US Military Fatalities per Million']

data = [[1952,190],
        [1956,  0],
        [1960,  0],
        [1964,  1],
        [1968,146],
        [1972,  0],
        [1976,  2],
        [1980,  0],
        [1984,  0],
        [1988,  0],
        [1992,  0],
        [1996,  0],
        [2000,  0],
        [2004,  4],
        [2008, 14],
        [2012,  5]]

deaths = pd.DataFrame(data=data, columns=columns)
```

```python
df = df.merge(deaths)
```

```python
features = ['Average Recent Growth in Personal Incomes',
            'US Military Fatalities per Million']

target = 'Incumbent Party Vote Share'

X = df[features]
y = df[target]

model = sm.OLS(y, sm.add_constant(X))
results = model.fit()
print(results.summary())
```

```python
model = LinearRegression()
model.fit(X, y)
print('Intercept:', model.intercept_)
pd.Series(model.coef_, features)
```

```python
np.sqrt(mean_squared_error(y, model.predict(X)))
```

# Train / Test Split

```python
train = df.query('Year < 2008')
test  = df.query('Year >= 2008')
X_train = train[features]
y_train = train[target]
X_test  = test[features]
y_test  = test[target]
X_train.shape, y_train.shape, X_test.shape, y_test.shape
```

```python
model.fit(X_train, y_train)
model.predict(X_test)
```

```python
test
```

### More about the "Bread & Peace" model
- https://fivethirtyeight.com/features/what-do-economic-models-really-tell-us-about-elections/
- https://statmodeling.stat.columbia.edu/2007/12/15/bread_and_peace/
- https://avehtari.github.io/RAOS-Examples/ElectionsEconomy/hibbs.html
- https://douglas-hibbs.com/
- http://www.douglas-hibbs.com/HibbsArticles/HIBBS-PRESVOTE-SLIDES-MELBOURNE-Part1-2014-02-26.pdf

# Dimensionality in Linear Regression!

Muliple Regression is simply an extension of the bivariate case. The reason why we see the bivariate case demonstrated so often is simply because it's easier to graph and all of the intuition from the bivariate case is the same as we keep on adding explanatory variables.

As we increase the number of $x$ values in our model we are simply fitting a n-1-dimensional plane to an n-dimensional cloud of points within an n-dimensional hypercube.

# Interpreting Coefficients

One of Linear Regression's strengths is that the parameters of the model (coefficients) are readily interpretable and useful. Not only do they describe the relationship between x and y but they put a number on just how much x is associated with y. We should be careful to not speak about this relationshiop in terms of causality because these coefficients are in fact correlative measures. We would need a host of additional techniques in order to estimate a causal effect using linear regression (econometrics).

\begin{align}
\hat{\beta} = \frac{Cov(x,y)}{Var(y)}
\end{align}

Going back to the two equations for the two models that we have estimated so far, lets replace their beta values with their actual values to see if we can make sense of how to interpret these beta coefficients.

## Bivariate Model

$y_i = \beta_0 + \beta_1temperature + \epsilon$

$sales_i = -596.2 + 24.69temperature + \epsilon$

What might $\beta_0$ in this model represent? It represents the level of sales that we would have if temperature were 0. Since this is negative one way of interpreting it is that it's so cold outside that you would have to pay people to eat ice cream. A more appropriate interpretation is probably that the ice cream store owner should close his store down long before the temperature reaches 0 degrees farenheit (-17.7 celsius). The owner can compare his predicted sales with his costs of doing business to know how warm the weather has to get before he should open his store.

What might the $beta_1$ in this model reprsent? it represents the increase in sales for each degree of temperature increase. For every degree that the temperature goes up outside he has $25 more in sales.

## Multiple Regression Model

$y_i = \beta_0 + \beta_1age_i + \beta_2weight_i + \epsilon$

$BloodPressure_i = 30.99+ .86age_i + .33weight_i + \epsilon$

The interpretation of coefficients in this example are similar. The intercept value repesents the blood pressure a person would have if they were 0 years old and weighed 0 pounds. This not a super useful interpretation. If we look at our data it is unlikely that we have any measurements like these in the dataset. This means that our interpretation of our intercept likely comes from extrapolating the regression line (plane). Coefficients having straightforward interpretations is a strength of linear regression if we're careful about extrapolation and only interpreting our data within the context that it was gathered.

The interpretation of our other coefficients can be a useful indicator for how much a person similar to those in our dataset's blood pressure will go up on average with each additional year of age and pound of weight.

# Basic Model Validation

One of the downsides of relying on $R^2$ too much is that although it tells you when you're fitting the data well, it doesn't tell you when you're *overfitting* the data. The best way to tell if you're overfitting the data is to get some data that your model hasn't seen yet, and evaluate how your predictions do. This is essentially what "model validation" is.

# Why is Linear Regression so Important?

## Popularity

Linear Regression is an extremely popular technique that every data scientist **needs** to understand. It's not the most advanced technique and there are supervised learning techniques that will obtain a higher accuracy, but where it lacks in accuracy it makes up for it in interpretability and simplicity.

## Interpretability

Few other models possess coefficients that are so directly linked to their variables with a such a clear interpretation. Tomorrow we're going to learn about ways to make them even easier to interpret.

## Simplicity

A linear regression model can be communicated just by writing out its equation. It's kind of incredible that such high dimensional relationships can be described from just a linear combination of variables and coefficients.


# Engineering Economy Epic Cheat Sheet
---

```python
!cp drive/MyDrive/Economy/A_F.py .
!cp drive/MyDrive/Economy/A_P.py .
!cp drive/MyDrive/Economy/G.py .
!cp drive/MyDrive/Economy/P_F.py .
!cp drive/MyDrive/Economy/ie.py .
!pip3 install -q matplotlib==3.4.2
```

```!pip3 install -q matplotlib==3.4.2``` ***might give error, in order to fix it, restart runtime and run it again.***

Import core files to your drive, and if needed change the previous cell.

```python
from A_F import *
from A_P import *
from G import *
from P_F import *
from ie import *

import sympy
from sympy import *
from sympy.solvers import solve
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
from IPython.display import display, Latex
import pandas as pd
import numpy as np

```

Here you can see each core function, remember that i should be $0 <= i <= 1$.

- Functions in **A_F.py**:
  >- A_F(F, i, n):
  $F(A/F, i, n)$

  >- F_A(A, i, n):
  $A(F/A, i, n)$

  >- F_Aj(A1, i, j, n):
  $A_1(F/A, i, j, n)$

- Functions in **A_P.py**:
  >- A_P(P, i, n, inf=False):
  $P(A/P, i, n)$

  >- P_A(A, i, n, inf=False):
  $A(P/A, i, n)$

  >- P_Aj(A1, i, j, n):
  $A_1(P/A, i, j, n)$

- Functions in **G.py**:
  >- P_G(G, i, n):
  $G(P/G, i, n)$

  >- A_G(G, i, n):
  $G(P/G, i, n)$

- Functions in **ie.py**:
  >- ie(r, t): $(1 + \dfrac{r}{t})^t - 1$

- Functions in **P_F.py**:
  >- P_F(F, i, n): $F(P/F, i, n)$
  >- F_P(P, i, n): $P(F/P, i, n)$

You can try them out in the next cell.

```python
print(P_G(250, 0.24, 30) + P_A(1000, 0.24, 30))
print(F_P(8444.31, 0.24, 30))
print(A_F(5360616.30, 0.02, 240))
```

    8444.317323931356
    5360616.307938108
    933.1839701888509

```python
def GeoMean(rs):
  p = 1
  for r in rs:
    p *= (1 + r)
  r = p ** (1/len(rs)) - 1
  return r
```

To calcualte Geometric mean, use GeoMean(rs) function:

**GeoMean(rs = [r1, r2, r3 ..., rn])** = $r_g = \sqrt[n]{(1 + r_1) (1 + r_2) (1 + r_3) ... (1 + r_n)}$

Here give it a try:

```python
print(GeoMean([1]))
print(GeoMean([1, 1]))
print(GeoMean([2, 1]))
```

    1.0
    1.0
    1.4494897427831779

```python
def SolveRoR(eq, i):
  shouldBreak = False
  start = 0.01
  while shouldBreak == False:
    if start > 1:
      break
    try:
      print("trying ", start)
      i = nsolve(eq, i, (start, 0.99))
      shouldBreak = True
    except:
      shouldBreak = False
      start += 0.01
  return i
```

You can use SolveRoR(eq, i) to calculate i, instead of looking it up in the i's tables of the book.
Let's find the *i* for the corresponding formula:

$30000 = 8000(P/A, i, 5) + 7500(P/F, i, 5)$

```python
i = Symbol("i")
i = SolveRoR(-30000 + P_A(8000.0, i, 5) + P_F(7500, i, 5), i)
print(-30000 + P_A(8000.0, i, 5) + P_F(7500, i, 5),"\ni =", i)
```

    trying  0.01
    7.73070496506989e-12
    i = 0.156949623919420

### Ok, so far with few lines of code you're good to go from chapter 1 till the end of chapter 10.

#Chapter 6 Rate of Return

You can calculate the most economical project based on RoR in 2 methods:
1. Plot, use RoRDraw(PWs)
2. Compute, use RoRCompute(PWs, n). **This is my preferred method**.

```python
def RoRDraw(PWs):
  PWs = dict(sorted(PWs.items(), key=lambda x: x[1][0]))
  # print(PWs)
  PWBs = [PW[1] for PW in PWs.values()]
  PWCs = [PW[0] for PW in PWs.values()]
  labels = PWs.keys()
  # print(labels)
  fig, ax = plt.subplots(figsize=(13,13))
  ax.scatter(PWCs, PWBs)

  m =  1
  ax.axline([0, 0], slope=m, color='pink')
  for i, label in enumerate(labels):
    ax.annotate(label, (PWCs[i], PWBs[i]))
  for i in range(1, len(labels)):
    ax.plot(PWCs[i-1:i+1], PWBs[i-1:i+1])
    ax.annotate(((PWBs[i] - PWBs[i-1]) / (PWCs[i] - PWCs[i-1])) \
               ,((PWCs[i-1] + PWCs[i]) / 2, (PWBs[i-1] + PWBs[i]) / 2))
```

```python
def RoRCompute(PWs, n):
  PWs = dict(sorted(PWs.items(), key=lambda x: x[1][0]))
  # print(PWs)
  PWBs = [PW[1] for PW in PWs.values()]
  PWCs = [PW[0] for PW in PWs.values()]
  labels = list(PWs.keys())
  # print(labels)
  table = {'index': [], 'RoR': []}
  for j in range(1, len(labels)):
    table['index'].append(labels[j - 1] + '-' + labels[j])
    i = Symbol("i")
    P = PWCs[j] - PWCs[j-1]
    A = PWBs[j] - PWBs[j-1]
    i = SolveRoR((P / A) - P_A(1, i, n), i)
    table['RoR'].append(i)
  display(pd.DataFrame(table))
```

Let's check some examples:

```python
PWs = {'A': (269, 647), 'B': (652, 818)} # (PWc, PWb)

RoRDraw(PWs)
RoRCompute(PWs, 6) # n is 6 here, it's set based on the question
```

A is the most economical project.

```python
PWs = {'A': (2000, 4703), 'B': (4000, 7329), 'C': (5000, 8029)} # (PWc, PWb)

RoRDraw(PWs)
RoRCompute(PWs, 20) # n is 20 here, it's set based on the question
```

B is the most economical project.

```python
PWs = {'A': (2000, 4100), 'B': (4000, 6390), 'C': (1000, 1170), 'D': (6000, 7610), 'E': (9000, 7850)} # (PWc, PWb)

RoRDraw(PWs)
RoRCompute(PWs, 20) # n is 20 here, it's set based on the question
```

B is the most economical project.

###Rest of the codes are for chapter 11 to chapter 14.

#Chapter 11 Decline
---

In the following cells, you can see each decline type implemented in a class, but all of them are drived from DeclineBase.

```python
class DeclineBase:
  def __init__(self, P, n):
    self.P = P
    self.n = n

  def GetD(self, m):
    pass
  def BV(self, m):
    pass
  def GetTable(self):
    table = {"m": [], "Dm": [], "BVm": []}
    for i in range(self.n + 1):
      table["m"].append(i)
      table["Dm"].append(self.GetD(i))
      table["BVm"].append(self.BV(i))
    df = pd.DataFrame(table)
    display(df)
  def GetDs(self):
    return [self.GetD(i) for i in range(1, self.n + 1)]
  def GetPWD(self, i):
    return sum([P_F(self.GetD(m), i, m) for m in range(1, self.n + 1)])
  def GetEUA(self, i):
    return A_P(self.GetPWD(i), i, self.n)
  def GetTaxSaving(self, TR, i):
    return TR * self.GetPWD(i)
```

```python
class DeclineNull(DeclineBase):
  def __init__(self, P, n, SV):
    super().__init__(P, n)
    self.SV = SV

    sP = Symbol('P')
    sSV = Symbol('SV')
    sn = Symbol('n')
    sm = Symbol('m')
    sD = Symbol('D')
    self.DFormula = eval("(sP - sSV) / sn")
    self.BVFormula = eval("sP - sm*sD")

  def GetD(self, m):
    return 0

  def BV(self, m):
    D = self.GetD(m)
    BV = self.P - m * D
    display(Latex(f'$BV_{m}={sympy.latex(self.BVFormula)} = {BV}$'))
    return BV

  def GetTaxSaving(self, TR, i):
    return  0
```

```python
class DeclineLinear(DeclineBase):
  def __init__(self, P, n, SV):
    super().__init__(P, n)
    self.SV = SV

    sP = Symbol('P')
    sSV = Symbol('SV')
    sn = Symbol('n')
    sm = Symbol('m')
    sD = Symbol('D')
    self.DFormula = eval("(sP - sSV) / sn")
    self.BVFormula = eval("sP - sm*sD")

  def GetD(self, m):
    D = (self.P - self.SV) / self.n
    display(Latex(f'${"D ="}{sympy.latex(self.DFormula)} = {D}$'))
    return D

  def BV(self, m):
    D = self.GetD(m)
    BV = self.P - m * D
    display(Latex(f'$BV_{m}={sympy.latex(self.BVFormula)} = {BV}$'))
    return BV

  def GetTaxSaving(self, TR, i):
    r = ((TR * (self.P - self.SV)) / self.n) * P_A(1, i, self.n)
    print(f"method1: {super().GetTaxSaving(TR, i)}", f"method2: {r}")
    return  r
```

```python
class DeclineSYD(DeclineBase):
  def __init__(self, P, n, SV):
    super().__init__(P, n)
    self.SV = SV

    sP = Symbol('P')
    sSV = Symbol('SV')
    sn = Symbol('n')
    sm = Symbol('m')
    sD = Symbol('D')
    SYD = sympify("(n * (n + 1)) / 2")
    self.SYDFormula = eval("SYD")
    self.DFormula = eval("((sn - sm + 1) / (SYD)) * (sP - sSV)")
    self.BVFormula = eval("sP - ((sm*(sn - sm/2 + 0.5)/SYD) * (sP - sSV))")

    self.SYD = (self.n * (self.n + 1)) / 2
    display(Latex(f'$SYD = {sympy.latex(self.SYDFormula)} = {self.SYD}$'))
  def GetSYD(self):
    return self.SYD

  def GetD(self, m):
    SYD = self.GetSYD()
    Dm = ((self.n - m + 1) / SYD) * (self.P - self.SV)
    display(Latex(f'$D_{m} = {sympy.latex(self.DFormula)} = {Dm}$'))
    return  Dm

  def BV(self, m):
    SYD = self.GetSYD()
    BVm = self.P - (((m * (self.n - (m / 2) + 0.5)) / SYD) * (self.P - self.SV))
    display(Latex(f'$BV_{m} = {sympy.latex(self.BVFormula)} = {BVm}$'))
    return BVm

  def GetTaxSaving(self, TR, i):
    r = ((2 * TR * (self.P - self.SV)) / ((self.n + 1) * i)) * (1 + (1 / (self.n * i)) * ((1 + i)**(-self.n) - 1))
    print(f"method1: {super().GetTaxSaving(TR, i)}", f"method2: {r}")
    return  r
```

```python
class DeclineDB(DeclineBase):
  def __init__(self, P, n, SV, d):
    super().__init__(P, n)
    self.SV = SV
    self.hasd = d > 0
    self.d = d if d > 0 else (1 - (SV / P)**(1/n))
    sP = Symbol('P')
    sSV = Symbol('SV')
    sn = Symbol('n')
    sm = Symbol('m')
    sD = Symbol('D')
    sd = Symbol('d')
    self.DFormula  = eval("sd * sP * (1 - sd)**(sm - 1)")
    self.dFormula  = eval("1 - ((sSV / sP)**(1/sn))")
    self.BVFormula = eval("sP * (1 - sd)**sm")
    self.Getd()
  def Getd(self):
    display(Latex(f'$d = {sympy.latex(self.dFormula)} = {self.d}$'))

  def GetD(self, m):
    Dm = self.d * self.P * ((1 - self.d)**(m - 1))
    display(Latex(f'$D_{m} = BV_{m-1}*d = {sympy.latex(self.DFormula)} = {Dm}$'))
    return Dm

  def BV(self, m):
    BVm = self.P * ((1 - self.d)**m)
    display(Latex(f'$BV_{m} = {sympy.latex(self.BVFormula)} = {BVm}$'))
    return BVm

  def GetTaxSaving(self, TR, i):
    r = ((2 * self.P * TR) / self.n) * ((1 - ((1 - self.d) / (1 + i)) ** self.n) / (i + self.d))
    print(f"method1: {super().GetTaxSaving(TR, i)}", f"method2: {r}")
    return  r

```

```python
class DeclineDDB(DeclineDB):
  def __init__(self, P, n, SV, d):
    super().__init__(P, n, SV, d)
    self.d = d if d > 0 else 2 / n

    sn = Symbol('n')
    self.dFormula  = eval("2 / sn")
    self.Getd()
    self.fixedDs = []

  def GetFixedTable(self):
    if self.BV(self.n) < self.SV:
      table = {"m": [], "Dm": [], "BVm": []}
      isDZero = False
      for m in range(1, self.n + 1):
        table['m'].append(m)
        BV = self.BV(m)

        if not isDZero and BV < self.SV:
          isDZero = True
          table['Dm'].append(self.BV(m - 1) - self.SV)
          table['BVm'].append(self.SV)
          continue
        if isDZero:
          table['Dm'].append(0)
          table['BVm'].append(self.SV)
        else:
          table['Dm'].append(self.GetD(m))
          table['BVm'].append(BV)
      print(table)
      display(pd.DataFrame(table))
      self.fixedDs = table['Dm']
    elif self.BV(self.n) > self.SV:
      table2 = {"m": [], "Dm": [], "SLm": [], 'BVm': []}

      # table = dict([(str(j), []) for j in range(1, self.n + 1)])
      # table['m'] = [m for m in range(1, self.n + 1)]
      table2['m'] = [m for m in range(1, self.n + 1)]
      for m in range(1, self.n + 1):
        # for i in range(1, m):
        #   table[str(m)].append(self.GetD(i))
        SL = (self.BV(m - 1) - self.SV) / (self.n - (m - 1))
        table2['SLm'].append(SL)
        table2['Dm'].append(self.GetD(m))
        table2['BVm'].append(self.BV(m))
        # for i in range(m, self.n + 1):
        #   table[str(m)].append(SL)
      # print(table)
      print(table2)
      # df = pd.DataFrame(table)
      # df.set_index('m')
      df2 = pd.DataFrame(table2)
      df2.set_index('m')
      # display(df)
      display(df2)

      for m in range(self.n):
        if table2['SLm'][m] > table2['Dm'][m]:
          for j in range(m, self.n):
            table2['Dm'][j] = table2['SLm'][m]
            table2['BVm'][j] = table2['BVm'][j - 1] - table2['SLm'][m]
          break
      del table2['SLm']
      print(table2)
      df2 = pd.DataFrame(table2)
      df2.set_index('m')
      display(df2)
      self.fixedDs = table2['Dm']
      # return df
    else:
      print("no need for fixed table")
  def GetFixedDs(self):
    return self.fixedDs
  def GetTaxSaving(self, TR, i):
    return TR * self.GetPWD(i)
```

```python
class DeclineSF(DeclineBase):
  def __init__(self, P, n, SV, i):
    super().__init__(P, n)
    self.SV = SV
    self.i = i

  def GetD(self, m):
    D = A_F((self.P - self.SV), self.i, self.n) * F_P(1, self.i, m - 1)
    print(D)
    display(Latex(f'$D_{m}=(P - SV)(A / F, i/100, n)(F / P, i/100, m - 1) = {D}$'))
    return D

  def BV(self, m):

    BV = self.P - A_F((self.P - self.SV), self.i, self.n) * F_A(1, self.i, m)
    display(Latex(f'$BV_{m}=P - (P - SV)(A / F, i/100, n)(F / A, i/100, m) = {BV}$'))
    return BV
```

```python
class DeclineUP(DeclineBase):
  def __init__(self, P, n, SV, Ums):
    super().__init__(P, n)
    self.SV = SV
    self.U = sum(Ums)
    self.Ums = Ums

  def GetD(self, m):
    if m == 0:
      return 0
    Dm = (self.P - self.SV) * (self.Ums[m - 1] / self.U)
    display(Latex(f'$D_{m} = (P - SV)(U_{m} / U) = {Dm}$'))
    return Dm

  def BV(self, m):
    s = 0
    for i in range(1, m + 1):
      s += self.GetD(i)
    BVm = self.P - s
    display(Latex(f'$BV_{m}' + ' = P - \sum_{k=1}^m D_k = ' + f'P - {s} ={BVm}$'))
    return BVm
```

```python
class DeclineTO(DeclineBase):
  def __init__(self, P, n, SV, Qms):
    super().__init__(P, n)
    self.SV = SV
    self.Q = sum(Qms)
    self.Qms = Qms

  def GetD(self, m):
    if m == 0:
      return 0
    Dm = (self.P - self.SV) * (self.Qms[m - 1] / self.Q)
    display(Latex(f'$D_{m} = (P - SV)(Q_{m} / Q) = {Dm}$'))
    return Dm

  def BV(self, m):
    s = 0
    for i in range(1, m + 1):
      s += self.GetD(i)
    BVm = self.P - s
    display(Latex(f'$BV_{m}' + ' = P - \sum_{k=1}^m D_k = ' + f'P - {s} ={BVm}$'))
    return BVm

```

Try them out:

```python
# d = DeclineSYD(200000, 9, 10000)
# d = DeclineLinear(80000, 10, 10000)
P = 70000
n = 8
SV = 20000
d = DeclineDDB(P, n, SV, -1)
d.GetTable()
# d = DeclineDDB(82000, 7, 5000, -1)
# d.GetTable()
# d.GetFixedTable()

# d = DeclineSF(80000, 10, 10000, 0.1)
# d = DeclineUP(2000000, 5, 400000, [2000, 4000, 8000, 16000, 10000])
# d = DeclineTO(2000000, 5, 400000, [2000, 4000, 8000, 16000, 10000])
# d.GetTable()
```

$d = 1 - \left(\frac{SV}{P}\right)^{\frac{1}{n}} = 0.14495003320915523$

$d = \frac{2}{n} = 0.25$

$D_0 = BV_-1*d = P d \left(1 - d\right)^{m - 1} = 23333.333333333332$

$BV_0 = P \left(1 - d\right)^{m} = 70000.0$

$D_1 = BV_0*d = P d \left(1 - d\right)^{m - 1} = 17500.0$

$BV_1 = P \left(1 - d\right)^{m} = 52500.0$

$D_2 = BV_1*d = P d \left(1 - d\right)^{m - 1} = 13125.0$

$BV_2 = P \left(1 - d\right)^{m} = 39375.0$

$D_3 = BV_2*d = P d \left(1 - d\right)^{m - 1} = 9843.75$

$BV_3 = P \left(1 - d\right)^{m} = 29531.25$

$D_4 = BV_3*d = P d \left(1 - d\right)^{m - 1} = 7382.8125$

$BV_4 = P \left(1 - d\right)^{m} = 22148.4375$

$D_5 = BV_4*d = P d \left(1 - d\right)^{m - 1} = 5537.109375$

$BV_5 = P \left(1 - d\right)^{m} = 16611.328125$

$D_6 = BV_5*d = P d \left(1 - d\right)^{m - 1} = 4152.83203125$

$BV_6 = P \left(1 - d\right)^{m} = 12458.49609375$

$D_7 = BV_6*d = P d \left(1 - d\right)^{m - 1} = 3114.6240234375$

$BV_7 = P \left(1 - d\right)^{m} = 9343.8720703125$

$D_8 = BV_7*d = P d \left(1 - d\right)^{m - 1} = 2335.968017578125$

$BV_8 = P \left(1 - d\right)^{m} = 7007.904052734375$

      m
      Dm
      BVm

      0
      0
      23333.333333
      70000.000000

      1
      1
      17500.000000
      52500.000000

      2
      2
      13125.000000
      39375.000000

      3
      3
      9843.750000
      29531.250000

      4
      4
      7382.812500
      22148.437500

      5
      5
      5537.109375
      16611.328125

      6
      6
      4152.832031
      12458.496094

      7
      7
      3114.624023
      9343.872070

      8
      8
      2335.968018
      7007.904053

#Chapter 12 Tax
---

```python
#TAX
def Tax(GIs, OCs, Ds, TR, n, useCFBT=False, CFBTs=[], useLoan=False, PRs=[], Is=[]):

  table = {'Year': [i + 1 for i in range(n)]}

  if not useCFBT:
    table['GI'] = GIs
    table['OC'] = OCs
    table['CFBT'] =  [GIs[i] - OCs[i] for i in range(n)]
  else:
    table['CFBT'] =  CFBTs

  table['D']  = Ds
  if useLoan:
    table['I'] = Is
    table['PR'] = PRs
  table['TI']   = [table['CFBT'][i] - Ds[i] for i in range(n)]
  if useLoan:
    table['TI'] = [table['TI'][i] - Is[i] for i in range(n)]
  table['TX']   = [TI * TR if TI > 0 else 0 for TI in table['TI']]
  table['CFAT'] = [table['CFBT'][i] - table['TX'][i] for i in range(n)]
  if useLoan:
    table['CFAT'] = [table['CFAT'][i] - Is[i] - PRs[i] for i in range(n)]
  # print(table)
  display(pd.DataFrame(table))
  return table['CFAT']

def SumCFAT(CFATs, i):
  return sum([P_F(CFATs[j - 1], i, j) for j in range(1, len(CFATs) + 1)])
```

```python
n = 10
P = 11e5
SV = 1e5
GIs = [27000 - 1000 * i for i in range(n)]
OCs = [10000 + 500 * i  for i in range(n)]
useCFBT = True
# CFBTs = [6e3] * n
CFBTs = [3e5 - 1e4 * (m-1) for m in range(1, n + 1)]
useLoan = False
Is = [5e4] * n
PRs = [5e4] * n

TR  = 0.4
d  = DeclineLinear(P, n, SV)
d.GetTable()
# d.GetFixedTable()
Ds = d.GetDs()
# Ds = d.GetFixedDs()

CFATs = Tax(GIs, OCs, Ds, TR, n, useCFBT, CFBTs, useLoan, PRs, Is)
i = 0.1
print((-(P) + SumCFAT(CFATs, i) + P_F(SV, i, n))) # if using loan, change P
print(SumCFAT(CFATs, i))
# print(P_F(6500, i, n))
print(d.GetTaxSaving(TR, i))
```

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$BV_0=- D m + P = 1100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$BV_1=- D m + P = 1000000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$BV_2=- D m + P = 900000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$BV_3=- D m + P = 800000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$BV_4=- D m + P = 700000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$BV_5=- D m + P = 600000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$BV_6=- D m + P = 500000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$BV_7=- D m + P = 400000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$BV_8=- D m + P = 300000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$BV_9=- D m + P = 200000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$BV_10=- D m + P = 100000.0$

      m
      Dm
      BVm

      0
      0
      100000.0
      1100000.0

      1
      1
      100000.0
      1000000.0

      2
      2
      100000.0
      900000.0

      3
      3
      100000.0
      800000.0

      4
      4
      100000.0
      700000.0

      5
      5
      100000.0
      600000.0

      6
      6
      100000.0
      500000.0

      7
      7
      100000.0
      400000.0

      8
      8
      100000.0
      300000.0

      9
      9
      100000.0
      200000.0

      10
      10
      100000.0
      100000.0

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

      Year
      CFBT
      D
      TI
      TX
      CFAT

      0
      1
      300000.0
      100000.0
      200000.0
      80000.0
      220000.0

      1
      2
      290000.0
      100000.0
      190000.0
      76000.0
      214000.0

      2
      3
      280000.0
      100000.0
      180000.0
      72000.0
      208000.0

      3
      4
      270000.0
      100000.0
      170000.0
      68000.0
      202000.0

      4
      5
      260000.0
      100000.0
      160000.0
      64000.0
      196000.0

      5
      6
      250000.0
      100000.0
      150000.0
      60000.0
      190000.0

      6
      7
      240000.0
      100000.0
      140000.0
      56000.0
      184000.0

      7
      8
      230000.0
      100000.0
      130000.0
      52000.0
      178000.0

      8
      9
      220000.0
      100000.0
      120000.0
      48000.0
      172000.0

      9
      10
      210000.0
      100000.0
      110000.0
      44000.0
      166000.0

    153011.039513421
    1214456.7105704679

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

$D =\frac{P - SV}{n} = 100000.0$

    method1: 245782.6842281872 method2: 245782.68422818743
    245782.68422818743

#Chapter 13 & 14
These 2 chapters were bit tricky to derive a formula out of them. I left some of my codes, I leave it to you to make them better. good luck!

```python
# Economic Service Life
def ESL():
  table = {'n': [], 'EUAC1': [], 'EUAC2': [], 'EUAC3': []}

  i = 0.1
  n = 15
  SVs = [33e3 - (2500) * (m - 1) for m in range(1, n + 1)]
  # SVs = [0] * n
  # OCs = [2e3 * (1.15) ** (m-1) for m in range(1, n + 1)]
  # OCs = [2e3 + 1500*(m - 1) for m in range(1, n + 1)]
  # OCs = [2e3, 27e2, 3e3, 35e2, 45e2]

  print(SVs)
  # print(OCs)

  table['n'] = [m for m in range(1, n + 1)]
  table['EUAC1'] = [A_P(33e3, i, m) for m in range(1, n + 1)]
  # t = [P_F(OCs[0], i, 1)]
  # for m in range(2, n + 1):
  #   t.append(t[m - 2] + P_F(OCs[m-1], i, m))
  # table['EUAC2'] = [A_P(t[m-1], i, m) for m in range(1, n + 1)]
  table['EUAC2'] = [0 for m in range(1, n + 1)]
  # table['EUAC2'] = [2e3 + A_G(1500, i, m) for m in range(1, n + 1)]
  # table['EUAC3'] = [A_(900, i, m) for m in range(1, n + 1)]
  table['EUAC3'] = [A_F(-SVs[m - 1], i, m) for m in range(1, n + 1)]
  table['EUAC'] = [table['EUAC1'][m-1] + table['EUAC2'][m-1] + table['EUAC3'][m-1] for m in range(1, n + 1)]

  df = pd.DataFrame(table).set_index('n')
  display(df)
  t = min(table['EUAC'])
  print(table['EUAC'].index(t) + 1, t)

ESL()
```

    [33000.0, 30500.0, 28000.0, 25500.0, 23000.0, 20500.0, 18000.0, 15500.0, 13000.0, 10500.0, 8000.0, 5500.0, 3000.0, 500.0, -2000.0]

      EUAC1
      EUAC2
      EUAC3
      EUAC

      n

      1
      36300.000000
      0
      -33000.000000
      3300.000000

      2
      19014.285714
      0
      -14523.809524
      4490.476190

      3
      13269.788520
      0
      -8459.214502
      4810.574018

      4
      10410.536522
      0
      -5494.505495
      4916.031028

      5
      8705.316866
      0
      -3767.342058
      4937.974808

      6
      7577.043552
      0
      -2656.951297
      4920.092255

      7
      6778.381490
      0
      -1897.298995
      4881.082496

      8
      6185.652580
      0
      -1355.382272
      4830.270308

      9
      5730.137789
      0
      -957.327008
      4772.810781

      10
      5370.598031
      0
      -658.826646
      4711.771385

      11
      5080.783687
      0
      -431.705136
      4649.078551

      12
      4843.189398
      0
      -257.198233
      4585.991165

      13
      4645.691284
      0
      -122.335571
      4523.355713

      14
      4479.625367
      0
      -17.873112
      4461.752255

      15
      4338.634637
      0
      62.947554
      4401.582191

    1 3300.0

```python
n = np.linspace(1, 30, 30)
print(n)
plt.plot(n, -116400 + 32000 * P_A(1, 0.13, n) - 2500 * P_G(1, 0.13, n), color='r')
plt.plot(n, -170000 + 32000 * P_A(1, 0.13, n) + 20000 * P_F(1, 0.13, n), color='b')
```

```python

```


```python
import numpy as np
import quadpy
import sympy as sp
import numpy as np
import matplotlib.pyplot as plt
from tectosaur2 import gauss_rule, refine_surfaces, integrate_term, tensor_dot
from tectosaur2.laplace2d import double_layer, hypersingular
from tectosaur2.mesh import panelize_symbolic_surface
from tectosaur2.debug import plot_centers
```

```python

```

```python
n_fault=3

qx, qw = gauss_rule(7)
t = sp.var("t")

# The number of fault panels should be a multiple of 40.
# so that the kinks in the "a" parameter profile occur at panel boundaries.
edges = np.linspace(-1, 1, n_fault+1)
panel_bounds = np.stack((edges[:-1],edges[1:]), axis=1)
fault = panelize_symbolic_surface(
    t, t * 0, (t + 1) * -0.5,
    panel_bounds,
    qx, qw
)
```

```python
from tectosaur2.mesh import build_interp_matrix, build_interpolator
Im = build_interp_matrix(build_interpolator(fault.qx), np.array([-1,1]))
Im = np.array(Im)
```

```python
constraints.shape
```

    (2, 21)

```python
fault.n_pts
```

    21

```python
fault.pts
```

    array([[ 0.        , -0.00848201],
           [ 0.        , -0.04307814],
           [ 0.        , -0.09902581],
           [ 0.        , -0.16666667],
           [ 0.        , -0.23430753],
           [ 0.        , -0.2902552 ],
           [ 0.        , -0.32485132],
           [ 0.        , -0.34181535],
           [ 0.        , -0.37641147],
           [ 0.        , -0.43235914],
           [ 0.        , -0.5       ],
           [ 0.        , -0.56764086],
           [ 0.        , -0.62358853],
           [ 0.        , -0.65818465],
           [ 0.        , -0.67514868],
           [ 0.        , -0.7097448 ],
           [ 0.        , -0.76569247],
           [ 0.        , -0.83333333],
           [ 0.        , -0.90097419],
           [ 0.        , -0.95692186],
           [ 0.        , -0.99151799]])

```python
field = fault.pts[:,1] ** 2
field[:7] -= 1e-1
field[14:] += 1e-1
Im.dot(field[:7]), Im.dot(field[7:14]), Im.dot(field[14:])
```

    (array([-0.1       ,  0.01111111]),
     array([0.11111111, 0.44444444]),
     array([0.54444444, 1.1       ]))

```python
constraints = np.zeros((fault.n_panels, fault.n_pts))
for i in range(fault.n_panels-1):
    constraints[i,i*fault.panel_order:(i+1)*fault.panel_order] = Im[1]
    constraints[i,(i+1)*fault.panel_order:(i+2)*fault.panel_order] = -Im[0]
constraints[-1,-fault.panel_order:] = Im[1]
constraints_rhs = np.zeros(constraints.shape[0])
constraints_rhs[-1] = 1.0
plt.figure(figsize=(10,10))
plt.spy(constraints[:20,:120])
plt.show()
```

```python
mat = np.zeros((fault.n_pts + constraints.shape[0], fault.n_pts+constraints.shape[0]))
mat[:fault.n_pts,:fault.n_pts] = np.eye(fault.n_pts)
mat[fault.n_pts:,:fault.n_pts] = constraints
mat[:fault.n_pts, fault.n_pts:] = constraints.T
```

```python
Cfield = np.linalg.solve(mat, np.concatenate((field, constraints_rhs)))[:-3]
```

```python
plt.plot(fault.pts[:,1], field)
plt.plot(fault.pts[:,1], Cfield)
plt.show()
```

```python
# Ix + By = d
# Cx + 0y = r

# x = d - By
# C(d - By) = r
# Cd = CBy + r
# x = Fd = (I - B(CB^-1)(Cd - r))d
# x = (I - B(CB^-1)C)d -
# F = (I - B((CB)^-1)C)d
continuity = np.eye(fault.n_pts) - constraints.T.dot(np.linalg.inv(constraints.dot(constraints.T)).dot(constraints))
y = np.linalg.inv(constraints.dot(constraints.T)).dot(constraints.dot(field) - constraints_rhs)
c = field - constraints.T.dot(np.linalg.inv(constraints.dot(constraints.T)).dot(constraints.dot(field) - constraints_rhs))
Cfield - c
```

    array([ 0.00000000e+00,  0.00000000e+00,  0.00000000e+00,  0.00000000e+00,
            6.93889390e-18, -3.46944695e-18,  2.42861287e-17,  0.00000000e+00,
            0.00000000e+00,  0.00000000e+00,  0.00000000e+00,  0.00000000e+00,
            5.55111512e-17,  0.00000000e+00,  1.11022302e-16,  1.11022302e-16,
            0.00000000e+00,  0.00000000e+00,  0.00000000e+00,  0.00000000e+00,
            0.00000000e+00])

```python
np.linalg.cond(CAiB)
```

```python

```


# GPS

Based on D. Kalman's method, I expand the four constraint equations as following:

$
\begin{align}
2.4x + 4.6y + 0.4z - 2(0.047^2)9.9999 t &= 1.2^2 + 2.3^2 + 0.2^2 + x^2 + y^2 + z^2 - 0.047^2 t^2 \\
-1x + 3y + 3.6z - 2(0.047^2)13.0681 t &= 0.5^2 + 1.5^2 + 1.8^2 +  x^2 + y^2 + z^2 - 0.047^2 t^2 \\
-3.4x + 1.6y + 2.6z - 2(0.047^2)2.0251 t &= 1.7^2 + 0.8^2 + 1.3^2 +  x^2 + y^2 + z^2 - 0.047^2 t^2 \\
3.4x + 2.8y - 1z - 2(0.047^2)10.5317 t &= 1.7^2 + 1.4^2 + 0.5^2 +  x^2 + y^2 + z^2 - 0.047^2 t^2
\end{align},
$

And subtract the first equation from the rest.

$
\begin{align}
-3.4x - 1.6y + 3.2z - 2(0.047^2)3.0682 t &= -1.0299  \\
-5.8x - 3y + 2.2z + 2(0.047^2)7.9750 t &= -1.5499  \\
1x    - 1.8y - 1.4z - 2(0.047^2)0.5318 t &= -1.6699
\end{align},
$

Converting it to matrix format as following:

$
\begin{equation}
\begin{bmatrix}
-3.4 & 1.6 & 3.2 & -0.01355  \\
-5.8 & -3   & 2.2 & 0.0352   \\
1    & -1.8 & -1.4 & -0.00235
\end{bmatrix}
\begin{bmatrix}
x \\ y \\ z \\ t
\end{bmatrix}
=
\begin{bmatrix}
-1.0299 \\ -1.5499 \\ -1.6699
\end{bmatrix}
\end{equation}.
$

# Newton's Method

For having $f(x)=0$, based on state variables $x=(x, y, z, t)$ we can convert the nonlinear distance equation as following:

$
\begin{equation}
f(x) = \frac{1}{2}
\begin{bmatrix}
(x-1.2)^2+(y-2.3)^2+(z-0.2)^2-(0.047*(t-09.9999))^2 \\
(x+0.5)^2+(y-1.5)^2+(z-1.8)^2-(0.047*(t-13.0681))^2 \\
(x+1.7)^2+(y-0.8)^2+(z-1.3)^2-(0.047*(t-02.0251))^2 \\
(x-1.7)^2+(y-1.4)^2+(z+0.5)^2-(0.047*(t-10.5317))^2
\end{bmatrix}
= 0.
\end{equation}
$

```julia
f(x) = [(x[1]-1.2)^2+(x[2]-2.3)^2+(x[3]-0.2)^2-0.047*(x[4]-09.9999)^2,
    (x[1]+0.5)^2+(x[2]-1.5)^2+(x[3]-1.8)^2-0.047*(x[4]-13.0681)^2,
    (x[1]+1.7)^2+(x[2]-0.8)^2+(x[3]-1.3)^2-0.047*(x[4]-02.0251)^2,
    (x[1]-1.7)^2+(x[2]-1.4)^2+(x[3]+0.5)^2-0.047*(x[4]-10.5317)^2]./2

f([1.2 2.3 0.2 9.9999])
```

    4-element Array{Float64,1}:
     0.0
     2.82377449586
     4.4404602765600005
     0.7683539358599999

## Jacobian

$
\begin{equation}
Df(x_0) =
\begin{bmatrix}
(x-1.2) & (y-2.3) & (z-0.2) & -0.047(t-09.9999)\\
(x+0.5) & (y-1.5) & (z-1.8) & -0.047(t-13.0681)\\
(x+1.7) & (y-0.8) & (z-1.3) & -0.047(t-02.0251)\\
(x-1.7) & (y-1.4) & (z+0.5) & -0.047(t-10.5317)
\end{bmatrix}.
\end{equation}
$

```julia
D(x) = [x[1]-1.2 x[2]-2.3 x[3]-0.2 -0.047*(x[4]-09.9999);
    x[1]+0.5 x[2]-1.5 x[3]-1.8 -0.047*(x[4]-13.0681);
    x[1]+1.7 x[2]-0.8 x[3]-1.3 -0.047*(x[4]-02.0251);
    x[1]-1.7 x[2]-1.4 x[3]+0.5 -0.047*(x[4]-10.5317)]/2

D([1.7 1.4 -0.5 10.5317])
```

    4×4 Array{Float64,2}:
     0.25  -0.45  -0.35  -0.0124973
     1.1   -0.05  -1.15   0.0596054
     1.7    0.3   -0.9   -0.199905
     0.0    0.0    0.0   -0.0

# Solutions

```julia
using LinearAlgebra

function gps(x)
    t = 1            # tolerance of answers
    for i=1:1e5
        d = f(x)\D(x)
        x -= d
        if norm(d)<1e-15 return x end
    end
    return x
end

s = gps([0 0 0 0])
```

    1×4 Array{Float64,2}:
     -0.152014  1.16284  0.239069  3.61735

```julia
b = [-1.0299;-1.5499;-1.6699]
A = [-3.4  1.6  3.2  -0.01355  ;
-5.8  -3    2.2  0.0352   ;
1     -1.8  -1.4  -0.00235]

s = b\A
```

    1×4 Transpose{Float64,Array{Float64,1}}:
     1.73099  0.961006  -0.698654  -0.00586697

## Adjourn

```julia
using Dates

println("mahdiar")
Dates.format(now(), "Y/U/d HH:MM")
```

    mahdiar

    "2021/March/19 11:13"


# AX-12A Quadruped
## Kinematics

#### Kevin Walchko

25 Feb 2018

-----------

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

---

**Still in development**

The kinematics are designed for a leg with 4 servos. The robot is shown below in the stowed and standing configuration. A standard 12 inch ruler is displayed for scale.

- **Coxa:** hip segment which moves left/right in red
- **Femur:** blue segment
- **Tibia:** yellow segment
- **Tarsus:** green segment

```python
%matplotlib inline

from __future__ import print_function
from __future__ import division
import numpy as np
from matplotlib import pyplot as plt
```

## Forward Kinematics

The [modified DH parameters](https://en.wikipedia.org/wiki/Denavit%E2%80%93Hartenberg_parameters#Modified_DH_parameters) are:

| i |$a_i$        | $\alpha_i$   | $d_i$   | $theta_i$  |
|---|-------------|--------------|---------|------------|
| 1 | 0           | 0            | 0       | $\theta_1$ |
| 2 | $L_1$       | 90           | 0       | $\theta_2$ |
| 3 | $L_2$       | 0            | 0       | $\theta_3$ |
| 4 | $L_3$       | 0            | 0       | $\theta_4$ |
| 5 | $L_4$       | 0            | 0       |          0 |

- $a_i$: **link length** in mm from $Z_{i-1}$ to $Z_i$ along $X_i$
- $\alpha_i$: **twist angle** between $Z_{i-1}$ and $Z_i$ measured about $X_i$
- $d_i$: **offset distance** between $X_{i-1}$ and $X_i$ along $Z_i$
- $\theta_i$: **rotation angle** between $X_{i-1}$ and $X_i$ measured about $Z_i$

| Link  | Name   | mm |
|-------|--------|----|
| $L_1$ | coxa   | xx |
| $L_2$ | femur  | xx |
| $L_3$ | tibia  | xx |
| $L_4$ | tarsus | xx |

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

def makeT(a, alpha, d, theta):
    # create a modified DH homogenious matrix
    return np.array([
        [           cos(theta),           -sin(theta),           0,             a],
        [sin(theta)*cos(alpha), cos(theta)*cos(alpha), -sin(alpha), -d*sin(alpha)],
        [sin(theta)*sin(alpha), cos(theta)*sin(alpha),  cos(alpha),  d*cos(alpha)],
        [                    0,                     0,           0,             1]
    ])

def simplifyT(tt):
    """
    This goes through each element of a matrix and tries to simplify it.
    """
    ret = []
    for row in tt:
        m = []
        for col in row:
            m.append(simplify(col))
        ret.append(m[:])
    return np.array(ret)

def subs(tt, m):
    """
    This allows you to simplify the trigonomic mess that kinematics can
    create and also substitute in some inputs in the process

    Yes, this is basically the same as above. I could combine these into 1
    function, but I wanted to beclearer on what I am doing.
    """
    ret = tt.copy()
    for i, row in enumerate(tt):
        for j, col in enumerate(row):
            try:
                ret[i,j] = col.subs(m)
            except:
                ret[i,j] = simplify(col)
    return ret
```

```python
t1, t2, t3, t4 = symbols('t1 t2 t3 t4')
l1, l2, l3, l4 = symbols('l1 l2 l3 l4')
```

```python
# a, alpha, d, theta
T1 = makeT(0, 0, 0, t1)
T2 = makeT(l1, pi/2, 0, t2)
T3 = makeT(l2, 0, 0, t3)
T4 = makeT(l3, 0, 0, t4)
T5 = makeT(l4, 0, 0, 0)
```

```python
print('T1 = ', T1)
print('T2 = ', T2)
print('T3 = ', T3)
print('T4 = ', T4)
print('T5 = ', T5)
```

    T1 =  [[cos(t1) -sin(t1) 0 0]
     [sin(t1) cos(t1) 0 0]
     [0 0 1 0]
     [0 0 0 1]]
    T2 =  [[cos(t2) -sin(t2) 0 l1]
     [0 0 -1 0]
     [sin(t2) cos(t2) 0 0]
     [0 0 0 1]]
    T3 =  [[cos(t3) -sin(t3) 0 l2]
     [sin(t3) cos(t3) 0 0]
     [0 0 1 0]
     [0 0 0 1]]
    T4 =  [[cos(t4) -sin(t4) 0 l3]
     [sin(t4) cos(t4) 0 0]
     [0 0 1 0]
     [0 0 0 1]]
    T5 =  [[1 0 0 l4]
     [0 1 0 0]
     [0 0 1 0]
     [0 0 0 1]]

```python
T = T1.dot(T2.dot(T3.dot(T4.dot(T5))))
print('T = ', T)
```

    T =  [[((-sin(t3)*sin(t4) + cos(t3)*cos(t4))*cos(t2) - (sin(t3)*cos(t4) + sin(t4)*cos(t3))*sin(t2))*cos(t1)
      (-(-sin(t3)*sin(t4) + cos(t3)*cos(t4))*sin(t2) + (-sin(t3)*cos(t4) - sin(t4)*cos(t3))*cos(t2))*cos(t1)
      sin(t1)
      (l1 - (l4*sin(t4)*cos(t3) + (l3 + l4*cos(t4))*sin(t3))*sin(t2) + (l2 - l4*sin(t3)*sin(t4) + (l3 + l4*cos(t4))*cos(t3))*cos(t2))*cos(t1)]
     [((-sin(t3)*sin(t4) + cos(t3)*cos(t4))*cos(t2) - (sin(t3)*cos(t4) + sin(t4)*cos(t3))*sin(t2))*sin(t1)
      (-(-sin(t3)*sin(t4) + cos(t3)*cos(t4))*sin(t2) + (-sin(t3)*cos(t4) - sin(t4)*cos(t3))*cos(t2))*sin(t1)
      -cos(t1)
      (l1 - (l4*sin(t4)*cos(t3) + (l3 + l4*cos(t4))*sin(t3))*sin(t2) + (l2 - l4*sin(t3)*sin(t4) + (l3 + l4*cos(t4))*cos(t3))*cos(t2))*sin(t1)]
     [(-sin(t3)*sin(t4) + cos(t3)*cos(t4))*sin(t2) + (sin(t3)*cos(t4) + sin(t4)*cos(t3))*cos(t2)
      (-sin(t3)*sin(t4) + cos(t3)*cos(t4))*cos(t2) + (-sin(t3)*cos(t4) - sin(t4)*cos(t3))*sin(t2)
      0
      (l4*sin(t4)*cos(t3) + (l3 + l4*cos(t4))*sin(t3))*cos(t2) + (l2 - l4*sin(t3)*sin(t4) + (l3 + l4*cos(t4))*cos(t3))*sin(t2)]
     [0 0 0 1]]

```python
Tf = simplify(T)
```

```python
print(Tf)
```

    [[cos(t1)*cos(t2 + t3 + t4), -sin(t2 + t3 + t4)*cos(t1), sin(t1), (l1 + l2*cos(t2) + l3*cos(t2 + t3) + l4*cos(t2 + t3 + t4))*cos(t1)], [sin(t1)*cos(t2 + t3 + t4), -sin(t1)*sin(t2 + t3 + t4), -cos(t1), (l1 + l2*cos(t2) + l3*cos(t2 + t3) + l4*cos(t2 + t3 + t4))*sin(t1)], [sin(t2 + t3 + t4), cos(t2 + t3 + t4), 0, l2*sin(t2) + l3*sin(t2 + t3) + l4*sin(t2 + t3 + t4)], [0, 0, 0, 1]]

```python
print('position x: {}'.format(Tf[0,3]))
print('position y: {}'.format(Tf[1,3]))
print('position z: {}'.format(Tf[2,3]))
```

    position x: (l1 + l2*cos(t2) + l3*cos(t2 + t3) + l4*cos(t2 + t3 + t4))*cos(t1)
    position y: (l1 + l2*cos(t2) + l3*cos(t2 + t3) + l4*cos(t2 + t3 + t4))*sin(t1)
    position z: l2*sin(t2) + l3*sin(t2 + t3) + l4*sin(t2 + t3 + t4)

```python
from math import cos, sin, pi, atan2, sqrt, acos
def forward(t1,t2,t3,t4, degrees=True):
#     l1 = 28
#     l2 = 90
#     l3 = 84
#     l4 = 98
    l1 = 52
    l2 = 89
    l3 = 90
    l4 = 95

    if degrees:
        t1 *= pi/180
        t2 *= pi/180
        t3 *= pi/180
        t4 *= pi/180

    x = (l1 + l2*cos(t2) + l3*cos(t2 + t3) + l4*cos(t2 + t3 + t4))*cos(t1)
    y = (l1 + l2*cos(t2) + l3*cos(t2 + t3) + l4*cos(t2 + t3 + t4))*sin(t1)
    z = l2*sin(t2) + l3*sin(t2 + t3) + l4*sin(t2 + t3 + t4)
    return (x,y,z)
```

## Inverse Kinematics

## Law of Cosines

$$
a^2 = b^2 + c^2 - 2bc \cos(A) \rightarrow \cos(A)=\frac{-a^2+b^2+c^2}{2bc}\\
b^2 = a^2 + c^2 - 2ac \cos(B) \rightarrow \cos(B)=\frac{a^2-b^2+c^2}{2ac}\\
c^2 = a^2 + b^2 - 2ab \cos(C) \rightarrow \cos(C)=\frac{a^2+b^2-c^2}{2ab}
$$

- [Wolfram: law of cosines](http://mathworld.wolfram.com/LawofCosines.html)
- [Cosine law calculator](https://www.calculatorsoup.com/calculators/geometry-plane/triangle-law-of-cosines.php)

## Law of Sines

$$
\frac{a}{\sin(A)} = \frac{b}{\sin(B)} = \frac{c}{\sin(C)}
$$

```python
def cosinelaw(a,b,c):
    # cos(g) = (a^2+b^2-c^2)/2ab
    return acos((a**2+b**2-c**2)/(2*a*b))

def inverse(x,y,z,o, degrees=True):
    """
    Azimuth angle is between x and w and lies in the x-y plane

               ^ x
         w     |
           \   |
         l1 \  |
             \ |
              \|
    <----------+ (z is out of the page - right hand rule)
    y

    Most of the leg moves in the plane defined by w-z

    ^ z      l3
    |      o-----o
    |     /       \ l4
    |    / l2      E
    |   /
    +--o-------------> w
     l1

    l1: coxa
    l2: femur
    l3: tibia
    l4: tarsus

    All joint angles returned are in degrees: (t1, t2, t3, t4)
    """
    # mm
    l1 = 52
    l2 = 89
    l3 = 90
    l4 = 95
    t1 = atan2(y,x)

    if degrees:
        o = o*pi/180

    w = sqrt(x**2+y**2)-l1
    j4w = w+l4*cos(o)
    j4z = z+l4*sin(o)
    r = sqrt(j4w**2+j4z**2)
    g1 = atan2(j4z,j4w)
    g2 = cosinelaw(l2,r,l3)
    t2 = g1+g2

    t3 = pi+cosinelaw(l2,l3,r)

    j2w=l2*cos(t2)
    j2z=l2*sin(t2)
    c=sqrt((w-j2w)**2+(z-j2z)**2)
    t4=pi+cosinelaw(l3,l4,c)

    if degrees:
        t1 *= 180/pi
        t2 *= 180/pi
        t3 *= 180/pi
        t4 *= 180/pi

    return (t1,t2,t3,t4)
```

# Test

Let's test that both the forward and inverse kinematics agree ... and they should. The errors are printed below. Please note, that 0 and 360 degrees are the same thing.

```python
def testAng(a1,a2,a3,a4):
    # all angles in degrees

    print('*'*25)

    f=forward(a1,a2,a3,a4)
    print('Pts FK:', f)

    f = f + (180+(a2+a3+a4),)
    print('debug', f)
    i=inverse(*f)
    print('Angles IK [deg]:', i)

    print('Delta Angles:')
    err = []
    for a,b in zip([a1,a2,a3,a4],i):
        err.append(a-b)

    print('  a1:{:.2f} a2:{:.2f} a3:{:.2f} a4:{:.2f}'.format(err[0],err[1],err[2], err[3]))

def testPos(x,y,z,o):
    # a,y,z in mm
    # o in deg measured from the floor, so if the leg is straight down, then o = 90 deg

    print('*'*25)

    i=inverse(x,y,z,o)
    print('Angles IK [deg]:', i)

    f=forward(*i)
    print('Pts FK:', f)

    print('Delta Angles:')
    err = []
    for a,b in zip([x,y,z],f):
        err.append(a-b)

    print('  x:{:.2f} y:{:.2f} z:{:.2f}'.format(err[0],err[1],err[2]))
```

```python
testPos(150,0,-20,90)
testPos(300,0,0,180)
testPos(50,0,-90,90)
```

    *************************
    Angles IK [deg]: (0.0, 84.18024923704603, 267.1662445169008, 278.6535062460531)
    Pts FK: (149.99999999999977, 0.0, -20.000000000000057)
    Delta Angles:
      x:0.00 y:0.00 z:0.00
    *************************
    Angles IK [deg]: (0.0, 31.462945785152435, 297.4628594057476, 328.92580519090006)
    Pts FK: (249.3824093191971, 0.0, -83.99524833598178)
    Delta Angles:
      x:50.62 y:0.00 z:84.00
    *************************
    Angles IK [deg]: (0.0, 210.80433148991102, 183.38804106711368, 235.80762744297533)
    Pts FK: (50.000000000000114, 0.0, -89.99999999999994)
    Delta Angles:
      x:-0.00 y:0.00 z:-0.00

```python
testAng(0,0,0,0)
testAng(0,45,-45,-90)
testAng(0,0,-90,0)
```

    *************************
    Pts FK: (326.0, 0.0, 0.0)
    debug (326.0, 0.0, 0.0, 180)
    Angles IK [deg]: (0.0, 3.723951862350912e-15, 360.0, 360.0)
    Delta Angles:
      a1:0.00 a2:-0.00 a3:-360.00 a4:-360.00
    *************************
    Pts FK: (204.93250352560273, 0.0, -32.067496474397274)
    debug (204.93250352560273, 0.0, -32.067496474397274, 90)
    Angles IK [deg]: (0.0, 45.000000000000014, 315.00000000000006, 270.0)
    Delta Angles:
      a1:0.00 a2:-0.00 a3:-360.00 a4:-360.00
    *************************
    Pts FK: (141.0, 0.0, -185.0)
    debug (141.0, 0.0, -185.0, 90)
    Angles IK [deg]: (0.0, 0.0, 270.0, 360.0)
    Delta Angles:
      a1:0.00 a2:0.00 a3:-360.00 a4:-360.00

# Plot Leg Locations

Let's get a visual of what is going on when we command a leg to a 3d location. The command is:

```python
plotLeg(x,y,z,orientation)
```

The *orientation* is the tarsus (foot) angle between the ground plane and the tarsus. Thus, if the tarsus is pointing straight down, the angle is 90 degrees. This is measured from the floor to the tarsus link.

```python
def rplot(t1, t2, t3, t4, degrees=True):
    """Given the 4 joint angles (in rads), plot the arm in the x-y and w-z planes

    x = (d2 + l1*cos(t2) + l2*cos(t2 + t3) + l3*cos(t2 + t3 + t4))*cos(t1)
    y = (d2 + l1*cos(t2) + l2*cos(t2 + t3) + l3*cos(t2 + t3 + t4))*sin(t1)
    z = l1*sin(t2) + l2*sin(t2 + t3) + l3*sin(t2 + t3 + t4)
    """
    l1 = 52
    l2 = 89
    l3 = 90
    l4 = 95

    ptsx = [0]
    ptsy = [0]

    if degrees:
        t1 *= pi/180
        t2 *= pi/180
        t3 *= pi/180
        t4 *= pi/180

    # our definition is reverse or these joints
    # link 1
    x0 = l1
    y0 = 0
    ptsx.append(x0)
    ptsy.append(y0)

    # link 2
    x1 = x0 + l2*cos(t2)
    y1 = y0 + l2*sin(t2)
    ptsx.append(x1)
    ptsy.append(y1)

    # link 3
    x2 = x1 + l3*cos(t2 + t3)
    y2 = y1 + l3*sin(t2 + t3)
    ptsx.append(x2)
    ptsy.append(y2)

    # link 4
    x3 = x2 + l4*cos(t2 + t3 + t4)
    y3 = y2 + l4*sin(t2 + t3 + t4)
    ptsx.append(x3)
    ptsy.append(y3)

    plt.subplot(1,2,1,projection='polar')
    plt.plot([0, t1], [0, 1.0])
    plt.grid(True)
    plt.title('Azimuth Angle (x-y plane)\n')

    plt.subplot(1,2,2)
    plt.plot(ptsx, ptsy, 'b-', marker='o')
    plt.axis('equal')
    plt.grid(True)
    plt.title('w-z Plane')

def plotLeg(x, y, z, orient):
    """Given a point (in inches) and orientation (in rads), this calculates
    the joint angles, then uses those angles to calculate the forward solution
    and prints out the error. It also plots the arm.
    """
    angles = inverse(x, y, z, orient)

    angles = list(angles)

    for i, a in enumerate(angles):
        if a > 180:
            angles[i] = -360 + a

    a,b,c,d = angles
    print('Angles: {:.2f} {:.2f} {:.2f} {:.2f}'.format(a,b,c,d))

    print('w-z plane (w,z): {:.2f} {:.2f}'.format(sqrt(x**2 + y**2), z))

    rplot(*angles)
```

```python
plotLeg(0,75,10,90)
```

```python
plotLeg(110,-110,-40,90)
```

```python
plotLeg(110,0,-70,90)
```

```python
plotLeg(110,110,-70,90)
```

# Torque

$ \sum T_{AX-12A} = T_{CM} $

For this version, we have 3x AX-12A's holding up the robot. The coxia servo only swings side-to-side, so it doesn't help support the weight.

$ T_{CM} = weight / 4 * foot_{location}$

**Now**, Robotis suggests the max weight should only be 1/5 for sustainable dynamic movement. So we will divide by another 5 to get that number.

```python
def N2lbf(x):
    return x*0.224809

tax12 = 1.5 # Nm
foot = 0.155 # m
w = 3*tax12*4/foot/5
print('Max robot weight: {:.2f} N'.format(w))
print('Max weight: {:.2f} lbf'.format(N2lbf(w)))
```

    Max robot weight: 23.23 N
    Max weight: 5.22 lbf

```python
gax12 = 0.0546 # kg
motors = 16*gax12
print('Motors: {:.2f} N'.format(motors))
print('Motors: {:.2f} lbf'.format(N2lbf(motors)))
```

    Motors: 0.87 N
    Motors: 0.20 lbf

```python
rw = w - motors
print('Remaining weight: {:.2f} N'.format(rw))
print('Remaining weight: {:.2f} lbf'.format(N2lbf(rw)))
```

    Remaining weight: 22.35 N
    Remaining weight: 5.02 lbf

# Servos

The servos used in this robot are Dynamixel AX-12A smart servos by Robotis.

Each leg is composed of 4 servos, with Leg 0 (servo 1, servo 2, servo 3, servo 4) being the right front leg. The remaining 3 legs are numbered counter clockwise from 0 with the servo numbers increasing with each leg. The servo numbers are also the servo ID numbers when commanding them.

The servos are able to move between 0-300 degrees. In servo space, with the leg laid out straight (or as straight as posible given its construction), there are offsets between servo space (what the servos can actually do) and DH space (how the forward/reverse kinematics were derived).

For example, in DH space, the straight leg would have all servos commanded to angle 0, but as seen in the pictures and the table below, there are offsets.

| Leg Orientation | 1 | 2 | 3 | 4 |
|-----------------|---|---|---|---|
| Straight        |150|123|194|167|
| Stand           |150|160|130|100|
| Stow            |150|250| 80| 60|

Inorder to align the servos with the DH frame layout, the following offsets are applied:

| Servo           | 1 | 2 | 3 | 4 |
|-----------------|---|---|---|---|
| Offset (deg)    |150|123|194|167|

```python

```