#### **Model project: A Solow model with fossil fuels and a climate externality**

The motivation for this project is to analyze long-term growth in th presence of finite resources and a negative externality from previously utilized ressources. For this purpose, we analyze the following Solow model:

**Model setup**

Equations (1)-(7) below charicterize a Solow model for a closed economy with depletable fossil , fuels. The stock of fossil fuels is denoted by $R_r$ and $E_t$ is the ammount of fossil fuels used as an input in production in each period. $Y_t$ denotes GDP and $K_t, L_t$ and $A_t$ denotes Capital, labor and total factor productivity (TFP), respectively. Equations (3) describes capital accumulation, equation (4) and (5) decribes the evolution of the stock of labour and TFP, respectively.

:
\begin{equation}
    Y = D_t \cdot K^{\alpha}_{t} (A_t L_t)^\beta E^\epsilon_t, \quad \alpha, \beta, \epsilon > 0 \quad \alpha + \beta +\epsilon = 1 \tag{1}
\end{equation}
\begin{equation}
    D_t = \left( \frac{R_t}{R_0} \right) ^\phi, \quad \phi > 0 \tag{2}
\end{equation}
\begin{equation}
    K_{t+1} = sY_t +(1-\delta)K_t, \quad 0 < \delta < 1 \tag{3}
\end{equation}
\begin{equation}
     L_{t+1} = (1+n)L_t, \quad  n \geq 0  \tag{4}
\end{equation}
\begin{equation}
     A_{t+1} = (1+g)A_t, \quad  g \geq 0 \tag{5}
\end{equation}
\begin{equation}
     R_{t+1} = R_t - E_t \tag{6}
\end{equation}
\begin{equation}
     E_{t} = s_E R_t, \quad  0  < s_E  < \delta \tag{7}
\end{equation}

**Loading packages**

```python
import numpy as np
from scipy import linalg
from scipy import optimize as opt
from scipy.ndimage.interpolation import shift
import sympy as sm
import pandas as pd
import matplotlib.pyplot as plt
sm.init_printing(pretty_print = True)
from ipywidgets import interact, interactive, fixed, interact_manual
import ipywidgets as widgets

import matplotlib.animation as animation
from matplotlib.widgets import Slider

```

**Climate externality**

The following graph maps the damage function of the climate externality for different values of the $\phi$, i.e. the parameter that determines the marginal damage that the externality inflcits. As we will see, the nature of the loss functio is critical for whether climate change will affect long term growth.
\begin{equation}
D_t = \left(  \frac{R_t}{R_0} \right) ^\phi , \qquad \phi  > 0  \tag{Externality}
\end{equation}
For $\phi>1$ the marginal damage caused by the cumulcative use of fossil fuels will be decreasing and for $1>\phi>0$ the marginal damage will be increasing.

```python
    # The following figure shoes the transition of the damage function as fossil fuels are being depleated.

    def Damage_function(R0,RT,phi):
        D = (RT/R0)**phi;
        return D

    def plot_func(phi):
        R0 = 100;
        R_series = np.linspace(0.1,R0,100)
        D_series = np.zeros(100)

        for i in range(0,100):
            D_series[i] = Damage_function(R0, R_series[i],phi)

        fig = plt.figure()
        ax = fig.add_subplot(111)
        ax.set_title("Transition of $D_t$ for different values of phi")
        ax.set_xlabel('$R_T$ - stock left')
        ax.set_ylabel('Damage function')
        ax.set_xlim([0, 100])
        ax.set_ylim([0, 1])
        x1 = np.linspace(*ax.get_xlim())
        x2 = np.linspace(*ax.get_ylim())
        ax.plot(x1, x2, 'k--')
        ax.plot(R_series,D_series,'b')

    interact(plot_func, phi = widgets.FloatSlider(Value=1, min=0.05, max=4, step=0.05))
```

    interactive(children=(FloatSlider(value=0.05, description='phi', max=4.0, min=0.05, step=0.05), Output()), _do…

    <function __main__.plot_func(phi)>

**Production function**

The following function defines the Cobb-Douglas production function with the multiplicative damage function.
We rewrite (1) using $E_t = s_E \cdot R_T$ and the defition of $D_t$:

$$Y_t = R_t^{\phi + \epsilon} R_0^{-\phi} s_E^\epsilon K^{\alpha}_{t} (A_t L_t)^\beta$$

```python
# Production function
def production(R,R0,A,L,K,phi,epsilon,se, beta, alpha):
    Y = R**(phi + epsilon) * R0 **(-phi) * se**epsilon * (A*L)**beta * K**alpha
    return Y
```

```python
# Analytical long run steady state level growth rate for plotting
def gy_ss(beta,epsilon,g,n,phi,se):
    g_ss = g*beta/(beta+epsilon) -n*epsilon/(beta+epsilon) -se*(epsilon+phi)/(beta+epsilon)
    return g_ss
```

**Simulating the model**

We simulate the model from time $t=1$ up to time $T=200$ which is abitrarily chosen, in a recursive fashion. The variables $R_t$, $A_t$ and $L_t$ grows exogeneously, output in the current period is given by current values $Y_t = F(K_t,L_t,A_t,R_T)$, but the next period kapital level $K_{t+1} = sY_t +(1-\delta)K_t $ grows in a recursive fashion. We need to initialize the series at index [0] given this feature. We simulate the economy this way and plot the output/labor ratios and kapital/labor ratios.

```python
#Parameter values
alpha = 0.2
beta = 0.6
epsilon = 1-alpha -beta
s = 0.2
se = 0.005
g = 0.02
n = 0.02
phi = 0.8
delta = 0.08

#Starting values for state varaibles
K0 = 10
R0 = 10
L0 = 10
A0 = 10

# Empty vectors for storring result
K_list = []
R_list = []
A_list = []
L_list = []
Y_list = []

# Initialize
K_list.append(K0)
R_list.append(R0)
A_list.append(A0)
L_list.append(L0)
Y_list.append(production(R0,R0,A0,L0,K0,phi,epsilon,se, beta,alpha))

T = 200;

t = 1
while t <= T:
    K_list.append(s*Y_list[t-1] + (1-delta)*K_list[t-1])
    R_list.append(R_list[t-1]*(1-se))
    A_list.append(A_list[t-1]*(1+g))
    L_list.append(L_list[t-1]*(1+n))
    Y_list.append(production(R_list[t],R0,A_list[t],L_list[t],K_list[t],phi,epsilon,se, beta,alpha))
    t += 1

Y_list = np.array(Y_list) # To numpy array
L_list = np.array(L_list) # To numpy array
K_list = np.array(K_list) # To numpy array

# Per units of labor
y_list = Y_list/L_list  # Element wise division
k_list = K_list/L_list

Y_max = np.max(Y_list)
K_max = np.max(K_list)
```

```python
# Growth rate
y_list_lagged = np.roll(y_list,1)
k_list_lagged = np.roll(k_list,1)
k_growth = np.log(k_list[1:200]) - np.log(k_list_lagged[1:200])
y_growth = np.log(y_list[1:200]) - np.log(y_list_lagged[1:200])
g_ss=gy_ss(beta,epsilon,g,n,phi,se) # steady state growth rate

#Plot
f = plt.figure()
# first subplot

f.set_figheight(10)
f.set_figwidth(20)

plt.subplot(2,2,1)
plt.plot(y_list)
plt.ylabel('y - GDP per worker')
plt.xlabel('t - time period')
plt.title('Evolution of GDP per worker')

plt.subplot(2,2,2)
plt.plot(k_list, color = 'g')
plt.ylabel('k - Capital per worker')
plt.xlabel('t - time period')
plt.title('Evolution of Capital per worker')

plt.subplot(2,2,3)
plt.plot(y_growth)
plt.axhline(g_ss, color='r', linestyle='--')
plt.ylabel('Growth in - GDP per worker')
plt.xlabel('t - time period')
plt.title('Evolution of Growth in gdp per worker')

plt.subplot(2,2,4)
plt.plot(k_growth, color = 'g')
plt.axhline(g_ss, color='r', linestyle='--')
plt.ylabel('Growth in - GDP per worker')
plt.xlabel('t - time period')
plt.title('Evolution of Growth in Capital per worker')

plt.show()
```

From the graphs above we notice there is possitive growth in GDP pr. worker and Capital pr. worker even in the long run. This seems at firsthand rather counter intuitive: How can one manage to obtained sustained growth in the long run, when we exhaust a non-renewable ressource which is essential for production. This is a result of perfect substitution between the inputs of manmade capital and natural ressources. As time passes we will substitute towards using less natural ressources and more manmade capital in production, and in the limit we use an infinitesimal amount of the natural ressource. It should be noted the assumption of perfect substitution between these inputs have been critized by prominent economist as HERMAN E. DALY (1990) who argues against neoclassical production functions "... Do extra sawmills substitute for diminishing forests? Do more refineries substitute for depleted oil wells? Do larger nets substitute for declining fish populations?".

Additional it is noticeable that the growth rate in capital per worker and growth rate in gdp per worker, converges to the same level in the long run. From this we suggest there must exist some level of capital/ output ratio which is constant in a steady state level. In the following we examing this.

**Finding the steady-state**

In the last section we established that the growth rate of capital and output was identical in the long run. If we define capital/ output ratio $z_t \equiv \frac{K_t}{Y_t} = \frac{k_t}{y_t}$, we are now interested in determining the steady of this ratio. Straight forward algebra of the model equations gives us the following transition equation:

$z_{t+1} = \left( \frac{1}{1-s_e}\right)^{\epsilon + \phi}\left(\frac{1}{(1+n)(1+g)}\right)^{\beta}(s+(1-\delta)z_{t})^{1-\alpha}z_{t}^{\alpha}$

The steady state value is characterized by $z_{t} = z_{t+1} = z_{ss}$ or equivalently the growth rate of $z$ $g_z = 0$.

**Numerical solution of the capital output ratio, $z_t$**

To find the steady state numerically, we try different numerical methods. We consider the Newton algorithm which is gradient based, the bisection method and the brentq method, which combines a bisection medthod along with other optimization procedures. We simply use numerical derivatives for the newton algorithm, but one could consider for simple problems of passing analytical gradient and hessian. Concerning the bisection method and the brentq (who combines this method), we need to pass an interval in which to find the root. WE have used different optimization methods with the same same tolerance level for all the algorithms.

```python
# Solve for ss
#Parameter values
alpha = 0.2
beta = 0.6
epsilon = 1-alpha -beta
s = 0.2
se = 0.005
g = 0.02
n = 0.02
phi = 0.8
delta = 0.08

def helpfun(se, eps, phi, n, g, beta):
    frac = (1/(1-se))**(epsilon + phi) * (1/((1+n)*(1+g)))**beta
    return (frac)

power_alpha = lambda z: z**alpha
depsav = lambda z: (s + (1-delta)*z)**(1-alpha)
Obj_ss = lambda zss: zss - helpfun(se, epsilon, phi, n, g, beta)*depsav(zss)*power_alpha(zss)

# Newton Method
z_start = 0.2;
zss_newton = opt.newton(Obj_ss, z_start, tol = 1.48e-08, rtol=4.5e-9, maxiter=100,
                        full_output=True, disp=True)
# Bisection
zmin = 0.001
zmax = 2
zss_bisect = opt.bisect(Obj_ss, zmin, zmax,args=(), xtol=1.48e-08,
                    rtol=4.5e-9, maxiter=100,
                    full_output=True, disp=True)
# Brent - q
zss_brentq = opt.brentq(Obj_ss, zmin, zmax,
                    args=(), xtol=1.48e-08,
                    rtol=4.5e-9, maxiter=100,
                    full_output=True, disp=True)
print(zss_newton)
print(zss_bisect)
print(zss_brentq)
# Brentq method
result = opt.root_scalar(Obj_ss,bracket=[0.01,10], method = 'brentq')
```

    (1.92835954580555,       converged: True
               flag: 'converged'
     function_calls: 7
         iterations: 6
               root: 1.92835954580555)
    (1.9283595391735433,       converged: True
               flag: 'converged'
     function_calls: 29
         iterations: 27
               root: 1.9283595391735433)
    (1.9283595458055507,       converged: True
               flag: 'converged'
     function_calls: 6
         iterations: 5
               root: 1.9283595458055507)

From the results we notice all 3 root-findings method converge to the same result (within an appropriate number of decimals). There is big difference in how fast the algorithms converge. The newton- and qbrent method approximately converge at the same speed the bisection method use a vastly higher amount of iterations to converge. In this application the "brent-q" method seems the fastest, but this may also be a result of starting values and interval size etc.

**Solving the model symbolcally using the sympy packages**

In the following we solve the steady state symbolically, by the sympy package. We had to "help" with some of the derivations for it to work.

```python
z = sm.symbols('k')
se = sm.symbols('se')
epsilon = sm.symbols('epsilon')
phi = sm.symbols('phi')
beta = sm.symbols('beta')
n = sm.symbols('n')
g = sm.symbols('g')
s = sm.symbols('s')
delta = sm.symbols('delta')
alpha = sm.symbols('alpha')

# write your code here
frag = 1/((1 + n)*(1 + g)) # Fraction
V = ((1/(1-se))**(phi + epsilon)) * frag**beta # Terms which does not depend on z

steadystate = sm.Eq(V**(1/(1-alpha)) * (s + (1-delta)*z),z)
zss = sm.solve(steadystate,z)[0]
z = sm.symbols('z^*')
zequation = sm.Eq(z,zss)
zequation
```

This expression looks somewhat similar as the analytical expression for the model, we will compare it numerically later.

```python
# We save python function
z_ss_func = sm.lambdify((s,epsilon,se,n,g,phi,beta,delta,alpha),zss) # Steady state value
```

We will compare these results to the analytical solution:
$$z^*=\frac{s}{(1-s_E)^{\frac{\epsilon+\phi}{\beta+\epsilon}}[(1+n)(1+g)]^\frac{\beta}{\beta+\epsilon}-(1-\delta)} >0$$

```python
# Analytical solution and comparing all the results

alpha = 0.2
beta = 0.6
epsilon = 1-alpha -beta
s = 0.2
se = 0.005
g = 0.02
n = 0.02
phi = 0.8
delta = 0.08

def z_ss_analytical(s, se, epsilon, phi, n, g, beta, alpha, delta):
    denominator = (1-se)**((epsilon+phi)/(beta+epsilon))*((1+n)*(1+g))**(beta/(beta + epsilon)) - (1-delta)
    z_ss = s/denominator
    return(z_ss)

z_ss_analytical = z_ss_analytical(s,se, epsilon, phi, n, g, beta, alpha, delta)
z_ss_sm = z_ss_func(s,epsilon,se,n,g,phi,beta,delta,alpha)
z_num = zss_brentq
print(f'''The steady-state value using the analytical expression is {z_ss_analytical:.4f},
    The steady-state value using the sympy derieved expression is {z_ss_sm:.4f},
    The steady-state value using numerical methods is {z_num[0]:.4f}''')

```

    The steady-state value using the analytical expression is 1.9284,
        The steady-state value using the sympy derieved expression is 1.9284,
        The steady-state value using numerical methods is 1.9284

Both our "sympy" and numerical methods gives the same result as the analytical solution. This shows that the solution derived by sympy was the correct solution.

**Plotting the transition diagram**

In order to plot the transition diagram we need the values for $z_t$ and $z_{t+1}$, respectively. Steady state is found where $z_t = z_{t+1}$ and thus where the curve intersects the 45 degree line.

We start out by defining a function for the transition equation.

```python
def z_next_func(s,epsilon,se,n,g,phi,beta,delta,alpha,x):
    z = (1/(1-se))**(epsilon+phi)*(1/((1+n)*(1+g)))**beta*(s+(1-delta)*x)**(1-alpha)*x**alpha
    return z
```

```python
# Create a for loop to calculate the values of the transition equation in all T=200 periods.
x = np.zeros((T,1))
x[0] = 0.00
for t in range(0,T-1):
    x[t+1] = z_next_func(s,epsilon,se,n,g,phi,beta,delta,alpha,x[t])

# Define the function "transition()" in order to create figure with the transition diagram
# and make it possible to create float sliders for specific variables.

def transition(s,epsilon,se,n,g,phi,beta,delta,alpha):
    zmin = 0.0
    zmax = z_ss_func(s,epsilon,se,n,g,phi,beta,delta,alpha)*1.2

    z_set = np.linspace(zmin, zmax,100)
    fig1 = plt.figure(figsize=(6.5,5),facecolor = 'white')
    plt.plot(z_set,z_next_func(s,epsilon,se,n,g,phi,beta,delta,alpha,z_set),'k-', #transition function
             z_set, z_set, 'r--',  #45 degree line
             z_ss_func(s,epsilon,se,n,g,phi,beta,delta,alpha), z_ss_func(s,epsilon,se,n,g,phi,beta,delta,alpha), 'ko', #st.st. point
            (z_ss_func(s,epsilon,se,n,g,phi,beta,delta,alpha), z_ss_func(s,epsilon,se,n,g,phi,beta,delta,alpha)),
            (z_ss_func(s,epsilon,se,n,g,phi,beta,delta,alpha),0),'k--') # Vertical dashed line at st.st.
    plt.ylim(bottom=0) #let plot start at z_(t+1) = 0
    plt.xlim(left=0) #let plot start at z_t = 0
    plt.xlabel('$z_t$')
    plt.ylabel('$z_{t+1}$')
    plt.title('Transition diagram for the capital output ratio, $z_t$')
    plt.show(fig1)

# Create float sliders
widgets.interact(transition,
                 s = widgets.FloatSlider(description='$s$',min=0, max=1, step=0.01, value=0.20),
                 epsilon = widgets.fixed(epsilon),
                 beta = widgets.fixed(beta),
                 alpha = widgets.fixed(alpha),
                 se = widgets.FloatSlider(description='$s_{E}$', min=0, max=0.01, step=0.001, value=0.005),
                 n = widgets.FloatSlider(description='$n$', min=0, max=1, step=0.001, value=0.01),
                 g = widgets.FloatSlider(description='$g$', min=0, max=1, step=0.001, value=0.02),
                 phi = widgets.FloatSlider(description='$\phi$', min=0, max=5, step=0.1, value=2),
                 delta = widgets.fixed(delta)
                )
```

    interactive(children=(FloatSlider(value=0.2, description='$s$', max=1.0, step=0.01), FloatSlider(value=0.005, …

    <function __main__.transition(s, epsilon, se, n, g, phi, beta, delta, alpha)>

**An increase in the savings rate:** Capital accumulation increases which leads to an increase in both capital and output. The effect of an increase in the savings rate is greater with respect to capital, however. This is due to the (highly plausable) assumption that the output elasticity with respect to capital is less than 1. Hence, an increase in the savings rate increases the capital/output ratio in steady state.
**An increase in $s_E$:** An increase in $s_E$ increases the steady state value of the capital/output ratio. An increase in $s_E$ means that a greater fraction of the remaining stock of fossil fuels is used each period. Initially this increases production through an increase in energy input.

**An increase in n:** An increase in population growth lowers the steady state value.
*An increase in g:* An increase in technology growth lowers the steady state value of the capital output ratio. This follows from the fact that less capital is needed in order to produce a given amount of output.

**An increase in $\phi$:** An increase in $\phi$ increases the steady state value of $z$.

**New damage function and the prospect of long-term growth**

Untill now it have been possible to have long term growth in the output labor ratio and the kapital labor ratio. Thus, we have been able to find a steady state capital output ratio $z_{ss}$. Now, we alter the damage function and introduce a parameter $\bar \rho $. This parameter reflects a "tipping point" in the damage function: when this point is surpassed production will halt completely! Formaly we define:

\begin{equation}
D_t = \left( \frac{ \left( \frac{R_t}{R_0}\right) - \bar \rho }{1-\bar \rho} \right) ^\phi , \qquad \phi  > 0 \qquad  0<\bar \rho < 1
\end{equation}

with $\bar \rho R_t \leq R_t \leq R_0$.

```python
## Second damage function

def Damagefunc2(R0,RT,phi,rho):
    D = (((RT/R0)-rho)/(1-rho))**phi
    return (D)

def plot_func(phi,rho):
        R0 = 100;
        R_series = np.linspace(R0*rho,R0,100)
        D_series = np.zeros(100)

        for i in range(0,100):
            D_series[i] = Damagefunc2(R0, R_series[i],phi,rho)

        fig = plt.figure()
        ax = fig.add_subplot(111)
        ax.set_title("Transition of $D_t$ for different values of phi")
        ax.set_xlabel('$R_T$ - stock left')
        ax.set_ylabel('Damage function')
        ax.set_xlim([0, 100])
        ax.set_ylim([0, 1])
        x1 = np.linspace(*ax.get_xlim())
        x2 = np.linspace(*ax.get_ylim())
        ax.plot(R_series,D_series,'b')

interact(plot_func, phi = widgets.FloatSlider(Value=1, min=0.05, max=4, step=0.05), rho = widgets.FloatSlider(Value=2, min=0.1, max=1, step=0.05))
```

    interactive(children=(FloatSlider(value=0.05, description='phi', max=4.0, min=0.05, step=0.05), FloatSlider(va…

    <function __main__.plot_func(phi, rho)>

 The figure illustrates that the prospect of long-term growth is higly dependent on the nature of the climate externality. In the first case, long-term growth was possible even though the value of the damage function converges to zero. Growth was possible because output tended to infinity as $D_t$ tenden to zero. In the seconde case, however, the tipping point means that the damage function becomes zero at a certain year and

**Concluding remarks**

In this project we have investigated how incorporating fossil fuels and an associated damage function alters the classic results from the general Solow model with technology. We have shown that balanced growth is attainable even in the presence of a climate externality. The assumptions underlying the damage function are however vital for this conclusion. A damage function that builds on the assumption that the damage effect has a gradual impact on production is compatible with balanced growth while the introduction of a "tipping point" in the damage function is not.
Naturally, this is of great importance when considering policy implications.


# The Discrete-Time Fourier Transform

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

## Theorems

The theorems of the discrete-time Fourier transform (DTFT) relate basic operations applied to discrete signals to their equivalents in the DTFT domain. They are of use to transform signals composed from modified [standard signals](../discrete_signals/standard_signals.ipynb), for the computation of the response of a linear time-invariant (LTI) system and to predict the consequences of modifying a signal or system by certain operations.

### Convolution Theorem

The [convolution theorem](https://en.wikipedia.org/wiki/Convolution_theorem) states that the DTFT of the linear convolution of two discrete signals $x[k]$ and $y[k]$ is equal to the scalar multiplication of their DTFTs $X(e^{j \Omega}) = \mathcal{F}_* \{ x[k] \}$ and $Y(e^{j \Omega}) = \mathcal{F}_* \{ y[k] \}$

\begin{equation}
\mathcal{F}_* \{ x[k] * y[k] \} = X(e^{j \Omega}) \cdot Y(e^{j \Omega})
\end{equation}

The theorem can be proven by introducing the [definition of the linear convolution](../discrete_systems/linear_convolution.ipynb) into the [definition of the DTFT](definition.ipynb) and changing the order of summation

\begin{align}
\mathcal{F} \{ x[k] * y[k] \} &= \sum_{k = -\infty}^{\infty} \left( \sum_{\kappa = -\infty}^{\infty} x[\kappa] \cdot y[k - \kappa] \right) e^{-j \Omega k}  \\
&= \sum_{\kappa = -\infty}^{\infty} \left( \sum_{k = -\infty}^{\infty} y[k - \kappa] \, e^{-j \Omega k} \right) x[\kappa] \\
&= Y(e^{j \Omega}) \cdot \sum_{\kappa = -\infty}^{\infty} x[\kappa] \, e^{-j \Omega \kappa} \\
&= Y(e^{j \Omega}) \cdot X(e^{j \Omega})
\end{align}

The convolution theorem is very useful in the context of LTI systems. The output signal $y[k]$ of an LTI system is given as the convolution of the input signal $x[k]$ with its impulse response $h[k]$. Hence, the signals and the system can be represented equivalently in the time and frequency domain

Calculation of the system response by transforming the problem into the DTFT domain can be beneficial since this replaces the computation of the linear convolution by a scalar multiplication. The (inverse) DTFT is known for many signals or can be derived by applying the properties and theorems to standard signals and their transforms. In many cases this procedure simplifies the calculation of the system response significantly.

The convolution theorem can also be useful to derive the DTFT of a signal. The key is here to express the signal as convolution of two other signals for which the transforms are known. This is illustrated in the following example.

#### Transformation of the triangular signal

The linear convolution of two [rectangular signals](../discrete_signals/standard_signals.ipynb#Rectangular-Signal) of lengths $N$ and $M$ defines a [signal of trapezoidal shape](../discrete_systems/linear_convolution.ipynb#Finite-Length-Signals)

\begin{equation}
x[k] = \text{rect}_N[k] * \text{rect}_M[k]
\end{equation}

Application of the convolution theorem together with the [DTFT of the rectangular signal](definition.ipynb#Transformation-of-the-Rectangular-Signal) yields its DTFT as

\begin{equation}
X(e^{j \Omega}) = \mathcal{F}_* \{ \text{rect}_N[k] \} \cdot \mathcal{F}_* \{ \text{rect}_M[k] \} =
e^{-j \Omega \frac{N+M-2}{2}} \cdot \frac{\sin(\frac{N \Omega}{2}) \sin(\frac{M \Omega}{2})}{\sin^2 ( \frac{\Omega}{2} )}
\end{equation}

The transform of the triangular signal can be derived from this result. The convolution of two rectangular signals of equal length $N=M$ yields the triangular signal $\Lambda[k]$ of length $2N - 1$

\begin{equation}
\Lambda_{2N - 1}[k] = \begin{cases} k + 1 & \text{for } 0 \leq k < N \\
2N - 1 - k & \text{for } N \leq k < 2N - 1 \\
0 & \text{otherwise}
\end{cases}
\end{equation}

From above result the DTFT of the triangular signal is derived by substitution of $N$ by $M$

\begin{equation}
\mathcal{F}_* \{ \Lambda_{2N - 1}[k] \} =
e^{-j \Omega (N-1)} \cdot \frac{\sin^2(\frac{N \Omega}{2}) }{\sin^2 ( \frac{\Omega}{2} )}
\end{equation}

Both the signal and the magnitude of its DTFT are plotted for illustration

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

N = 7

x = np.convolve(np.ones(N), np.ones(N), mode='full')
plt.stem(x)
plt.xlabel('$k$')
plt.ylabel('$x[k]$')

W = sym.symbols('Omega')
X = sym.exp(-sym.I*W *(N-1)) * sym.sin(N*W/2)**2 / sym.sin(W/2)**2
sym.plot(sym.Abs(X), (W, -5, 5), xlabel='$\Omega$', ylabel='$|X(e^{j \Omega})|$');
```

**Exercise**

* Change the length of the triangular signal in above example. How does its DTFT change?
* The triangular signal introduced above is of odd length $2N - 1$
    * Define a triangular signal of even length by convolving two rectangular signals
    * Derive its DTFT
    * Compare the DTFTs of a triangular signal of odd/even length

### Shift Theorem

The [shift of a signal](../discrete_signals/operations.ipynb#Shift) $x[k]$ can be expressed by a convolution with a shifted Dirac impulse

\begin{equation}
x[k - \kappa] = x[k] * \delta[k - \kappa]
\end{equation}

for $\kappa \in \mathbb{Z}$. This follows from the sifting property of the Dirac impulse. Applying the DTFT to the left- and right-hand side and exploiting the convolution theorem yields

\begin{equation}
\mathcal{F}_* \{ x[k - \kappa] \} = X(e^{j \Omega}) \cdot e^{- j \Omega \kappa}
\end{equation}

where $X(e^{j \Omega}) = \mathcal{F}_* \{ x[k] \}$. Note that $\mathcal{F}_* \{ \delta(k - \kappa) \} = e^{- j \Omega \kappa}$ can be derived from the definition of the DTFT together with the sifting property of the Dirac impulse. Above relation is known as shift theorem of the DTFT.

Expressing the DTFT $X(e^{j \Omega}) = |X(e^{j \Omega})| \cdot e^{j \varphi(e^{j \Omega})}$ by its absolute value $|X(e^{j \Omega})|$ and phase $\varphi(e^{j \Omega})$ results in

\begin{equation}
\mathcal{F}_* \{ x[k - \kappa] \} = | X(e^{j \Omega}) | \cdot e^{j (\varphi(e^{j \Omega}) - \Omega \kappa)}
\end{equation}

Shifting of a signal does not change the absolute value of its spectrum but it subtracts the linear contribution $\Omega \kappa$ from its phase.

### Multiplication Theorem

The transform of a multiplication of two signals $x[k] \cdot y[k]$ is derived by introducing the signals into the definition of the DTFT, expressing the signal $x[k]$ by its spectrum $X(e^{j \Omega}) = \mathcal{F}_* \{ x[k] \}$ and rearranging terms

\begin{align}
\mathcal{F}_* \{ x[k] \cdot y[k] \} &= \sum_{k=-\infty}^{\infty} x[k] \cdot y[k] \, e^{-j \Omega k} \\
&= \sum_{k=-\infty}^{\infty} \left( \frac{1}{2 \pi} \int_{-\pi}^{\pi} X(e^{j \nu}) \, e^{j \nu k} \; d \nu \right) y[k] \, e^{-j \Omega k} \\
&= \frac{1}{2 \pi} \int_{-\pi}^{\pi} X(e^{j \nu}) \sum_{k=-\infty}^{\infty} y[k] \, e^{-j (\Omega - \nu) k} \; d\nu \\
&= \frac{1}{2 \pi} \int_{-\pi}^{\pi} X(e^{j \nu}) \cdot Y(e^{j (\Omega - \nu)}) d\nu
\end{align}

where $Y(e^{j \Omega}) = \mathcal{F}_* \{ y[k] \}$.

The [periodic (cyclic/circular) convolution](https://en.wikipedia.org/wiki/Circular_convolution) of two aperiodic signals $h(t)$ and $g(t)$ is defined as

\begin{equation}
h(t) \circledast_{T} g(t) = \int_{-\infty}^{\infty} h(\tau) \cdot g_\text{p}(t - \tau) \; d\tau
\end{equation}

where $T$ denotes the period of the convolution, $g_\text{p}(t) = \sum_{n=-\infty}^{\infty} g(t + n T)$ the periodic summation of $g(t)$ and $\tau \in \mathbb{R}$ an arbitrary constant. The periodic convolution is commonly abbreviated by $\circledast_{T}$. With $h_\text{p}(t)$ denoting the periodic summation of $h(t)$ the periodic convolution can be rewritten as

\begin{equation}
h(t) \circledast_{T} g(t) = \int_{\tau_0}^{\tau_0 + T} h_\text{p}(\tau) \cdot g_\text{p}(t - \tau) \; d\tau
\end{equation}

where $\tau_0 \in \mathbb{R}$ denotes an arbitrary constant. The latter definition holds also for two [periodic signals](../periodic_signals/spectrum.ipynb) $h(t)$ and $g(t)$ with period $T$.

Comparison of the DTFT of two multiplied signals with the definition of the periodic convolution reveals that the preliminary result above can be expressed as

\begin{equation}
\mathcal{F}_* \{ x[k] \cdot y[k] \} = \frac{1}{2\pi} \, X(e^{j \Omega}) \circledast_{2 \pi} Y(e^{j \Omega})
\end{equation}

The DTFT of a multiplication of two signals $x[k] \cdot y[k]$ is given by the periodic convolution of their transforms $X(e^{j \Omega})$ and $Y(e^{j \Omega})$ weighted with $\frac{1}{2 \pi}$. The periodic convolution has a period of $T = 2 \pi$. Note, the convolution is performed with respect to the normalized angular frequency $\Omega$.

Applications of the multiplication theorem include the modulation and windowing of signals. The former leads to the modulation theorem introduced later, the latter is illustrated by the following example.

**Example**

Windowing of signals is used to derive signals of finite duration from signals of infinite duration or to truncate signals to a shorter length. The signal $x[k]$ is multiplied by a weighting function $w[k]$ in order to derive the finite length signal

\begin{equation}
y[k] = w[k] \cdot x[k]
\end{equation}

Application of the multiplication theorem yields the spectrum $Y(e^{j \Omega}) = \mathcal{F}_* \{ y[k] \}$ of the windowed signal as

\begin{equation}
Y(e^{j \Omega}) = \frac{1}{2 \pi} W(e^{j \Omega}) \circledast X(e^{j \Omega})
\end{equation}

where $W(e^{j \Omega}) = \mathcal{F}_* \{ w[k] \}$ and $X(e^{j \Omega}) = \mathcal{F}_* \{ x[k] \}$. In order to illustrate the consequence of windowing, a cosine signal $x[k] = \cos(\Omega_0 k)$ is truncated to a finite length using a rectangular signal

\begin{equation}
y[k] = \text{rect}_N[k] \cdot \cos(\Omega_0 k)
\end{equation}

where $N$ denotes the length of the truncated signal and $\Omega_0$ its normalized angular frequency. Using the DTFT of the [rectangular signal](definition.ipynb#Transformation-of-the-Rectangular-Signal) and the [cosine signal](properties.ipynb#Transformation-of-the-cosine-and-sine-signal) yields

\begin{align}
Y(e^{j \Omega}) &= \frac{1}{2 \pi} e^{-j \Omega \frac{N-1}{2}} \cdot \frac{\sin \left(\frac{N \Omega}{2} \right)}{\sin \left( \frac{\Omega}{2} \right)} \circledast \frac{1}{2} \left[ {\bot \!\! \bot \!\! \bot} \left( \frac{\Omega + \Omega_0}{2 \pi} \right) + {\bot \!\! \bot \!\! \bot} \left( \frac{\Omega - \Omega_0}{2 \pi} \right) \right] \\
&= \frac{1}{2} \left[ e^{-j (\Omega+\Omega_0) \frac{N-1}{2}} \cdot \frac{\sin \left(\frac{N (\Omega+\Omega_0)}{2} \right)}{\sin \left( \frac{\Omega+\Omega_0}{2} \right)} + e^{-j (\Omega-\Omega_0) \frac{N-1}{2}} \cdot \frac{\sin \left(\frac{N (\Omega-\Omega_0)}{2} \right)}{\sin \left( \frac{\Omega-\Omega_0}{2} \right)} \right]
\end{align}

The latter identity results from the sifting property of the Dirac impulse and the periodicity of both spectra. The signal $y[k]$ and its magnitude spectrum $|Y(e^{j \Omega})|$ are plotted for specific values of $N$ and $\Omega_0$.

```python
N = 20
W0 = 2*np.pi/10

k = np.arange(N)
x = np.cos(W0 * k)

plt.stem(k, x)
plt.xlabel('$k$')
plt.ylabel('$y[k]$');
```

```python
W = sym.symbols('Omega')

Y = 1/2 * ((sym.exp(-sym.I*(W+W0)*(N-1)/2) * sym.sin(N*(W+W0)/2) / sym.sin((W+W0)/2)) +
           (sym.exp(-sym.I*(W-W0)*(N-1)/2) * sym.sin(N*(W-W0)/2) / sym.sin((W-W0)/2)))

sym.plot(sym.Abs(Y), (W, -sym.pi, sym.pi), xlabel='$\Omega$', ylabel='$|Y(e^{j \Omega})|$');
```

**Exercise**

* Change the length $N$ of the signal by modifying the example. How does the spectrum change if you decrease or increase the length?

* What happens if you change the normalized angular frequency $\Omega_0$ of the signal?

* Assume a signal that is composed from a superposition of two finite length cosine signals with different frequencies. What qualitative condition has to hold that you can derive these frequencies from inspection of the spectrum?

### Modulation Theorem

The complex modulation of a signal $x[k]$ is defined as $e^{j \Omega_0 k} \cdot x[k]$ with $\Omega_0 \in \mathbb{R}$. The DTFT of the modulated signal is derived by applying the multiplication theorem

\begin{equation}
\mathcal{F}_* \left\{ e^{j \Omega_0 k} \cdot x[k] \right\} = \frac{1}{2 \pi} \cdot {\bot \!\! \bot \!\! \bot}\left( \frac{\Omega - \Omega_0}{2 \pi} \right) \circledast X(e^{j \Omega})
= X \big( e^{j \, (\Omega - \Omega_0)} \big)
\end{equation}

where $X(e^{j \Omega}) = \mathcal{F}_* \{ x[k] \}$.  Above result states that the complex modulation of a signal leads to a shift of its spectrum. This result is known as modulation theorem.

**Example**

An example for the application of the modulation theorem is the [downsampling/decimation](https://en.wikipedia.org/wiki/Decimation_(signal_processing) of a discrete signal $x[k]$. Downsampling refers to lowering the sampling rate of a signal. The example focuses on the special case of removing every second sample, hence halving the sampling rate. The downsampling is modeled by defining a signal $x_\frac{1}{2}[k]$ where every second sample is set to zero

\begin{equation}
x_\frac{1}{2}[k] = \begin{cases}
x[k] & \text{for even } k \\
0 & \text{for odd } k
\end{cases}
\end{equation}

In order to derive the spectrum $X_\frac{1}{2}(e^{j \Omega}) = \mathcal{F}_* \{ x_\frac{1}{2}[k] \}$, the signal $u[k]$ is introduced where every second sample is zero

\begin{equation}
u[k] = \frac{1}{2} ( 1 + e^{j \pi k} ) = \begin{cases} 1 & \text{for even } k \\
0 & \text{for odd } k \end{cases}
\end{equation}

Using $u[k]$, the process of setting every second sample of $x[k]$ to zero can be expressed as

\begin{equation}
x_\frac{1}{2}[k] = u[k] \cdot x[k]
\end{equation}

Now the spectrum $X_\frac{1}{2}(e^{j \Omega})$ is derived by applying the multiplication theorem and introducing the [DTFT of the exponential signal](definition.ipynb#Transformation-of-the-Exponential-Signal). This results in

\begin{equation}
X_\frac{1}{2}(e^{j \Omega}) = \frac{1}{4 \pi} \left(  {\bot \!\! \bot \!\! \bot}\left( \frac{\Omega}{2 \pi} \right) +
{\bot \!\! \bot \!\! \bot}\left( \frac{\Omega - \pi}{2 \pi} \right) \right) \circledast X(e^{j \Omega}) =
\frac{1}{2} X(e^{j \Omega}) + \frac{1}{2} X(e^{j (\Omega- \pi)})
\end{equation}

where $X(e^{j \Omega}) = \mathcal{F}_* \{ x[k] \}$. The spectrum $X_\frac{1}{2}(e^{j \Omega})$ consists of the spectrum of the original signal $X(e^{j \Omega})$ superimposed by the shifted spectrum $X(e^{j (\Omega- \pi)})$ of the original signal. This may lead to overlaps that constitute aliasing. In order to avoid aliasing, the spectrum of the signal $x[k]$ has to be band-limited to $-\frac{\pi}{2} < \Omega < \frac{\pi}{2}$ before downsampling.

### Parseval's Theorem

[Parseval's theorem](https://en.wikipedia.org/wiki/Parseval's_theorem) relates the energy of a discrete signal to its spectrum. The squared absolute value of a signal $x[k]$ represents its instantaneous power. It can be expressed as

\begin{equation}
| x[k] |^2 = x[k] \cdot x^*[k]
\end{equation}

where $x^*[k]$ denotes the complex conjugate of $x[k]$. Transformation of the right-hand side and application of the multiplication theorem results in

\begin{equation}
\mathcal{F}_* \{ x[k] \cdot x^*[k] \} = \frac{1}{2 \pi} \cdot X(e^{j \Omega}) \circledast_{2 \pi} X^*(e^{-j \Omega})
\end{equation}

Introducing the definition of the DTFT and the periodic convolution

\begin{equation}
\sum_{k = -\infty}^{\infty} x[k] \cdot x^*[k] \, e^{-j \Omega k}  =
\frac{1}{2 \pi} \int_{-\pi}^{\pi} X(e^{j \nu}) \cdot X^*(e^{j (\Omega - \nu)}) \; d\nu
\end{equation}

Setting $\Omega = 0$ followed by the substitution $\nu = \Omega$ yields Parseval's theorem

\begin{equation}
\sum_{k = -\infty}^{\infty} | x[k] |^2 = \frac{1}{2 \pi} \int_{-\pi}^{\pi} | X(e^{j \Omega}) |^2 \; d\Omega
\end{equation}

The sum over the samples of the squared absolute signal is equal to the integral over its squared absolute spectrum divided by $2 \pi$. Since the left-hand side represents the energy $E$ of the signal $x[k]$, Parseval's theorem states that the energy can be computed alternatively in the spectral domain by integrating over the squared absolute value of the spectrum.

**Copyright**

The notebooks are provided as [Open Educational Resource](https://de.wikipedia.org/wiki/Open_Educational_Resources). Feel free to use the notebooks for your own educational purposes. The text is licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/), the code of the IPython examples under the [MIT license](https://opensource.org/licenses/MIT). Please attribute the work as follows: *Lecture Notes on Signals and Systems* by Sascha Spors.


# Conservative SDOF - Lindstedt Poincare

The straight-forward expansion fails in part because the frequency is fixed in the expansion. Thus, secular terms arise which we cannot eliminate.

$$
\begin{gather*}
    \ddot{x} + \omega_0^2 x + \sum_{n=2}^{\infty} \alpha_n x^n.
\end{gather*}
$$

For the Lindstedt-Poincare method we introduce a dimensionless time $\tau$

$$
\begin{align*}
    \tau &= \omega t \\
    \dfrac{d}{dt} &= \omega \dfrac{d}{d\tau}\\
    \dfrac{d^2}{dt^2} &= \omega^2 \dfrac{^2}{d\tau^2}
\end{align*}
$$

```python
import sympy as sp
from sympy.simplify.fu import TR0, TR7, TR8, TR11
from math import factorial
```

```python
N = 3

# Define the symbolic parameters
epsilon = sp.symbols('epsilon')
omega = sp.symbols('omega')
omega_i = sp.symbols('omega_(0:' + str(N) + ')')
tau = sp.symbols('tau')
alpha_i = sp.symbols('alpha_(2:' + str(N+1) + ')')

x = sp.Function('x')(tau)
xdot = sp.Derivative(x, tau)
xddot = sp.Derivative(xdot, tau)

x1 = sp.Function('x_1')(tau)
x2 = sp.Function('x_2')(tau)
x3 = sp.Function('x_3')(tau)

# EOM
EOM = omega**2 * xddot + omega_i[0]**2 * x + sum([alpha_i[i-2] * x**i for i in range(2,N+1)])
EOM
```

$\displaystyle \alpha_{2} x^{2}{\left(\tau \right)} + \alpha_{3} x^{3}{\left(\tau \right)} + \omega^{2} \frac{d^{2}}{d \tau^{2}} x{\left(\tau \right)} + \omega_{0}^{2} x{\left(\tau \right)}$

```python
x_i = (x1, x2, x3)
x_e = sum([epsilon**i * x_i[i-1] for i in range(1,N+1)])
x_e
```

$\displaystyle \epsilon^{3} \operatorname{x_{3}}{\left(\tau \right)} + \epsilon^{2} \operatorname{x_{2}}{\left(\tau \right)} + \epsilon \operatorname{x_{1}}{\left(\tau \right)}$

```python
_omega = sum([epsilon**i * omega_i[i] for i in range(N)])
_omega
```

$\displaystyle \epsilon^{2} \omega_{2} + \epsilon \omega_{1} + \omega_{0}$

```python
# Substitute these into the EOM
EOM = EOM.subs([
    (x, x_e), (omega, _omega)
])
EOM
```

$\displaystyle \alpha_{2} \left(\epsilon^{3} \operatorname{x_{3}}{\left(\tau \right)} + \epsilon^{2} \operatorname{x_{2}}{\left(\tau \right)} + \epsilon \operatorname{x_{1}}{\left(\tau \right)}\right)^{2} + \alpha_{3} \left(\epsilon^{3} \operatorname{x_{3}}{\left(\tau \right)} + \epsilon^{2} \operatorname{x_{2}}{\left(\tau \right)} + \epsilon \operatorname{x_{1}}{\left(\tau \right)}\right)^{3} + \omega_{0}^{2} \left(\epsilon^{3} \operatorname{x_{3}}{\left(\tau \right)} + \epsilon^{2} \operatorname{x_{2}}{\left(\tau \right)} + \epsilon \operatorname{x_{1}}{\left(\tau \right)}\right) + \left(\epsilon^{2} \omega_{2} + \epsilon \omega_{1} + \omega_{0}\right)^{2} \frac{\partial^{2}}{\partial \tau^{2}} \left(\epsilon^{3} \operatorname{x_{3}}{\left(\tau \right)} + \epsilon^{2} \operatorname{x_{2}}{\left(\tau \right)} + \epsilon \operatorname{x_{1}}{\left(\tau \right)}\right)$

```python
EOM = sp.expand(EOM).doit()
EOM = sp.expand(EOM)
EOM
```

$\displaystyle \alpha_{2} \epsilon^{6} \operatorname{x_{3}}^{2}{\left(\tau \right)} + 2 \alpha_{2} \epsilon^{5} \operatorname{x_{2}}{\left(\tau \right)} \operatorname{x_{3}}{\left(\tau \right)} + 2 \alpha_{2} \epsilon^{4} \operatorname{x_{1}}{\left(\tau \right)} \operatorname{x_{3}}{\left(\tau \right)} + \alpha_{2} \epsilon^{4} \operatorname{x_{2}}^{2}{\left(\tau \right)} + 2 \alpha_{2} \epsilon^{3} \operatorname{x_{1}}{\left(\tau \right)} \operatorname{x_{2}}{\left(\tau \right)} + \alpha_{2} \epsilon^{2} \operatorname{x_{1}}^{2}{\left(\tau \right)} + \alpha_{3} \epsilon^{9} \operatorname{x_{3}}^{3}{\left(\tau \right)} + 3 \alpha_{3} \epsilon^{8} \operatorname{x_{2}}{\left(\tau \right)} \operatorname{x_{3}}^{2}{\left(\tau \right)} + 3 \alpha_{3} \epsilon^{7} \operatorname{x_{1}}{\left(\tau \right)} \operatorname{x_{3}}^{2}{\left(\tau \right)} + 3 \alpha_{3} \epsilon^{7} \operatorname{x_{2}}^{2}{\left(\tau \right)} \operatorname{x_{3}}{\left(\tau \right)} + 6 \alpha_{3} \epsilon^{6} \operatorname{x_{1}}{\left(\tau \right)} \operatorname{x_{2}}{\left(\tau \right)} \operatorname{x_{3}}{\left(\tau \right)} + \alpha_{3} \epsilon^{6} \operatorname{x_{2}}^{3}{\left(\tau \right)} + 3 \alpha_{3} \epsilon^{5} \operatorname{x_{1}}^{2}{\left(\tau \right)} \operatorname{x_{3}}{\left(\tau \right)} + 3 \alpha_{3} \epsilon^{5} \operatorname{x_{1}}{\left(\tau \right)} \operatorname{x_{2}}^{2}{\left(\tau \right)} + 3 \alpha_{3} \epsilon^{4} \operatorname{x_{1}}^{2}{\left(\tau \right)} \operatorname{x_{2}}{\left(\tau \right)} + \alpha_{3} \epsilon^{3} \operatorname{x_{1}}^{3}{\left(\tau \right)} + \epsilon^{7} \omega_{2}^{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{3}}{\left(\tau \right)} + 2 \epsilon^{6} \omega_{1} \omega_{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{3}}{\left(\tau \right)} + \epsilon^{6} \omega_{2}^{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{2}}{\left(\tau \right)} + 2 \epsilon^{5} \omega_{0} \omega_{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{3}}{\left(\tau \right)} + \epsilon^{5} \omega_{1}^{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{3}}{\left(\tau \right)} + 2 \epsilon^{5} \omega_{1} \omega_{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{2}}{\left(\tau \right)} + \epsilon^{5} \omega_{2}^{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{1}}{\left(\tau \right)} + 2 \epsilon^{4} \omega_{0} \omega_{1} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{3}}{\left(\tau \right)} + 2 \epsilon^{4} \omega_{0} \omega_{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{2}}{\left(\tau \right)} + \epsilon^{4} \omega_{1}^{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{2}}{\left(\tau \right)} + 2 \epsilon^{4} \omega_{1} \omega_{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{1}}{\left(\tau \right)} + \epsilon^{3} \omega_{0}^{2} \operatorname{x_{3}}{\left(\tau \right)} + \epsilon^{3} \omega_{0}^{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{3}}{\left(\tau \right)} + 2 \epsilon^{3} \omega_{0} \omega_{1} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{2}}{\left(\tau \right)} + 2 \epsilon^{3} \omega_{0} \omega_{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{1}}{\left(\tau \right)} + \epsilon^{3} \omega_{1}^{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{1}}{\left(\tau \right)} + \epsilon^{2} \omega_{0}^{2} \operatorname{x_{2}}{\left(\tau \right)} + \epsilon^{2} \omega_{0}^{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{2}}{\left(\tau \right)} + 2 \epsilon^{2} \omega_{0} \omega_{1} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{1}}{\left(\tau \right)} + \epsilon \omega_{0}^{2} \operatorname{x_{1}}{\left(\tau \right)} + \epsilon \omega_{0}^{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{1}}{\left(\tau \right)}$

```python
# Collect the coefficients for the epsilons
epsilon_Eq = sp.collect(EOM, epsilon, evaluate=False)
epsilon_1_Eq = sp.Eq(epsilon_Eq[epsilon], 0)
epsilon_1_Eq
```

$\displaystyle \omega_{0}^{2} \operatorname{x_{1}}{\left(\tau \right)} + \omega_{0}^{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{1}}{\left(\tau \right)} = 0$

```python
epsilon_2_Eq = sp.Eq(epsilon_Eq[epsilon**2], 0)
epsilon_2_Eq
```

$\displaystyle \alpha_{2} \operatorname{x_{1}}^{2}{\left(\tau \right)} + \omega_{0}^{2} \operatorname{x_{2}}{\left(\tau \right)} + \omega_{0}^{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{2}}{\left(\tau \right)} + 2 \omega_{0} \omega_{1} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{1}}{\left(\tau \right)} = 0$

```python
epsilon_3_Eq = sp.Eq(epsilon_Eq[epsilon**3], 0)
epsilon_3_Eq
```

$\displaystyle 2 \alpha_{2} \operatorname{x_{1}}{\left(\tau \right)} \operatorname{x_{2}}{\left(\tau \right)} + \alpha_{3} \operatorname{x_{1}}^{3}{\left(\tau \right)} + \omega_{0}^{2} \operatorname{x_{3}}{\left(\tau \right)} + \omega_{0}^{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{3}}{\left(\tau \right)} + 2 \omega_{0} \omega_{1} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{2}}{\left(\tau \right)} + 2 \omega_{0} \omega_{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{1}}{\left(\tau \right)} + \omega_{1}^{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{1}}{\left(\tau \right)} = 0$

```python
sp.dsolve(epsilon_1_Eq, x1)
```

$\displaystyle \operatorname{x_{1}}{\left(\tau \right)} = C_{1} \sin{\left(\tau \right)} + C_{2} \cos{\left(\tau \right)}$

Not as convenient as working with the polar form

```python
a = sp.symbols('a')
beta = sp.symbols('beta')
phi = tau + beta
x1_polar = a * sp.cos(tau + beta)
x1_polar
```

$\displaystyle a \cos{\left(\beta + \tau \right)}$

Update $\epsilon^2$ equation

```python
epsilon_2_Eq = sp.expand(TR7(epsilon_2_Eq.subs(x1, x1_polar)))
epsilon_2_Eq
```

$\displaystyle \frac{a^{2} \alpha_{2} \cos{\left(2 \beta + 2 \tau \right)}}{2} + \frac{a^{2} \alpha_{2}}{2} + \omega_{0}^{2} \operatorname{x_{2}}{\left(\tau \right)} + \omega_{0}^{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{2}}{\left(\tau \right)} + 2 \omega_{0} \omega_{1} \frac{\partial^{2}}{\partial \tau^{2}} a \cos{\left(\beta + \tau \right)} = 0$

In the equation above we can see that the $\cos\phi$ terms are secular which causes the approximation fail due to resonance. Therefore, we must set those terms to go to zero.

$\omega_1$ = 0

```python
epsilon_2_Eq = epsilon_2_Eq.subs(omega_i[1], 0)
epsilon_2_Eq
```

$\displaystyle \frac{a^{2} \alpha_{2} \cos{\left(2 \beta + 2 \tau \right)}}{2} + \frac{a^{2} \alpha_{2}}{2} + \omega_{0}^{2} \operatorname{x_{2}}{\left(\tau \right)} + \omega_{0}^{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{2}}{\left(\tau \right)} = 0$

```python
temp = sp.dsolve(epsilon_2_Eq)
temp
```

$\displaystyle \operatorname{x_{2}}{\left(\tau \right)} = C_{1} e^{- i \tau} + C_{2} e^{i \tau} + \frac{a^{2} \alpha_{2} \cos{\left(2 \beta + 2 \tau \right)}}{6 \omega_{0}^{2}} - \frac{a^{2} \alpha_{2}}{2 \omega_{0}^{2}}$

```python
x2_p = a**2 * alpha_i[0] * sp.cos(2*phi) / 6 / omega_i[0]**2 - a**2 * alpha_i[0] / 2 / omega_i[0]**2
x2_p
```

$\displaystyle \frac{a^{2} \alpha_{2} \cos{\left(2 \beta + 2 \tau \right)}}{6 \omega_{0}^{2}} - \frac{a^{2} \alpha_{2}}{2 \omega_{0}^{2}}$

We only take the particular solution into consideration, and update $\epsilon^3$ equation

```python
epsilon_3_Eq = epsilon_3_Eq.subs([
    (omega_i[1], 0), (x1, x1_polar), (x2, x2_p)
])
```

```python
epsilon_3_Eq = TR8(epsilon_3_Eq)
epsilon_3_Eq
```

$\displaystyle \frac{a^{3} \alpha_{2}^{2} \left(- 5 \cos{\left(\beta + \tau \right)} + \cos{\left(3 \beta + 3 \tau \right)}\right)}{6 \omega_{0}^{2}} + a^{3} \alpha_{3} \left(\frac{3 \cos{\left(\beta + \tau \right)}}{4} + \frac{\cos{\left(3 \beta + 3 \tau \right)}}{4}\right) + \omega_{0}^{2} \operatorname{x_{3}}{\left(\tau \right)} + \omega_{0}^{2} \frac{d^{2}}{d \tau^{2}} \operatorname{x_{3}}{\left(\tau \right)} + 2 \omega_{0} \omega_{2} \frac{\partial^{2}}{\partial \tau^{2}} a \cos{\left(\beta + \tau \right)} = 0$

We remove the secular term by setting

$$
\begin{gather*}
    \omega_2 = \dfrac{(9 \alpha_3 \omega^2_0 - 10 \alpha_2^2) a^2}{24\omega_0^3}
\end{gather*}
$$

```python
omega_2_new = (9 * alpha_i[1] * omega_i[0]**2 - 10 * alpha_i[0]**2) * a**2 / 24 / omega_i[0]**3
omega = omega_i[0] + epsilon**2 *  omega_2_new
omega
```

$\displaystyle \frac{a^{2} \epsilon^{2} \left(- 10 \alpha_{2}^{2} + 9 \alpha_{3} \omega_{0}^{2}\right)}{24 \omega_{0}^{3}} + \omega_{0}$

```python
t = sp.symbols('t')
x_new = epsilon * a * sp.cos(omega * t + beta)
x_new
```

$\displaystyle a \epsilon \cos{\left(\beta + t \left(\frac{a^{2} \epsilon^{2} \left(- 10 \alpha_{2}^{2} + 9 \alpha_{3} \omega_{0}^{2}\right)}{24 \omega_{0}^{3}} + \omega_{0}\right) \right)}$

We can find $x_3(t)$ after this, but we do not have to. This is because we will "reconstitute" the solution to $O(\epsilon^3)$

$$
\begin{align*}
    \begin{cases}
        x &= \epsilon x_1 + \epsilon^2 x_2 + O(\epsilon^3)\\
        \omega &= \omega_0 + \epsilon \omega_1 + \epsilon^2 \omega_2 + O(\epsilon^3)\\
        \tau &= \omega t
    \end{cases}
\end{align*}
$$

```python
# Imposing provided initial conditions
a0 = sp.symbols('a_0', real=True, positive=True)
beta0 = sp.symbols('beta_0', real=True, positive=True)

x0 = epsilon * a0 * sp.cos(beta0)
x0
```

$\displaystyle a_{0} \epsilon \cos{\left(\beta_{0} \right)}$

```python
xdot0 = -epsilon * a0 * sp.sin(beta0)
xdot0
```

$\displaystyle - a_{0} \epsilon \sin{\left(\beta_{0} \right)}$

Expand the $x_1$ homogeneous coefficients

```python
A_i = sp.symbols('A_(1:' + str(N+1) + ')')
B_i = sp.symbols('B_(1:' + str(N+1) + ')')

a_i = sum([epsilon ** i * A_i[i] for i in range(N)])
a_i
```

$\displaystyle A_{1} + A_{2} \epsilon + A_{3} \epsilon^{2}$

```python
beta_i = sum([epsilon ** i * B_i[i] for i in range(N)])
beta_i
```

$\displaystyle B_{1} + B_{2} \epsilon + B_{3} \epsilon^{2}$

Establish the expansion for $\omega$ (found during the Lindstedt-Poincare analysis)

```python
omega = omega.subs(a, a_i)
omega
```

$\displaystyle \frac{\epsilon^{2} \left(- 10 \alpha_{2}^{2} + 9 \alpha_{3} \omega_{0}^{2}\right) \left(A_{1} + A_{2} \epsilon + A_{3} \epsilon^{2}\right)^{2}}{24 \omega_{0}^{3}} + \omega_{0}$

Establish the expansion for $x$ (found during the Lindstedt-Poincare analysis)

```python
x1_new = a_i * sp.cos(omega * t + beta_i)
x2_new = x2_p.subs([
    (tau, omega * t), (a, a_i), (beta, beta_i)
])
x_new = epsilon * x1_new + epsilon ** 2 * x2_new
```

```python
x_new
```

$\displaystyle \epsilon^{2} \left(\frac{\alpha_{2} \left(A_{1} + A_{2} \epsilon + A_{3} \epsilon^{2}\right)^{2} \cos{\left(2 B_{1} + 2 B_{2} \epsilon + 2 B_{3} \epsilon^{2} + 2 t \left(\frac{\epsilon^{2} \left(- 10 \alpha_{2}^{2} + 9 \alpha_{3} \omega_{0}^{2}\right) \left(A_{1} + A_{2} \epsilon + A_{3} \epsilon^{2}\right)^{2}}{24 \omega_{0}^{3}} + \omega_{0}\right) \right)}}{6 \omega_{0}^{2}} - \frac{\alpha_{2} \left(A_{1} + A_{2} \epsilon + A_{3} \epsilon^{2}\right)^{2}}{2 \omega_{0}^{2}}\right) + \epsilon \left(A_{1} + A_{2} \epsilon + A_{3} \epsilon^{2}\right) \cos{\left(B_{1} + B_{2} \epsilon + B_{3} \epsilon^{2} + t \left(\frac{\epsilon^{2} \left(- 10 \alpha_{2}^{2} + 9 \alpha_{3} \omega_{0}^{2}\right) \left(A_{1} + A_{2} \epsilon + A_{3} \epsilon^{2}\right)^{2}}{24 \omega_{0}^{3}} + \omega_{0}\right) \right)}$

```python
xdot_new = sp.diff(x_new, t)
xdot_new
```

$\displaystyle - \frac{\alpha_{2} \epsilon^{2} \left(\frac{\epsilon^{2} \left(- 10 \alpha_{2}^{2} + 9 \alpha_{3} \omega_{0}^{2}\right) \left(A_{1} + A_{2} \epsilon + A_{3} \epsilon^{2}\right)^{2}}{12 \omega_{0}^{3}} + 2 \omega_{0}\right) \left(A_{1} + A_{2} \epsilon + A_{3} \epsilon^{2}\right)^{2} \sin{\left(2 B_{1} + 2 B_{2} \epsilon + 2 B_{3} \epsilon^{2} + 2 t \left(\frac{\epsilon^{2} \left(- 10 \alpha_{2}^{2} + 9 \alpha_{3} \omega_{0}^{2}\right) \left(A_{1} + A_{2} \epsilon + A_{3} \epsilon^{2}\right)^{2}}{24 \omega_{0}^{3}} + \omega_{0}\right) \right)}}{6 \omega_{0}^{2}} - \epsilon \left(\frac{\epsilon^{2} \left(- 10 \alpha_{2}^{2} + 9 \alpha_{3} \omega_{0}^{2}\right) \left(A_{1} + A_{2} \epsilon + A_{3} \epsilon^{2}\right)^{2}}{24 \omega_{0}^{3}} + \omega_{0}\right) \left(A_{1} + A_{2} \epsilon + A_{3} \epsilon^{2}\right) \sin{\left(B_{1} + B_{2} \epsilon + B_{3} \epsilon^{2} + t \left(\frac{\epsilon^{2} \left(- 10 \alpha_{2}^{2} + 9 \alpha_{3} \omega_{0}^{2}\right) \left(A_{1} + A_{2} \epsilon + A_{3} \epsilon^{2}\right)^{2}}{24 \omega_{0}^{3}} + \omega_{0}\right) \right)}$

For IC analysis, taylor $x$ and $\dot{x}$ up to order $\epsilon^3$

```python
# The built in sympy series command takes forever (bad algorithm) so we just implement our original Taylor series function
def taylor(expr, var, upto):
    res = expr.subs(var, 0)
    dexpr = sp.diff(expr, var)
    for n in range(1,upto+1):
        res += dexpr.subs(var, 0) / factorial(n) * var**n
        dexpr = sp.diff(dexpr, var)
    return res + sp.O(var**upto)

xTaylor = taylor(x_new, epsilon, 3)
xTaylor
```

$\displaystyle \epsilon^{2} \left(\frac{A_{1}^{2} \alpha_{2} \cos{\left(2 B_{1} + 2 \omega_{0} t \right)}}{6 \omega_{0}^{2}} - \frac{A_{1}^{2} \alpha_{2}}{2 \omega_{0}^{2}} - A_{1} B_{2} \sin{\left(B_{1} + \omega_{0} t \right)} + A_{2} \cos{\left(B_{1} + \omega_{0} t \right)}\right) + A_{1} \epsilon \cos{\left(B_{1} + \omega_{0} t \right)} + O\left(\epsilon^{3}\right)$

```python
xdotTaylor = taylor(xdot_new, epsilon, 3)
xdotTaylor
```

$\displaystyle \epsilon^{2} \left(- \frac{A_{1}^{2} \alpha_{2} \sin{\left(2 B_{1} + 2 \omega_{0} t \right)}}{3 \omega_{0}} - A_{1} B_{2} \omega_{0} \cos{\left(B_{1} + \omega_{0} t \right)} - A_{2} \omega_{0} \sin{\left(B_{1} + \omega_{0} t \right)}\right) - A_{1} \epsilon \omega_{0} \sin{\left(B_{1} + \omega_{0} t \right)} + O\left(\epsilon^{3}\right)$

Order $\epsilon$ initial condition for $x$

```python
temp = sp.collect(xTaylor.removeO(), epsilon, evaluate=False)
ICeq1 = sp.Eq(temp[epsilon].subs(t, 0), x0 / epsilon)
ICeq1
```

$\displaystyle A_{1} \cos{\left(B_{1} \right)} = a_{0} \cos{\left(\beta_{0} \right)}$

Order $\epsilon^2$ initial condition equation for $x$

```python
ICeq2 = sp.Eq(temp[epsilon**2].subs(t, 0), 0)
ICeq2
```

$\displaystyle \frac{A_{1}^{2} \alpha_{2} \cos{\left(2 B_{1} \right)}}{6 \omega_{0}^{2}} - \frac{A_{1}^{2} \alpha_{2}}{2 \omega_{0}^{2}} - A_{1} B_{2} \sin{\left(B_{1} \right)} + A_{2} \cos{\left(B_{1} \right)} = 0$

Order $\epsilon$ initial condition for $\dot{x}$

```python
temp = sp.collect(xdotTaylor.removeO(), epsilon, evaluate=False)
ICeq3 = sp.Eq(temp[epsilon].subs(t, 0), -a0 * omega_i[0] * sp.sin(beta0))
ICeq3
```

$\displaystyle - A_{1} \omega_{0} \sin{\left(B_{1} \right)} = - a_{0} \omega_{0} \sin{\left(\beta_{0} \right)}$

Order $\epsilon^2$ initial condition equation for $\dot{x}$

```python
ICeq4 = sp.Eq(temp[epsilon**2].subs(t, 0), 0)
ICeq4
```

$\displaystyle - \frac{A_{1}^{2} \alpha_{2} \sin{\left(2 B_{1} \right)}}{3 \omega_{0}} - A_{1} B_{2} \omega_{0} \cos{\left(B_{1} \right)} - A_{2} \omega_{0} \sin{\left(B_{1} \right)} = 0$

```python
res = sp.solve([ICeq1, ICeq2, ICeq3, ICeq4], [A_i[0], A_i[1], B_i[0], B_i[1]], force=True)
```

```python
res[0][0].subs(sp.atan(sp.tan(beta0/2)), beta0/2)
```

$\displaystyle a_{0}$

```python
sp.simplify(res[0][1].subs(sp.atan(sp.tan(beta0/2)), beta0/2))
```

$\displaystyle \frac{a_{0}^{2} \alpha_{2} \left(2 \sin{\left(2 \beta_{0} \right)} + \sin{\left(4 \beta_{0} \right)}\right)}{24 \omega_{0}^{2} \sin{\left(\beta_{0} \right)}}$

```python
res[0][2].subs(sp.atan(sp.tan(beta0/2)), beta0/2)
```

$\displaystyle \beta_{0}$

```python
sp.simplify(TR8(res[0][3].subs(sp.atan(sp.tan(beta0/2)), beta0/2)))
```

$\displaystyle - \frac{a_{0} \alpha_{2} \left(9 \sin{\left(\beta_{0} \right)} + \sin{\left(3 \beta_{0} \right)}\right)}{12 \omega_{0}^{2}}$


```python
from sympy import Symbol
from sympy import expand, factor

x = Symbol('x')
ex = (x + 3/(x**2))**5
expand(ex)

```

$\displaystyle x^{5} + 15 x^{2} + \frac{90}{x} + \frac{270}{x^{4}} + \frac{405}{x^{7}} + \frac{243}{x^{10}}$

답 15


```python
import time
import random
from typing import List
import sympy
import math
import string
import types
```

```python
sympy.init_printing()
```

```python
def quick_sort(collection: list) -> list:
    if len(collection) < 2:
        return collection
    pivot = collection.pop()
    greater: List[int] = []
    lesser: List[int] = []

    for element in collection:
        (greater if element > pivot else lesser).append(element)
    return quick_sort(lesser) + [pivot] + quick_sort(greater)
```

```python
user_input = input("input numbers seperated by a comma:\n").strip()
unsorted = user_input.split(',')
print(quick_sort(unsorted))
```

    ['']

```python
unsorted = random.shuffle(list(range(1000000)), None)
```

```python
n = sympy.Symbol('n')
expr = sympy.solve(8 * sympy.functions.log(n, 2) - n)
expr
```

```python
expr[0].evalf()
```

```python
f_1 = 8 * sympy.functions.ln(n)
f_2 = n
sympy.plot(f_1, f_2, (n,0.1,100))
```

```python
f_1 = (n**2 * 100)
f_2 = (2**n)
p = sympy.plot(f_1, f_2, (n,0,100), show=False, legend=True)
p[0].label = '$100 * n**2$'
p[0].line_color = 'red'
p[1].label = '$2**n$'
p.show()
```

```python
f_1 = sympy.log(n)
f_2 = sympy.sqrt(n)
f_3 = n
f_4 = n * sympy.log(n)
f_5 = n**2
f_6 = n**3
f_7 = 2**n
f_8 = sympy.factorial(n)
```

```python
t0 = 1000 # 1000 milliseconds
```

```python
r_1 = sympy.solve(f_1 - t0)
r_2 = sympy.solve(f_2 - t0)
r_3 = sympy.solve(f_3 - t0)
r_4 = sympy.solve(f_4 - t0)
r_5 = sympy.solve(f_5 - t0)
r_6 = sympy.solve(f_6 - t0)
r_7 = sympy.solve(f_7 - t0)
```

```python
r_8 = sympy.solve(f_8 - t0)
```

```python
print(r_1, r_2, r_3, r_4, r_5, r_6, r_7)
```

    [exp(1000)] [1000000] [1000] [exp(LambertW(1000))] [-10*sqrt(10), 10*sqrt(10)] [10, -5 - 5*sqrt(3)*I, -5 + 5*sqrt(3)*I] [log(10**(3/log(2)))]

```python
# c_01 = random.sample(range(-100, 100), 10)
c_01 = random.choices(string.ascii_letters + string.digits, k=10)

print(c_01)

for i, item in enumerate(c_01):
    for j in range(i)[::-1]:
        if item < c_01[j]:
            c_01[j+1] = c_01[j]
            c_01[j] = item
        else:
            break
print(c_01)
```

    ['M', 'X', 'L', '8', 'D', 'i', 'g', 'S', 'b', 'N']
    ['8', 'D', 'L', 'M', 'N', 'S', 'X', 'b', 'g', 'i']

```python
A = random.sample(range(-100, 100), 10)
print(A)
# A = random.choices(string.ascii_letters + string.digits, k=10)

for j, key in enumerate(A):
    if j == 0:
        pass
    i = j - 1
    while i > 0 and A[i] > key:
        A[i+1] = A[i]
        i = i - 1
    A[i+1] = key

print(A)
```

```python
def fib(n: int) -> int:
    if n <= 0: return 0
    if n == 1 | n == 2: return 1
    prev = curr= 1
    for i in range(3, n+1):
        sum = prev + curr
        prev = curr
        curr = sum
    return curr
```

```python

fib(4)
```

    3

```python
def fib_recursive(n: int) -> int:
    if n <= 2: return 1
    return fib_recursive(n-1) + fib_recursive(n-2)
```

```python
fib_recursive(6)
```

    8

```python
def coinChange(coins: List[int], amount: int) -> int:
    # 数组大小为 amount，初始值为 n，对于每个 amount，最多需要 amount 个 1 分的 coin。
    results = list(range(amount+1))
    for i, r in enumerate(results): # 从 0 开始计算最小 amount
        if i == 0: continue # start from amount == 1
        for coin in coins:
            remains = r - coin
            if remains < 0: continue
            results[i] = min(r, 1 + results[remains - coin])
    return results[amount]

```

```python
for amount in range(12):
    print("{}, {}".format(amount, coinChange([1,2,5], amount)))
```

    0, 0
    1, 1
    2, 2
    3, 3
    4, 1
    5, 2
    6, 4
    7, 5
    8, 5
    9, 5
    10, 1
    11, 2

```python
def coinChange(coins: List[int], amount: int) -> int:
    if amount < 0: return -1
    dp = list(range(amount+1))
    for i in range(amount+1):
        result = []
        for coin in coins:
```


# Lunch Time Python

## Lunch 2: SymPy

[SymPy](https://www.sympy.org/) is a Python library for symbolic mathematics.
It can parse mathematical expressions, substitute, differentiate, integrate and evaluate them, as well as solve algebraic and differential equations.
It is also itself written entirely in Python, with a focus keeping the code comprehensible and easily extensible.
There is also a related project [SymEngine](https://github.com/symengine/symengine.py),
which is written in C++ with a focus on speed, which offers a much faster implementation of a subset of SymPy's functionality.

*Press `Spacebar` to go to the next slide (or `?` to see all navigation shortcuts)*

[Lunch Time Python](https://ssciwr.github.io/lunch-time-python/), [Scientific Software Center](https://ssc.iwr.uni-heidelberg.de), [Heidelberg University](https://www.uni-heidelberg.de/)

# SymPy Installation

- Anaconda: pre-installed
- Conda: `conda install sympy`
- Pip: `python -m pip install sympy`

Or try it out online:

- [live.sympy.org](https://live.sympy.org/)
  - online python shell with SymPy installed
- [sympygamma.com](https://www.sympygamma.com/input/?i=cos%28x%29)
  - SymPy powered web app similar to [Wolfram|Alpha](https://www.wolframalpha.com/)
- [this notebook on binder](https://mybinder.org/v2/gh/ssciwr/lunch-time-python.git/HEAD?labpath=lunchtime2%2Flunchtime2.ipynb)
  - interactive version of this notebook on binder

# Symbols

- basic building block of expressions
- `sympy.symbols`
  - takes a string of variable names separated by spaces
  - returns a tuple of Symbol objects, one for each name
  - usually a good idea to use the same name for the Python object, but you don't have to

```python
import sympy as sp

# Define a single symbol x named 'x':

x = sp.symbols("x")
```

```python
x
```

```python
type(x)
```

```python
# Define multiple symbols at once:

y, z, t, nu = sp.symbols("y z t nu")
```

```python
# Can use different name for symbol & object (not recommended!)

confusing = sp.symbols("alpha")
print(confusing)
```

# Expressions

- Symbols can be combined with the usual arithmetic operations (`+`, `-`, `*`, `/`)
- Result is an expression
- The type of an expression depends on the operation

```python
expr = x + y - 2 * z
```

```python
expr
```

```python
type(expr)
```

```python
expr.args
```

```python
type(expr.args[2])
```

```python
expr.args[2].args
```

# Numbers

- will automatically convert to SymPy Numbers when required
- but take care with math only involving Python numbers!
- sometimes an explicit conversion is helpful

```python
x + 1
```

```python
type((1 + x).args[0])
```

```python
type((123 + x).args[0])
```

```python
# be careful with unexpected Python maths!
x + 1 / 2
```

```python
# explicit construction of Rational
x + sp.Rational(1, 2)
```

```python
# explicit construction of Integer
x + sp.Integer(1) / 2
```

```python
# equivalent but now all operations involve a sympy expr:
(2 * x + 1) / 2
```

# Functions

- `sympy.functions` contains many built-in analytic functions

```python
expr = sp.sin(x) * sp.exp(y) + sp.gamma(t)
```

```python
expr
```

# Substitution

- evaluate an expression by substituting numbers for symbols
- substitute sub-expressions for sub-expressions
- `.subs` takes a list of `(old, new)` pairs

```python
(x + 1).subs(x, 1)
```

```python
(x * sp.cos(y)).subs([(x, 1), (y, 0.2)])
```

```python
(x * y).subs(x, y ** 3)
```

# Parsing

- `sympify` converts a string into a SymPy expression
- existing symbols are used if the name matches
- otherwise new symbols will be created as necessary

```python
expr = sp.sympify("x")
```

```python
type(expr)
```

```python
expr == x
```

```python
expr = sp.sympify("cos(x) + a")
```

```python
expr
```

```python
expr.args[0]
```

```python
expr.args[1].args[0] == x
```

# Evaluating

- `evalf` for one off numerical evaluation of an expression
    - can take a dict of `Symbol : number` pairs
- `lambdify` for efficient repeated evaluation of an expression
    - replaces sympy functions like `sin`, `cos` with numpy equivalents

```python
sp.sqrt(2)
```

```python
sp.sqrt(2).evalf()
```

```python
expr = sp.sqrt(1 + x)
```

```python
expr
```

```python
expr.evalf(subs={x: 1})
```

```python
f = sp.lambdify(x, expr)
```

```python
f(1)
```

```python
import numpy as np

a = np.array([1, 2, 3, 5.21])
f(a)
```

# Simplification

- `simplify` applies various simplifications to an expression
- `expand` expands a polynomial to a sum of monomials
- `factor` looks for common terms to factorize a polynomial
- `collect` collects common powers of a term
- as well as [many more](https://docs.sympy.org/latest/tutorial/simplification.html)

```python
sp.cos(x) ** 2 + sp.sin(x) ** 2
```

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

```python
sp.gamma(x) / sp.gamma(x - 3)
```

```python
sp.simplify(sp.gamma(x) / sp.gamma(x - 3))
```

```python
(z + t) ** 2 * (x + 2 * y) ** 6
```

```python
print(sp.expand((z + t) ** 2 * (x + 2 * y) ** 6))
```

```python
sp.factor(
    t ** 2 * x ** 6
    + 12 * t ** 2 * x ** 5 * y
    + 60 * t ** 2 * x ** 4 * y ** 2
    + 160 * t ** 2 * x ** 3 * y ** 3
    + 240 * t ** 2 * x ** 2 * y ** 4
    + 192 * t ** 2 * x * y ** 5
    + 64 * t ** 2 * y ** 6
    + 2 * t * x ** 6 * z
    + 24 * t * x ** 5 * y * z
    + 120 * t * x ** 4 * y ** 2 * z
    + 320 * t * x ** 3 * y ** 3 * z
    + 480 * t * x ** 2 * y ** 4 * z
    + 384 * t * x * y ** 5 * z
    + 128 * t * y ** 6 * z
    + x ** 6 * z ** 2
    + 12 * x ** 5 * y * z ** 2
    + 60 * x ** 4 * y ** 2 * z ** 2
    + 160 * x ** 3 * y ** 3 * z ** 2
    + 240 * x ** 2 * y ** 4 * z ** 2
    + 192 * x * y ** 5 * z ** 2
    + 64 * y ** 6 * z ** 2
)
```

# Differentiation

- `sympy.diff` differentiates expressions
  - `diff(f, x)` : $df/dx$
  - `diff(f, x, 3)` : $d^3f/dx^3$
  - `diff(f, x, y, z)` : $d^3f/dxdydz$

```python
expr
```

```python
sp.diff(expr, x)
```

# Integration

- `sympy.integrate` integrates expressions
  - indefinite by default, without the integration constant
  - definite if tuple of `(Symbol, lower_limit, upper_limit)` is provided

```python
expr
```

```python
sp.integrate(expr, x)
```

```python
sp.integrate(expr, (x, 0, 1))
```

```python
sp.diff(sp.integrate(expr, x), x)
```

# Limits

- `sympy.limit` takes limit of expression at singular point

```python
sinc = sp.sin(x) / x
```

```python
sinc.subs(x, 0)
```

```python
sinc.limit(x, 0)
```

# Series expansion

- `sympy.series` expands an expression around a point

```python
sinc.series(x, 0, 10)
```

# SymEngine

- Fast C++ implementation of (some of) SymPy

```python
expr = sp.cos(x + 7 * x ** 2) + sp.sin(x - 2 * x ** 3)
```

```python
%time _ = (sp.series(expr, n=100))
```

```python
import symengine as se

%time _ = se.series(expr, n=100)
```

# More at [docs.sympy.org](https://docs.sympy.org/)

- Algebraic equation solving
- Differential equation solving
- Matrices
- Assumptions
- Printing
- Code generation

## [ssciwr.github.io/lunch-time-python](https://ssciwr.github.io/lunch-time-python/)