# beta distribution
The distribution is
\begin{equation}
    \begin{split}
        f(x;\alpha,\beta) &= \dfrac{x^{\alpha-1}(1-x)^{\beta-1}}{\int_0^1 u^{\alpha-1}(1-u)^{\beta-1} du} \\
        & = \dfrac{\Gamma(\alpha+\beta)}{\Gamma(\alpha)\Gamma(\beta)}x^{\alpha-1}(1-x)^{\beta-1} \\
        & = \dfrac{1}{B(\alpha, \beta)}x^{\alpha-1}(1-x)^{\beta-1}
    \end{split}
\end{equation}
$\Gamma$ and $B$ are gamma and beta function respectively.

Average and variance are,
\begin{equation}
    \begin{split}
        \bar{X} &= \dfrac{\alpha}{\alpha+\beta} \\
        \bar{X\prime\prime^2} &= \dfrac{\alpha\beta}{(\alpha+\beta)^2(\alpha+\beta+1)}
    \end{split}
\end{equation}
With the mean and variance,
\begin{equation}
    \begin{split}
        \alpha & = \bar{X}\left(\dfrac{1}{\eta_X}-1\right) \\
        \beta & = \left(1-\bar{X}\right)\left(\dfrac{1}{\eta_X}-1\right)
    \end{split}
\end{equation}
where
\begin{equation}
    \eta_X = \dfrac{\bar{X\prime\prime^2}}{\bar{X}\left(1-\bar{X}\right)}
\end{equation}

```python
import sys
import numpy as np
import matplotlib.pyplot as plt
import numpy.polynomial.polynomial as P
from scipy.integrate import simps
from scipy.stats import beta
from scipy.interpolate import interp1d
import scipy as sp
```

```python
def beta_coef(ave, var):
    """Obtain the coefficients for beta distribution with average and variance"""
    a = ave*(1./var-1.)
    b = (1.-ave)*(1./var-1.)
    return a, b
```

```python
# Numerical Integration
```

```python
def beta_integration_direct(f, x, a, b, fit='cubic'):
    """Integrate over the given sample, intepolate pdf at the boundary"""
    # f(x): function value
    # x: axis points
    # a: alpha for the beta distribution
    # b: beta for the beta distribution
    # fit: fitting formula

    rv = beta(a, b)
    pdf = rv.pdf(x)

    y = interp1d(x[1:-1], pdf[1:-1],
                 kind=fit,
                 fill_value='extrapolate'
                )

    if a < 1. :
        pdf[0] = y(0.) if y(0.) > 0. else 0.
    if b < 1. :
        pdf[-1] = y(1.) if y(1.) > 0. else 0.

    return simps(f*pdf, x)
```

```python
def beta_integration_log(f, x, a, b, fit='cubic', epsilon=1.e-9, n_ext=100):
    """Integrate over the given sample, log spaced intepolation of function and pdf towards the boundary"""

    f_interp = interp1d(f, x,
                        kind = fit,
                        fill_value = 'extrapolate'
                       )

    if a < 1.:
        x_ext = np.logspace(np.log10(epsilon),
                            np.log10(x[1]),
                            num=n_ext
                           )
        x_ext = np.insert(x_ext, 0, 0.)
        f_ext = f_interp(x_ext)

        x = np.hstack((x_ext, x[2:]))
        f = np.hstack((f_ext, f[2:]))

    if b < 1.:
        x_ext = np.logspace(np.log10(epsilon),
                            np.log10(1.-x[-2]),
                            num=n_ext
                           )
        x_ext = np.flip( 1. - np.insert(x_ext, 0, 0.) )
        f_ext = f_interp(x_ext)

        x = np.hstack((x[:-2], x_ext))
        f = np.hstack((f[:-2], f_ext))

    rv = beta(a, b)
    pdf = rv.pdf(x)

    y = interp1d(x[1:-1], pdf[1:-1],
                 kind=fit,
                 fill_value='extrapolate'
                )

    if a < 1. :
        pdf[0] = y(0.) if y(0.) > 0. else 0.
    if b < 1. :
        pdf[-1] = y(1.) if y(1.) > 0. else 0.

    return simps(f*pdf, x)
```

```python
def beta_integration_linear(f, x, a, b, fit='cubic', n_ext=100):
    """Integrate over the given sample, linear spaced intepolation of function and pdf towards the boundary"""

    f_interp = interp1d(f, x,
                        kind = fit,
                        fill_value = 'extrapolate'
                       )

    if a < 1.:
        x_ext = np.linspace(0., x[1], num=n_ext)
        f_ext = f_interp(x_ext)

        x = np.hstack((x_ext, x[2:]))
        f = np.hstack((f_ext, f[2:]))

    if b < 1.:
        x_ext = np.linspace(x[-2], 1., num=n_ext)
        f_ext = f_interp(x_ext)

        x = np.hstack((x[:-2], x_ext))
        f = np.hstack((f[:-2], f_ext))

    rv = beta(a, b)
    pdf = rv.pdf(x)

    y = interp1d(x[1:-1], pdf[1:-1],
                 kind=fit,
                 fill_value='extrapolate'
                )

    if a < 1. :
        pdf[0] = y(0.) if y(0.) > 0. else 0.
    if b < 1. :
        pdf[-1] = y(1.) if y(1.) > 0. else 0.

    return simps(f*pdf, x)
```

# Analytical integration of the beta distribution
For the polynomial function
\begin{equation}
    \phi(x) = \sum_{i=0}^n c_i x^i,
\end{equation}
integration with the beta distribution
\begin{equation}
    \begin{split}
        \int_{x_1}^{x_2} \phi(x) f(x;\alpha, \beta) dx &= \int_{x_1}^{x_2} \sum_{i=0}^n c_i x^i f(x;\alpha, \beta) dx \\
        &= \sum_{i=0}^n c_i\dfrac{B(\alpha+i,\beta)}{B(\alpha,\beta)}\int_{x_1}^{x_2} f(x;\alpha+i, \beta) dx
    \end{split}
\end{equation}

```python
def beta_integration_analytic(f, x, a, b):

    rv0 = beta(a, b)
    cdf0 = rv0.cdf(x)
    B0 = sp.special.beta(a, b)

    rv1 = beta(a+1., b)
    cdf1 = rv1.cdf(x)
    B1 = sp.special.beta(a+1, b)

    c0 = np.zeros(x.size)
    c1 = np.zeros(x.size)

    for i in range(x.size-1):
        c = P.polyfit(x[i:i+2], f[i:i+2], 1)

        c0[i] -= c[0]
        c0[i+1] += c[0]

        c1[i] -= c[1]
        c1[i+1] += c[1]

    c1 *= B1/B0

    return np.sum(c0*cdf0+c1*cdf1)
```

```python
def delta_integration(f, x, x_ave):
    y = interp1d(x, f, kind='cubic')
    return y(x_ave)
```

```python
def bimodal_integration(f, x_ave):
    return f[0]*(1.-x_ave)+f[-1]*x_ave
```

```python
def beta_integration(f, x, x_ave, x_nvar, method='analytic'):

    epsilon = 1.e-9

    if x_ave < epsilon:
        return f[0]
    elif x_ave > 1.-epsilon:
        return f[-1]
    elif x_nvar < epsilon:
        return delta_integration(f, x, x_ave)
    elif x_nvar > 1.-epsilon:
        return bimodal_integration(f, x_ave)
    else:
        a, b = beta_coef(x_ave, x_nvar)
        #return beta_integration_analytic(f, x, a, b)

        if method == 'analytic' :
            return beta_integration_analytic(f, x, a, b)
        elif method == 'log' :
            return beta_integration_log(v, v, a, b)
        elif method == 'linear' :
            return beta_integration_linear(v, v, a, b)
        else :
            return beta_integration_direct(v, v, a, b)
```

```python
# load flamelet solution
flamelet = np.genfromtxt('Table_1.csv', delimiter=',', names=True)
```

```python
methods = ['direct', 'log', 'linear', 'analytic']
ls = ['g:', 'b-.', 'c:', 'r--']
varName = 'Z'
v = flamelet[varName]
npts = 1000

data = np.zeros((npts, len(methods)))
```

```python
nvar = 0.8
for i, ave in enumerate(np.linspace(0., 1., num=npts)):
    for j, m in enumerate(methods):
        data[i, j] = beta_integration(v, v, ave, nvar, method=m)
```

```python
plt.figure(0)
for j, m in enumerate(methods):
    plt.plot(np.linspace(0., 1., num=npts), data[:,j], ls[j], label=m, lw=1)
plt.legend()
```

```python
ave = 0.1
for i, nvar in enumerate(np.linspace(0., 1., num=npts)):
    for j, m in enumerate(methods):
        data[i, j] = beta_integration(v, v, ave, nvar, method=m)
```

```python
plt.figure(1)
for j, m in enumerate(methods):
    plt.plot(np.linspace(0., 1., num=npts), data[:,j], ls[j], label=m, lw=1)
plt.legend()
```


# Playing with Sets and Probability
In this chapter, we’ll start by learning how
we can make our programs understand and
manipulate sets of numbers. We’ll then see how
sets can help us understand basic concepts in prob-
ability. Finally, we’ll learn about generating random
numbers to simulate random events. Let’s get started!

---

## Set Construction
In mathematical notation, you represent a set by writing the set members
enclosed in curly brackets. For example, {2, 4, 6} represents a set with 2, 4,
and 6 as its members. To create a set in Python, we can use the FiniteSet
class from the sympy package, as follows:

```python
from sympy import FiniteSet
s = FiniteSet(2,4,6)
s
```

$\displaystyle \left\{2, 4, 6\right\}$

```python
from sympy import FiniteSet
from fractions import Fraction
s = FiniteSet(1, 1.5, Fraction(1, 5))

print("Set is:" + str(s) + " and its length is:" + str(len(s)))
```

    Set is:FiniteSet(1/5, 1, 1.5) and its length is:3


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

# 4. Multivariate Regression

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

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

## Answer 4(a)

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

input-output training data $ \{(x_i,y_i) \}^m_{i=1} $ where each $x_i$ is a $d$-dimensional vector and each $y_i$ is a $p$-dimensional vector, meaning for each example of feature $x_i$ we get $p$ number of possible outputs. Thus $X$ is $m\times d $ dimensinal and $Y$ is $m \times p$ dimensional.
As we know $$Y=\phi(X) W$$
Assuming $\phi(X)$ remains same dimension as $X$, $W$ would be $d \times p$ dimensional.
For single output case, squared error loss function was
\begin{align}
    J(W) = & \sum_{i=1}^m || W^T x_i - y_i ||^2_2 \\
    J(W) = &  || X W - y ||^2_2 \\
    \bigtriangledown_W J= & \bigtriangledown_W \Big[ (XW-Y)^T (XW-Y) \Big]
\end{align}
Where W was $d \times 1$ dimensional.

But now for multiple outputs of $y_i$, assuming outputs are independet of prior,

squared error loss function for each possible $p$ output will be :
\begin{align}
    J(W_p) = &  || X W_p - y_p ||^2_2 \\
     \bigtriangledown_{W_p} J= & \bigtriangledown_{W_p} \Big[ (XW-Y)^T (XW-Y) \Big] \\
                            = & X^TX W_p -X^T y_p = 0 \\
    thus, \, W_p = & (X^TX)^{-1} X^T y_p
\end{align}

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

## Answer 4(b)

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

From the proof above we can say that if we independently solve $p$ classical linear regression an and get $W$ parameter $p$ times, we solve the multivariate regression task.

In this problem we assumed outputs are independent of prior but in baysian approximation where we have a prior on output, for multivate output we need to consider all the output prior for future $W$ parameter updates and thus can't calculate posterior output inpendently.

consider noisy conversation $y = f(x) + \epsilon = \phi(x)^T w + \epsilon$
according to bayes rules:

$ P_\phi(w|y,X) = \frac{ P_\phi(y|X,w)P(w) }{P_\phi(y|X) } $

$L = ||Y - XW||^2$

$W = (X^TX)^{-1}X^TY$

$L = ||Y - XW||^2 + ||XW - XW_R||^2$

```python

```


```python
from sympy import init_session
init_session()
```

    IPython console for SymPy 0.7.6 (Python 3.4.3-64-bit) (ground types: gmpy)

    These commands were executed:
    >>> from __future__ import division
    >>> from sympy import *
    >>> x, y, z, t = symbols('x y z t')
    >>> k, m, n = symbols('k m n', integer=True)
    >>> f, g, h = symbols('f g h', cls=Function)
    >>> init_printing()

    Documentation can be found at http://www.sympy.org

# Euler Equations

The Euler equations in primitive variable form, $q = (\rho, u, p)^\intercal$ appear as:

$$q_t + A(q) q_x = 0$$

with the matrix $A(q)$:

$$A(q) = \left ( \begin{array}{ccc} u  & \rho     & 0 \\
                                  0  &  u       & 1/\rho \\
                                  0  & \gamma p & u \end{array} \right )
$$

The sound speed is related to the adiabatic index, $\gamma$, as $c^2 = \gamma p /\rho$.

We can represent this matrix symbolically in SymPy and explore its eigensystem.

```python
from sympy.abc import rho
rho, u, c = symbols('rho u c')

A = Matrix([[u, rho, 0], [0, u, rho**-1], [0, c**2 * rho, u]])
A
```

The eigenvalues are the speeds at which information propagates with.  SymPy returns them as a
dictionary, giving the multiplicity for each eigenvalue.

```python
A.eigenvals()
```

The right eigenvectors are what SymPy gives natively.  For a given eigenvalue, $\lambda$, these
satisfy:

$$A r = \lambda r$$

## Right Eigenvectors

```python
R = A.eigenvects()   # this returns a tuple for each eigenvector with multiplicity -- unpack it
r = []
lam = []
for (ev, _, rtmp) in R:
    r.append(rtmp[0])
    lam.append(ev)

# we can normalize them anyway we want, so let's make the first entry 1
for n in range(len(r)):
    v = r[n]
    r[n] = v/v[0]
```

### 0-th right eigenvector

```python
r[0]
```

this corresponds to the eigenvalue

```python
lam[0]
```

### 1-st right eigenvector

```python
r[1]
```

this corresponds to the eigenvalue

```python
lam[1]
```

### 2-nd right eigenvector

```python
r[2]
```

this corresponds to the eigenvalue

```python
lam[2]
```

Here they are as a matrix, $R$, in order from smallest to largest eigenvalue

```python
R = zeros(3,3)
R[:,0] = r[1]
R[:,1] = r[0]
R[:,2] = r[2]
R
```

## Left Eigenvectors

The left eigenvectors satisfy:

$$l A = \lambda l$$

SymPy doesn't have a method to get left eigenvectors directly, so we take the transpose of this expression:

$$(l A)^\intercal = A^\intercal l^\intercal = \lambda l^\intercal$$

Therefore, the transpose of the left eigenvectors, $l^\intercal$, are the right eigenvectors of transpose of $A$

```python
B = A.transpose()
B
```

```python
L = B.eigenvects()
l = []
laml = []
for (ev, _, ltmp) in L:
    l.append(ltmp[0].transpose())
    laml.append(ev)

```

Traditionally, we normalize these such that $l^{(\mu)} \cdot r^{(\nu)} = \delta_{\mu\nu}$

```python
for n in range(len(l)):
    if lam[n] == laml[n]:
        ltmp = l[n]
        p = ltmp.dot(r[n])
        l[n] = ltmp/p
```

### 0-th left eigenvector

```python
l[0]
```

### 1-st left eigenvector

```python
l[1]
```

### 2-nd left eigenvector

```python
l[2]
```

```python

```


Symbolic Algebra with `sympy`
====

```python
from sympy import *
```

```python
from sympy import init_session
init_session()
```

    IPython console for SymPy 1.5.1 (Python 3.6.10-64-bit) (ground types: gmpy)

    These commands were executed:
    >>> from __future__ import division
    >>> from sympy import *
    >>> x, y, z, t = symbols('x y z t')
    >>> k, m, n = symbols('k m n', integer=True)
    >>> f, g, h = symbols('f g h', cls=Function)
    >>> init_printing()

    Documentation can be found at https://docs.sympy.org/1.5.1/

Basics
----

```python
from sympy.stats import *
E(Die('X', 6))
```

```python
sqrt(8)
```

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

```python
expr2 = x*expr
```

```python
expr2
```

```python
expand(expr2)
```

```python
factor(expand(expr2))
```

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

```python
integrate(exp(x)*sin(x) + exp(x)*cos(x), x)
```

```python
integrate(sin(x**2), (x, -oo, oo))
```

```python
dsolve(Eq(f(t).diff(t, t) - f(t), exp(t)), f(t))
```

```python
Matrix([[1,2],[2,2]]).eigenvals()
```

```python
nu = symbols('nu')
besselj(nu, z).rewrite(jn)
```

```python
latex(Integral(cos(x)**2, (x, 0, pi)))
```

    '\\int\\limits_{0}^{\\pi} \\cos^{2}{\\left(x \\right)}\\, dx'

```python
expr = cos(x) + 1
```

```python
expr.subs(x, y)
```

```python
expr = x**y
expr = expr.subs(y, x**y)
expr = expr.subs(y, x**y)
expr = expr.subs(x, x**x)
```

```python
expr
```

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

```python
expr.subs(sin(2*x), 2*sin(x)*cos(x))
```

```python
expr = x**4 - 4*x**3 + 4*x**2 - 2*x +3
```

```python
replacements = [(x**i, y**i) for i in range(5) if i%2 == 0]
expr.subs(replacements)
```

```python
sexpr = "x**4 - 4*x**3 + 4*x**2 - 2*x +3"
sympify(sexpr)
```

```python
sqrt(8).evalf()
```

```python
pi.evalf(100)
```

```python
expr = cos(2*x)
expr.evalf(subs = {x: 2.4})
```

```python
expr = cos(x)**2 + sin(x)**2
expr.evalf(subs = {x: 1}, chop=True)
```

```python
a = range(10)
expr = sin(x)
f = lambdify(x, expr, "numpy")
f(a)
```

    array([ 0.        ,  0.84147098,  0.90929743,  0.14112001, -0.7568025 ,
           -0.95892427, -0.2794155 ,  0.6569866 ,  0.98935825,  0.41211849])

Simplify
----

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

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

```python
expand((x + y)**3)
```

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

```python
factor_list(x**3 - x**2 + x - 1)
```

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

```python
expr = x*y + x - 3 + 2*x**2 - z*x**2 + x**3
cexpr = collect(expr, x)
cexpr
```

```python
cexpr.coeff(x**2)
```

```python
cancel((x**2 + 2*x + 1) / (x**2 + x))
```

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

```python
cancel(expr)
```

```python
factor(expr)
```

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

```python
apart(expr)
```

```python
trigsimp(sin(x)*tan(x)/sec(x))
```

```python
trigsimp(cosh(x)**2 + sinh(x)**2)
```

```python
expand_trig(sin(x + y))
```

```python
x, y = symbols('x y', positive=True)
a, b = symbols('a b', real=True)
z, t, c = symbols('z t c')
```

```python
powsimp(x**a*x**b)
```

```python
powsimp(x**a*y**a)
```

```python
powsimp(t**c*z**c)
```

```python
powsimp(t**c*z**c, force=True)
```

```python
expand_power_exp(x**(a + b))
```

```python
expand_power_base((x*y)**a)
```

```python
powdenest((x**a)**b)
```

```python
n = symbols('n', real=True)
```

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

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

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

```python
x, y, z = symbols('x y z')
k, m, n = symbols('k m n')
```

```python
factorial(n)
```

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

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

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

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

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

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

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

```python
expr
```

```python
hyperexpand(expr)
```

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

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

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

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

```python
syms
```

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

```python
frac
```

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

```python
frac
```

```python
from sympy.printing import print_ccode
print_ccode(frac)
```

    (a0*a1*a2*a3*a4 + a0*a1*a2 + a0*a1*a4 + a0*a3*a4 + a0 + a2*a3*a4 + a2 + a4)/(a1*a2*a3*a4 + a1*a2 + a1*a4 + a3*a4 + 1)

Calculus
----

```python
diff(cos(x), x)
```

```python
diff(x**4, x, 3)
```

```python
expr = exp(x*y*z)
diff(expr, x, y, 2, z, 4)
```

```python
deriv = Derivative(expr, x, y, 2, z, 4)
deriv
```

```python
deriv.doit()
```

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

```python
integrate(exp(-x), (x, 0, oo))
```

```python
integrate(exp(-x**2 - y**2), (x, -oo, oo), (y, -oo, oo))
```

```python
integrate(x**x, x)
```

```python
expr = Integral(log(x)**2, x)
expr
```

```python
expr.doit()
```

```python
integrate(sin(x**2), x)
```

```python
integrate(x**y*exp(-x), (x, 0, oo))
```

```python
limit(sin(x)/x, x, 0)
```

```python
expr = Limit((cos(x) - 1)/x, x, 0)
expr
```

```python
expr.doit()
```

```python
limit(1/x, x, 0, '-')
```

```python
expr = exp(sin(x))
expr.series(x, 0, 4)
```

```python
x + x**3 + x**6 + O(x**4)
```

```python
x * O(1)
```

```python
expr.series(x, 0, 4).removeO()
```

```python
exp(x - 6).series(x, 6)
```

```python
exp(x - 6).series(x, 6).removeO().subs(x, x - 6)
```

Working with matrices
----

```python
M = Matrix([[1,2,3],[3,2,1]])
P = Matrix([0,1,1])
M*P
```

```python
M
```

```python
M.shape
```

```python
M.col(-1)
```

```python
M = Matrix([[1,3], [-2,3]])
M**-1
```

```python
M.T
```

```python
eye(3)
```

```python
diag(1,2,3)
```

```python
M.det()
```

```python
M= Matrix([[1,0,1,3],[2,3,4,7],[-1,-3,-3,-4]])
M
```

```python
M.rref()
```

```python
M = Matrix([[1,2,3,0,0],[4,10,0,0,1]])
M
```

```python
M.nullspace()
```

```python
M = Matrix([[1,1,2],[1,4,1],[2,1,5]])
M
```

```python
L, U, P = M.LUdecomposition()
L, U, P
```

```python
L*U
```

```python
L, D = M.LDLdecomposition()
L, D
```

```python
L*D*L.T
```

```python
L = M.cholesky()
L
```

```python
L*L.T
```

```python
M = Matrix([[3, -2, 4, -2], [5,3,-3,-2], [5,-2,2,-2], [5,-2,-3,3]])
M
```

```python
M.eigenvals()
```

```python
M.eigenvects()
```

```python
P, D = M.diagonalize()
```

```python
P
```

```python
D
```

```python
P*D*P**-1
```

```python
lamda = symbols('lamda')
p = M.charpoly(lamda)
p
```

```python
factor(p)
```

Solving Algebraic and Differential Equations
----

```python
solve(x**2 - 1, x)
```

```python
solve((x - y + 2, x + y -3), (x, y))
```

```python
solve(x**3 - 6*x**2 + 9*x, x)
```

```python
roots(x**3 - 6*x**2 + 9*x, x)
```

```python
f, g = symbols('f g', cls=Function)
```

```python
f(x).diff(x)
```

```python
diffeq = Eq(f(x).diff(x, 2) - 2*f(x).diff(x) + f(x), sin(x))
```

```python
diffeq
```

```python
dsolve(diffeq, f(x))
```

```python
dsolve(f(x).diff(x)*(1 - sin(f(x))), f(x))
```

```python
a, t = symbols('a t')
f(t).diff(t)
diffeq = Eq(f(t).diff(t), a*t)
```

```python
diffeq
```

```python
dsolve(diffeq, f(t))
```

```python
x = symbols('x', cls=Function)
diffeq = Eq(x(t).diff(t), a*x(t))
diffeq
```

```python
dsolve(diffeq, x(t))
```

Numerics
----

```python
N(pi, 10)
```

```python
x = symbols('x')
```

```python
expr = Integral(sin(x)/(x**2), (x, 1, oo))
```

```python
expr.evalf()
```

```python
expr.evalf(maxn=20)
```

```python
expr.evalf(quad='osc')
```

```python
expr.evalf(20, quad='osc')
```

```python
expr = Integral(sin(1/x), (x, 0, 1))
expr
```

```python
expr.evalf()
```

```python
expr = expr.transform(x, 1/x)
expr
```

```python
expr.evalf(quad='osc')
```

```python
nsimplify(pi, tolerance=0.001)
```

```python
expr = sin(x)/x
```

```python
%timeit expr.evalf(subs={x: 3.14})
```

    238 µs ± 3.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

```python
f1 = lambdify(x, expr)
%timeit f1(3.14)
```

    1.36 µs ± 24.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

```python
f2 = lambdify(x, expr, 'numpy')
%timeit f2(3.14)
```

    1.36 µs ± 22.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

```python
%timeit f2(np.linspace(1, 10, 10000))
```

    475 µs ± 3.08 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

```python
%timeit [f1(x) for x in np.linspace(1, 10, 10000)]
```

    20.1 ms ± 212 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

```python
from mpmath import *
```

```python
f = odefun(lambda x, y: [-y[1], y[0]], 0, [1, 0])
for x in [0, 1, 2.5, 10]:
    nprint(f(x), 15)
    nprint([cos(x), sin(x)], 15)
```

    [1.0, 0.0]
    [1.0, 0.0]
    [0.54030230586814, 0.841470984807897]
    [0.54030230586814, 0.841470984807897]
    [-0.801143615546934, 0.598472144103957]
    [-0.801143615546934, 0.598472144103957]
    [-0.839071529076452, -0.54402111088937]
    [-0.839071529076452, -0.54402111088937]

```python
from sympy.plotting import plot
%matplotlib inline

plot(x*y**3 - y*x**3)
pass
```

```python
from sympy.plotting import plot3d_parametric_surface
from sympy import sin, cos
u, v = symbols('u v')
plot3d_parametric_surface(cos(u + v), sin(u - v), u-v, (u, -5, 5), (v, -5, 5))
pass
```

Statistics
----

```python
from sympy.stats import *
```

```python
k = Symbol("k", positive=True)
theta = Symbol("theta", positive=True)
z = Symbol("z")
X = Gamma("x", k, theta)
```

```python
D = density(X)(z)
D
```

```python
C = cdf(X, meijerg=True)(z)
C
```

```python
E(X)
```

```python
V = variance(X)
V
```

```python
simplify(V)
```

```python
N = Normal('Gaussian', 10, 5)
density(N)(z)
```

```python
density(N)(3).evalf()
```

```python
simplify(cdf(N)(z))
```

```python
P(N > 10)
```

```python
sample(N)
```

```python

```


# Занятие №3: эквивалентное сопротивление

```julia
using Base: hash, ==
using LinearAlgebra
```

```julia
"Тип данных для контакта, к которому подключаются сопротивления"
struct Pin
    value::Any
end

hash(x::Pin, h::UInt) = hash(x.value, h)
==(x::Pin, y::Pin) = x.value == y.value

"Пассивный элемент, т.е. сопротивление"
struct Passive{T <: Number}
    value::T
end

"Электрическая цепь из сопротивлений: множество контактов и отношение смежности между ними"
mutable struct Circuit{T <: Number}
    pins::Set{Pin}
    elements::Dict{Pin, Dict{Pin, Passive{T}}}
end

Circuit{T}() where T <: Number =
   Circuit(Set{Pin}(), Dict{Pin, Dict{Pin, Passive{T}}}())
```

```julia
"Добавить контакт в цепь"
function addpin!(circuit::Circuit{T}, a::Pin) where T <: Number
    if a in circuit.pins
        return false
    else
        push!(circuit.pins, a)
        circuit.elements[a] = Dict{Pin, Passive{T}}()
        return true
    end
end

"Добавить элемент в цепь и подключить к двум заданным контактам"
function addelement!(circuit::Circuit{T},
    element::Passive{T},
    a::Pin,
    b::Pin) where T <: Number

    addpin!(circuit, a)
    addpin!(circuit, b)

    circuit.elements[a][b] = element
    circuit.elements[b][a] = element
end

"Вывод уравнений для узловых потенциалов из правил Кирхгофа"
function equations(circuit::Circuit{T}) :: Tuple{Dict{Pin, Int},Matrix{T}} where T <: Number
    indices = Dict((p, i) for (i, p) in enumerate(circuit.pins))
    n = length(indices)

    equations = zeros(T, (n, n))
    for (p, pi) in pairs(indices)
        for (q, e) in circuit.elements[p]
            equations[pi, indices[q]] += 1 / e.value
            equations[pi, pi] -= 1 / e.value
        end
    end

    return (indices, equations)
end

"Найти эквивалентное сопротивление между двумя заданными узлами"
function equivalent(circuit::Circuit{T}, a::Pin, b::Pin) where T <: Number
    indices, eqs = equations(circuit)
    ia = indices[a]
    ib = indices[b]
    eqa = eqs[ia, :]

    eqs[ia, :] .= 0
    eqs[ia, ia] = 1

    eqs[ib, :] .= 0
    eqs[ib, ib] = 1

    rhs = zeros(T, length(indices))
    rhs[ib] = 1

    current = eqa ⋅ (eqs \ rhs)

    return 1 / current
end
```

    equivalent (generic function with 1 method)

# Примеры расчётов

```julia
circuit = Circuit{Float64}()
a = Pin("a")
b = Pin("b")
c = Pin("c")
addelement!(circuit, Passive(3.0), a, b)
addelement!(circuit, Passive(2.0), a, c)
addelement!(circuit, Passive(1.0), b, c)

equivalent(circuit, a, c)
```

    1.3333333333333333

```julia
circuit = Circuit{Rational{Int}}()
a = Pin("a")
b = Pin("b")
c = Pin("c")
addelement!(circuit, Passive(3//1), a, b)
addelement!(circuit, Passive(2//1), a, c)
addelement!(circuit, Passive(1//1), b, c)

equivalent(circuit, a, c)
```

    4//3

```julia
using Symbolics

@variables r_1 r_2 r_3

circuit = Circuit{Num}()
a = Pin("a")
b = Pin("b")
c = Pin("c")
addelement!(circuit, Passive(r_1), a, b)
addelement!(circuit, Passive(r_2), a, c)
addelement!(circuit, Passive(r_3), b, c)

equivalent(circuit, a, c)
```

\begin{equation}
\left( r_{2}^{-1} - \frac{1}{\left(  - \frac{1}{r_{1}} - \frac{1}{r_{3}} \right) r_{3} r_{1}} \right)^{-1}
\end{equation}


# Potência, Energia, Valor médio e RMS

Neste notebook avaliamos a potência, energia, valor médio e RMS de um sinal definidos de acordo com as seguintes equações:

\begin{equation}
P=\frac{1}{t_{2}-t_{1}}\int^{t_{2}}_{t_{1}}|x(t)|^2\mathrm{d}t
\end{equation}

\begin{equation}
E=\int^{t_{2}}_{t_{1}}|x(t)|^2\mathrm{d}t
\end{equation}

\begin{equation}
x_0=\frac{1}{t_{2}-t_{1}}\int^{t_{2}}_{t_{1}}x(t)\mathrm{d}t
\end{equation}

\begin{equation}
x_{\text{RMS}}=\sqrt{\frac{1}{t_{2}-t_{1}}\int^{t_{2}}_{t_{1}}x^2(t)\mathrm{d}t}
\end{equation}

```python
# importar as bibliotecas necessárias
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams.update({'font.size': 14})
```

```python
# tempo e frequência
Tp = 1
freq = 1
t = np.linspace(0, Tp, 1000) # vetor temporal

# sinal senoidal
A = 1
xt = A*np.sin(2*np.pi*freq*t)

# Valor médio
x_0 = np.mean(xt)

# Valor RMS
x_RMS = np.sqrt(np.mean(xt**2))

plt.figure(figsize=(12,4))
plt.title('Sinal, Valor médio e RMS')
plt.plot(t, xt, '--b', linewidth = 1, label = r'$x(t)$ [-]')
plt.plot(t, x_0*np.ones(len(t)), '--k', linewidth = 3, label = r'$x_0$ [-]')
plt.plot(t, x_RMS*np.ones(len(t)), '--r', linewidth = 3, label = r'$x_{RMS}$ [-]')
plt.legend(loc = 'upper right')
plt.grid(linestyle = '--', which='both')
plt.ylim((-1.2*A, 1.2*A))
plt.xlim((t[0], t[-1]))
plt.xlabel('Tempo [s]')
plt.ylabel('Amplitude [-]')

print('O valor médio é {:.4}'.format(x_0))
print('O valor RMS é {:.4}'.format(x_RMS))
```

```python

```

Aula 9 - Potencia e energia dos sinais/.ipynb_checkpoints/potencia e energia dos sinais-checkpoint.ipynb


# Komplexe Widerstände und Leitwerte

```python
# Bibliotheken laden
import numpy as np
import matplotlib.pyplot as plt
import cmath # für komplexe Zahlen
# Graphikparameter
plt.style.use('classic')
plt.rcParams.update({'font.size':24})
plt.rcParams.update({'figure.autolayout': True})
```

## Impedanz vs. Admittanz

Wir betrachten hier das Wechselstromverhalten von Ohmschen Widerständen, Induktivitäten und Kapazitäten.

Allgemein gesprochen spricht man bei Schaltkreisen mit diesen Bauelementen vom **komplexen Widerstand** bzw. von der **Impedanz** $\underline{Z}$. Der **komplexe Leitwert** heißt auch **Admittanz** $\underline{Y}$.

Die **Impedanz** $\underline{Z}$ setzt sich zusammen aus Real- und Imaginärteil:
- Der **Realteil** heißt **Wirkwiderstand** oder Ohmscher Widerstand oder **Resistanz** $R$.
- Der **Imaginärteil** heißt **Blindwiderstand** oder **Reaktanz** $X$.
- Der **Betrag** heißt **Scheinwiderstand** $Z$.
- Und dann gibt es noch die **Phase** $\varphi_Z$.

Man kann also schreiben:
\begin{equation}
\underline{Z} = R + jX = Z \cdot e^{j\varphi_Z}
\end{equation}

Die **Admittanz** $\underline{Y}$ setzt sich ebenfalls zusammen aus Real- und Imaginärteil:
- Der **Realteil** heißt **Wirkleitwert** oder Ohmscher Leitwert oder **Konduktanz** $G$.
- Der **Imaginärteil** heißt **Blindleitwert** oder **Suszeptanz** $B$.
- Der **Betrag** heißt **Scheinleitwert** $Y=1/Z$.
- Und dann gibt es noch die **Phase** $\varphi_Y=-\varphi_Z$.

Man kann also schreiben:
\begin{equation}
\underline{Z} = G + jB = Y \cdot e^{j\varphi_Y}
\end{equation}

*Vorsicht! Um Impedanz und Admittanz ineinander umzurechnen, reicht es **nicht**, den Kehrwert der Wirkanteile zu nehmen. Man muss den Kehrwert der **komplexen Größe** bilden:*

\begin{equation}
\underline{Z} = \frac{1}{\underline{Y}} = \frac{1}{G+jB} = \frac{1}{Y\cdot e^{j\varphi_Y}}
\end{equation}

\begin{equation}
\underline{Y} = \frac{1}{\underline{Z}} = \frac{1}{R+jX} = \frac{1}{Z\cdot e^{j\varphi_Z}}
\end{equation}

Noch ein wichtiger **physikalischer Hinweis**: Ohmsche Widerstände oder Leitwerte können **nicht negativ** werden. Wenn Sie rechnerisch also einen negativen Wert für den Realteil einer Impedanz oder Admittanz erhalten, ist irgendwo ein Rechenfehler in Ihrer Vorgehensweise.

Einige Beispiele in python:

```python
# komplexe Impedanz 1 Z = 6+ j2 (Ohm)
Z1 = complex(6,2)
Y1 = 1/Z1
print('Z1: ', Z1)
print('Y1: ', Y1) # 0,15 - j0,05 (Siemens)
```

```python
# komplexe Admittanz 2
Y2 = complex(0.2,-0.2) # Siemens
Z2 = 1/Y2
print('Z2: ', Z2) # Ohm
print('Y2: ', Y2)
```

**Aufgabe: Prüfen Sie die beiden Ergebnisse mit Ihrem Taschenrechner nach. Stellen Sie dazu den Taschenrechner in den komplexen Modus um.**

*Ergebnis:*

```python
# Phase der komplexen Impedanz Z1
phiZ1 = cmath.phase(Z1)
print('phiZ1: ', phiZ1, 'rad')
phiZ1g = 180*phiZ1/np.pi
print('phiZ1: ', phiZ1g, '°')
```

```python
# Phase der zugehörigen komplexen Admittanz Y1
phiY1 = cmath.phase(Y1)
print('phiY1: ', phiY1, 'rad')
phiY1g = 180*phiY1/np.pi
print('phiY1: ', phiY1g, '°')
```

**Aufgabe: Ermitteln Sie die Werte für die Admittanz $Y_2$ und deren Kehrwert $Z_2$. Benutzen Sie dafür Ihren Taschenrechner und nutzen Sie python, um Ihre Ergebnisse zu überprüfen.**

*Ergebnis:*

```python
# Scheinwiderstand der komplexen Impedanz Z1
Z1abs = np.abs(Z1)
print('Z1abs: ', Z1abs)
```

Betrag: $Z_1 = \sqrt{R_1^2 + X_1^2}$

```python
# Scheinleitwert der komplexen Admittanz Y1
Y1abs = np.abs(Y1)
print('Y1abs: ', Y1abs)
```

```python
# Überprüfung der Kehrwerte
print('Z1abs: ', Z1abs)
print('Kehrwert Y1abs: ', 1/Y1abs)
```

**Aufgabe: Berechnen Sie Scheinwiderstand und Scheinleitwert ebenso für $\underline{Y_2}$ und überprüfen Sie die Kehrwerte.**

*Ergebnis:*

```python
# Betrag und Phase in einem Befehl bei Z1
Z1absb, phiZ1b = cmath.polar(Z1)
print('Z1abs: ', Z1absb)
print('phiZ1: ', phiZ1b, ' rad')
print('phiZ1: ', 180*phiZ1b/np.pi, '°')
```

```python
# Betrag und Phase in einem Befehl bei Y1
Y1absb, phiY1b = cmath.polar(Y1)
print('Y1abs: ', Y1absb)
print('phiY1: ', phiY1b, ' rad')
print('phiY1: ', 180*phiY1b/np.pi, '°')
```

**Aufgabe: Finden Sie heraus, wie man im Taschenrechner gut zwischen Polar- und Normalform einer komplexen Zahl umrechnen kann und überprüfen Sie Ihr Ergebnis mit der Admittanz $Y_2$ in python.**

*Ergebnis:*

weitere Funktionen für komplexe Zahlen in python finden Sie in der Hilfe: https://docs.python.org/3.7/library/cmath.html

## Impedanz und Admittanz eines idealen Kondensators

**Strom eilt um $\pi/2$ vor:**
\begin{equation}
\varphi_Z = \varphi_u - \varphi_i = -\pi/2
\end{equation}

**Impedanz des idealen Kondensators:**
\begin{equation}
\underline{Z_C} = \frac{1}{j\omega C} = \frac{1}{j 2\pi f C} = -j\cdot\frac{1}{\omega C} = j X_C
\end{equation}

**Der Wirkwiderstand eines idealen Kondensators ist $0$. Die Reaktanz $X_C$ ist gleich dem Scheinwiderstand $-Z_C = X_C$.**

**Admittanz des idealen Kondensators:**
\begin{equation}
\underline{Y_C} = j\omega C = j 2\pi f C = j \cdot B_C
\end{equation}

**Der Wirkleitwert eines idealen Kondensators ist $0$. Die Suszeptanz $B_C$ ist gleich dem Scheinleitwert  $Y_C = B_C$.**

```python
# Beispiel für einen Kondensator mit Impedanz Z3
C = 2e-6 # 6 µF
f = 3e3 # 3 kHz
Z3 = complex(0,-1/(2*np.pi*f*C))
print('Z3:', Z3, 'Ohm')
phiZ3 = cmath.phase(Z3)
print('phiZ3:', phiZ3, 'rad')
print('phiZ3g: ', 180*phiZ3/np.pi, '°')
```

**Aufgabe: Ermitteln Sie die Admittanz Y3 aus dem Bauelementwert $C$, der Frequenz $f$ und vergleichen Sie den Kehrwert mit dem obigen Ergebnis. Üben Sie zunächst mit dem Taschenrechner und nutzen Sie python zur Überprüfung.**

*Ergebnis:*

## Impedanz und Admittanz einer idealen Induktivität

**Strom eilt um $\pi/2$ nach:**
\begin{equation}
\varphi_Z = \varphi_u - \varphi_i = \pi/2
\end{equation}

**Impedanz des idealen Kondensators:**
\begin{equation}
\underline{Z_L} = j\omega L = j2\pi f L = j X_L
\end{equation}

**Der Wirkwiderstand einer idealen Induktivität ist $0$. Die Reaktanz $X_L$ ist gleich dem Scheinwiderstand $Z_L = X_L$.**

**Admittanz der idealen Induktivität:**
\begin{equation}
\underline{Y_L} = \frac{1}{j\omega L} = \frac{1}{j 2\pi f L} = -j \frac{1}{2 \pi f L} = j\cdot B_L
\end{equation}

**Der Wirkleitwert eines idealen Kondensators ist $0$. Die Suszeptanz $B_L$ ist gleich dem Scheinleitwert multipliziert mit minus 1 $Y_L = -B_L$.**

```python
# Beispiel für eine Induktivität mit Impedanz Z4
L = 5e-6 # 5 µH
Z4 = complex(0,2*np.pi*f*L)
print('Z4:', Z4, 'Ohm')
phiZ4 = cmath.phase(Z4)
print('phiZ4:', phiZ4, 'rad')
print('phiZ4g: ', 180*phiZ4/np.pi, '°')
```

**Aufgabe: Ermitteln Sie die Admittanz Y4 aus dem Bauelementwert $L$, der Frequenz $f$ und vergleichen Sie den Kehrwert mit dem obigen Ergebnis. Üben Sie zunächst mit dem Taschenrechner und nutzen Sie python zur Überprüfung.**

*Ergebnis:*

## Serienschaltung zweier Impedanzen oder Admittanzen

```python
# Z1 und Z2 in Serie
Z12 = Z1+Z2
print('Z12:', Z12, 'Ohm')
print('Z12 polar:', cmath.polar(Z12),'Ohm')
```

```python
# Zeigerdiagramme von Z1, Z2 und Z12
# - wir machen 3 Diagramme nebeneinander
fig, ax = plt.subplots(1, 3, sharex=True, sharey=True, figsize=(8, 4))
# linkes Diagramm
ax[0].set_aspect('equal', adjustable='box') # Skalierung x wie y
ax[0].axis([-np.abs(Z12),np.abs(Z12),-np.abs(Z12),np.abs(Z12)])
ax[0].set_xlabel(r'$R_{1}$ [$\Omega$]',color='blue') # x-Achsentitel
ax[0].set_ylabel(r'$X$ [$\Omega$]') # y-Achsentitel
ax[0].annotate('', xytext = (0, 0),xy = (Z1.real, Z1.imag),
             arrowprops = {'facecolor' : 'blue',
                           'edgecolor' : 'blue',
                           'linewidth' : .001}) # Zeiger Z1
ax[0].grid() # Gitternetzlinien
# mittleres Diagramm: Z2
ax[1].set_aspect('equal', adjustable='box') # Skalierung x wie y
ax[1].set_xlabel(r'$R_{2}$ [$\Omega$]',color='green') # x-Achsentitel
ax[1].annotate('', xytext = (0, 0),xy = (Z2.real, Z2.imag),
             arrowprops = {'facecolor' : 'green',
                           'edgecolor' : 'green',
                           'linewidth' : .001}) # Zeiger Z2
ax[1].grid() # Gitternetzlinien
# rechtes Diagramm: Z1+Z2
ax[2].set_aspect('equal', adjustable='box') # Skalierung x wie y
ax[2].set_xlabel(r'$R_{12}$ [$\Omega$]',color='red') # x-Achsentitel
ax[2].annotate('', xytext = (0, 0),xy = (Z1.real, Z1.imag),
             arrowprops = {'facecolor' : 'blue',
                           'edgecolor' : 'blue',
                           'linewidth' : .001}) # Zeiger Z1
ax[2].annotate('', xytext = (Z1.real, Z1.imag),xy = (Z1.real+Z2.real, Z1.imag+Z2.imag),
             arrowprops = {'facecolor' : 'green',
                           'edgecolor' : 'green',
                           'linewidth' : .001}) # Zeiger Z2
ax[2].annotate('', xytext = (0, 0),xy = (Z12.real, Z12.imag),
             arrowprops = {'facecolor' : 'red',
                           'edgecolor' : 'red',
                           'linewidth' : .001}) # Zeiger Z12
ax[2].grid() # Gitternetzlinien
```

**Aufgabe: Ermitteln Sie ebenfalls die Impedanzen der folgenden Serienschaltungen und tragen Sie die Werte in der Tabelle (Doppelklick in Zelle -> bearbeiten *Wert* ) ein. Rechnen Sie zuerst mit dem Taschenrechner. Überprüfen Sie dann mit einem Zeigerdiagramm auf Papier und nutzen Sie python erst am Schluss, um sowohl Ihre Taschenrechner-Rechnung als auch Ihre Zeigerdiagramme zu überprüfen.**

| $Z_{1}$ in Serie mit $Z_2$ | $Z_{12}$ | *Wert* |
|----------------------------|----------|--------|
| $Z_{1}$ in Serie mit $Z_3$ | $Z_{13}$ | *Wert* |
| $Z_{1}$ in Serie mit $Z_4$ | $Z_{14}$ | *Wert* |
| $Z_{2}$ in Serie mit $Z_3$ | $Z_{23}$ | *Wert* |
| $Z_{2}$ in Serie mit $Z_4$ | $Z_{24}$ | *Wert* |
| $Z_{3}$ in Serie mit $Z_4$ | $Z_{34}$ | *Wert* |

*Kommentar:*

```python
# Admittanz
Y12 = 1/Z12
print('Y12:', Y12, 'S') # S steht für Siemens
```

```python
# Zeigerdiagramme von Y1, Y2 und Y12
# - wir machen 3 Diagramme nebeneinander
fig, ax = plt.subplots(1, 3, sharex=False, sharey=False, figsize=(8, 4))
# linkes Diagramm
ax[0].set_aspect('equal', adjustable='box') # Skalierung x wie y
ax[0].axis([-np.abs(Y1),np.abs(Y1),-np.abs(Y1),np.abs(Y1)])
ax[0].set_xlabel(r'$G_{1}$ [S]',color='blue') # x-Achsentitel
ax[0].set_ylabel(r'$B_1$ [S]', color='blue') # y-Achsentitel
ax[0].annotate('', xytext = (0, 0),xy = (Y1.real, Y1.imag),
             arrowprops = {'facecolor' : 'blue',
                           'edgecolor' : 'blue',
                           'linewidth' : .001}) # Zeiger Y1
ax[0].grid() # Gitternetzlinien
# mittleres Diagramm: Y2
ax[1].set_aspect('equal', adjustable='box') # Skalierung x wie y
ax[1].axis([-np.abs(Y2),np.abs(Y2),-np.abs(Y2),np.abs(Y2)])
ax[1].set_xlabel(r'$G_{2}$ [S]',color='green') # x-Achsentitel
ax[1].set_ylabel(r'$B_2$ [S]',color='green') # y-Achsentitel
ax[1].annotate('', xytext = (0, 0),xy = (Y2.real, Y2.imag),
             arrowprops = {'facecolor' : 'green',
                           'edgecolor' : 'green',
                           'linewidth' : .001}) # Zeiger Y2
ax[1].grid() # Gitternetzlinien
# rechtes Diagramm: Y12
ax[2].set_aspect('equal', adjustable='box') # Skalierung x wie y
ax[2].axis([-np.abs(Y12),np.abs(Y12),-np.abs(Y12),np.abs(Y12)])
ax[2].set_xlabel(r'$G_{12}$ [S]',color='red') # x-Achsentitel
ax[2].set_ylabel(r'$B_{12}$ [S]',color='red') # y-Achsentitel
ax[2].annotate('', xytext = (0, 0),xy = (Y1.real, Y1.imag),
             arrowprops = {'facecolor' : 'blue',
                           'edgecolor' : 'blue',
                           'linewidth' : .001}) # Zeiger Y1
ax[2].annotate('', xytext = (Y1.real, Y1.imag),xy = (Y1.real+Y2.real, Y1.imag+Y2.imag),
             arrowprops = {'facecolor' : 'green',
                           'edgecolor' : 'green',
                           'linewidth' : .001}) # Zeiger Y2
ax[2].annotate('', xytext = (0, 0),xy = (Y12.real, Y12.imag),
             arrowprops = {'facecolor' : 'red',
                           'edgecolor' : 'red',
                           'linewidth' : .001}) # Zeiger Y12
ax[2].grid() # Gitternetzlinien
```

**Aufgabe: Warum kann man die Gesamtadmittanz $Y_{12}$ nicht durch Addieren der Einzeladmittanzen erhalten?**

*Antwort:*

**Aufgabe: Ermitteln Sie ebenfalls die Admittanzen der folgenden Serienschaltungen und tragen Sie die Werte in der Tabelle (Doppelklick in Zelle -> bearbeiten *Wert* ) ein. Rechnen Sie zuerst mit dem Taschenrechner. Nutzen Sie python dann, um Ihre Taschenrechner-Rechnung zu überprüfen.**

| $Y_{1}$ in Serie mit $Y_2$ | $Y_{12}$ | *Wert* |
|----------------------------|----------|--------|
| $Y_{1}$ in Serie mit $Y_3$ | $Y_{13}$ | *Wert* |
| $Y_{1}$ in Serie mit $Y_4$ | $Y_{14}$ | *Wert* |
| $Y_{2}$ in Serie mit $Y_3$ | $Y_{23}$ | *Wert* |
| $Y_{2}$ in Serie mit $Y_4$ | $Y_{24}$ | *Wert* |
| $Y_{3}$ in Serie mit $Y_4$ | $Y_{34}$ | *Wert* |

*Kommentar:*

## Parallelschaltung zweier Impedanzen oder Admittanzen

```python
# Z1 und Z2 parallel
Z12p = Z1*Z2/(Z1+Z2)
print('Z12p:', Z12p, 'Ohm')
print('Z12p polar:', cmath.polar(Z12p),'Ohm')
```

```python
# Zeigerdiagramme von Z1, Z2 und Z12p
# - wir machen 3 Diagramme nebeneinander
fig, ax = plt.subplots(1, 3, sharex=False, sharey=False, figsize=(8, 4))
# linkes Diagramm
ax[0].set_aspect('equal', adjustable='box') # Skalierung x wie y
ax[0].axis([-np.abs(Z1),np.abs(Z1),-np.abs(Z1),np.abs(Z1)])
ax[0].set_xlabel(r'$R_{1}$ [$\Omega$]',color='blue') # x-Achsentitel
ax[0].set_ylabel(r'$X_1$ [$\Omega$]', color='blue') # y-Achsentitel
ax[0].annotate('', xytext = (0, 0),xy = (Z1.real, Z1.imag),
             arrowprops = {'facecolor' : 'blue',
                           'edgecolor' : 'blue',
                           'linewidth' : .001}) # Zeiger Z1
ax[0].grid() # Gitternetzlinien
# mittleres Diagramm: Z2
ax[1].set_aspect('equal', adjustable='box') # Skalierung x wie y
ax[1].axis([-np.abs(Z2),np.abs(Z2),-np.abs(Z2),np.abs(Z2)])
ax[1].set_xlabel(r'$R_{2}$ [$\Omega$]',color='green') # x-Achsentitel
ax[1].set_ylabel(r'$X_2$ [$\Omega$]',color='green') # y-Achsentitel
ax[1].annotate('', xytext = (0, 0),xy = (Z2.real, Z2.imag),
             arrowprops = {'facecolor' : 'green',
                           'edgecolor' : 'green',
                           'linewidth' : .001}) # Zeiger Z2
ax[1].grid() # Gitternetzlinien
# rechtes Diagramm: Z12p
ax[2].set_aspect('equal', adjustable='box') # Skalierung x wie y
ax[2].axis([-np.abs(Z12p),np.abs(Z12p),-np.abs(Z12p),np.abs(Z12p)])
ax[2].set_xlabel(r'$R_{12}$ [$\Omega$]',color='red') # x-Achsentitel
ax[2].set_ylabel(r'$X_{12}$ [$\Omega$]',color='red') # y-Achsentitel
ax[2].annotate('', xytext = (0, 0),xy = (Z1.real, Z1.imag),
             arrowprops = {'facecolor' : 'blue',
                           'edgecolor' : 'blue',
                           'linewidth' : .001}) # Zeiger Z1
ax[2].annotate('', xytext = (Z1.real, Z1.imag),xy = (Z1.real+Z2.real, Z1.imag+Z2.imag),
             arrowprops = {'facecolor' : 'green',
                           'edgecolor' : 'green',
                           'linewidth' : .001}) # Zeiger Z2
ax[2].annotate('', xytext = (0, 0),xy = (Z12p.real, Z12p.imag),
             arrowprops = {'facecolor' : 'red',
                           'edgecolor' : 'red',
                           'linewidth' : .001}) # Zeiger Z12p
ax[2].grid() # Gitternetzlinien
```

**Aufgabe: Warum kann man die Gesamtimpedanz $Z_{12,p}$ nicht durch Addieren der Einzelimpedanzen erhalten?**

*Antwort:*

**Aufgabe: Ermitteln Sie ebenfalls die Impedanzen der folgenden Parallelschaltungen und tragen Sie die Werte in der Tabelle (Doppelklick in Zelle -> bearbeiten *Wert* ) ein. Rechnen Sie zuerst mit dem Taschenrechner. Nutzen Sie python erst am Schluss, um Ihre Taschenrechner-Rechnung zu überprüfen.**

| $Z_{1}$ parallel zu $Z_2$ | $Z_{12,p}$ | *Wert* |
|----------------------------|----------|--------|
| $Z_{1}$ parallel zu $Z_3$ | $Z_{13,p}$ | *Wert* |
| $Z_{1}$ parallel zu $Z_4$ | $Z_{14,p}$ | *Wert* |
| $Z_{2}$ parallel zu $Z_3$ | $Z_{23,p}$ | *Wert* |
| $Z_{2}$ parallel zu $Z_4$ | $Z_{24,p}$ | *Wert* |
| $Z_{3}$ parallel zu $Z_4$ | $Z_{34,p}$ | *Wert* |

*Kommentar:*

```python
# Admittanz
Y12p = Y1 + Y2
print('Y12p:', Y12p, 'S') # S steht für Siemens
```

```python
# Zeigerdiagramme von Y1, Y2 und Y12p
# - wir machen 3 Diagramme nebeneinander
fig, ax = plt.subplots(1, 3, sharex=True, sharey=True, figsize=(8, 4))
# linkes Diagramm
ax[0].set_aspect('equal', adjustable='box') # Skalierung x wie y
ax[0].axis([-np.abs(Y12p),np.abs(Y12p),-np.abs(Y12p),np.abs(Y12p)])
ax[0].set_xlabel(r'$G_{1}$ [S]',color='blue') # x-Achsentitel
ax[0].set_ylabel(r'$B$ [S]') # y-Achsentitel
ax[0].annotate('', xytext = (0, 0),xy = (Y1.real, Y1.imag),
             arrowprops = {'facecolor' : 'blue',
                           'edgecolor' : 'blue',
                           'linewidth' : .001}) # Zeiger Y1
ax[0].grid() # Gitternetzlinien
# mittleres Diagramm: Y2
ax[1].set_aspect('equal', adjustable='box') # Skalierung x wie y
ax[1].set_xlabel(r'$G_{2}$ [S]',color='green') # x-Achsentitel
ax[1].annotate('', xytext = (0, 0),xy = (Y2.real, Y2.imag),
             arrowprops = {'facecolor' : 'green',
                           'edgecolor' : 'green',
                           'linewidth' : .001}) # Zeiger Y2
ax[1].grid() # Gitternetzlinien
# rechtes Diagramm: Y1+Y2
ax[2].set_aspect('equal', adjustable='box') # Skalierung x wie y
ax[2].set_xlabel(r'$G_{12}$ [S]',color='red') # x-Achsentitel
ax[2].annotate('', xytext = (0, 0),xy = (Y1.real, Y1.imag),
             arrowprops = {'facecolor' : 'blue',
                           'edgecolor' : 'blue',
                           'linewidth' : .001}) # Zeiger Y1
ax[2].annotate('', xytext = (Y1.real, Y1.imag),xy = (Y1.real+Y2.real, Y1.imag+Y2.imag),
             arrowprops = {'facecolor' : 'green',
                           'edgecolor' : 'green',
                           'linewidth' : .001}) # Zeiger Y2
ax[2].annotate('', xytext = (0, 0),xy = (Y12p.real, Y12p.imag),
             arrowprops = {'facecolor' : 'red',
                           'edgecolor' : 'red',
                           'linewidth' : .001}) # Zeiger Y12
ax[2].grid() # Gitternetzlinien
```

**Aufgabe: Ermitteln Sie ebenfalls die Admittanzen der möglichen Parallelschaltungen und tragen Sie die Werte in einer Tabelle ein. Dazu bitte: eine der oberen Zellen kopieren, einfügen und dann editieren. Rechnen Sie zuerst mit dem Taschenrechner. Überprüfen Sie dann mit einem Zeigerdiagramm auf Papier und nutzen Sie python erst am Schluss, um sowohl Ihre Taschenrechner-Rechnung als auch Ihre Zeigerdiagramme zu überprüfen.**

*Platz für Tabelle:*

*Kommentar:*

## Ohmsches Gesetz an Impedanzen oder Admittanzen

```python
# Strom
I5 = complex(1,1)
# Spannung
U5 = complex(230,0)
# Impedanz
Z5 = U5/I5
print('Z5:', Z5, 'Ohm')
# Admittanz
Y5 = I5/U5
print('Y5:', Y5, 'S')
```

**Aufgabe: Überprüfen Sie das Ergebnis mit Ihrem Taschenrechner. Verändern Sie dann die Werte der Spannung so, dass die Admittanz rein reell wird (Imaginärteil 0). Probieren Sie danach aus, die Spannung so anzupassen, dass die Impedanz rein imaginär wird (Realteil 0).**

Im Zeitbereich können wir diese beiden auch darstellen. Dafür berechnen wir die Amplitude mit $\hat{a}=A\cdot \sqrt{2}$ und die Phase $\varphi_a$ von Strom und Spannung.

```python
# Strom
i5dach = np.abs(I5)*np.sqrt(2) # Amplitude
print('i5dach =',i5dach,' A')
phii5 = cmath.phase(I5) # Phase
print('phii5 = ', phii5, ' rad')
print('phii5 = ', 180*phii5/np.pi, '°')
```

```python
# Spannung
u5dach = np.abs(U5)*np.sqrt(2) # Amplitude
print('u5dach =',u5dach,' V')
phiu5 = cmath.phase(U5) # Phase
print('phiu5 = ', phiu5, ' rad')
print('phiu5 = ', 180*phiu5/np.pi, '°')
```

```python
# Phasenverschiebung phi = phiu - phii
phi5 = phiu5 - phii5
print('phi5 = ', phi5, ' rad')
print('phi5 = ', 180*phi5/np.pi, '°')
```

```python
# Frequenz
f = 1000 # 1 kHz
omega = 2*np.pi*f
# Zeitvektor
t = np.linspace(0,3/f,1000)
# Zeitsignale
u5 = u5dach*np.cos(omega*t + phiu5)
i5 = i5dach*np.cos(omega*t + phii5)
```

```python
# Diagramm
plt.plot(1000*t,u5,label='$u_5(t)$ [V]')
plt.plot(1000*t,1000*i5,label='$i_5(t)$ [mA]')
plt.xlabel('Zeit $t$ [ms]')
plt.grid()
plt.legend()
```

**Aufgabe: Erzeugen Sie eine eigene Darstellung mit den Zahlen aus Aufgabe 2015-09-A.7 im ilias-Test (Teil-A-Tests). Geben Sie dabei hier Ihre eigenen Werte ein und stellen Sie Strom und Spannung im Diagramm dar.**

```python
# Angaben aus dem Test alle mit A7 markiert (Aufgabe 7)
# Frequenz
fA7 = 1780 # 2,02 kHz
TA7 = 1/fA7 # Periode
# Zeitvektor
tA7 = np.linspace(0,3*TA7,1000)
# Betrag der Phase, d.h. phi = phiu - phii oder phi = phii-phiu
deltatA7 = 66.32e-6 # 65,99 us
phiA7 = 2*np.pi*deltatA7/TA7 # für deltatA7 = TA7 ist 2 pi der Winkel
print('phi =',phiA7,' rad')
print('phi =',180*phiA7/np.pi, '°')
# Stromsignal ist früher -> phii = 0 (Annahme)
phiiA7 = 0
# Spannungssignal ist später -> phiu = phii + phi -> phi = phiu-phii
phiuA7 = phiiA7 + phiA7
# Amplitude auf eins setzen (nicht relevant)
uA7 = np.cos(2*np.pi*fA7*tA7 + phiuA7)
iA7 = np.cos(2*np.pi*fA7*tA7 + phiiA7)

# Diagramm
plt.plot(1000*tA7,uA7,label='$u_{A7} (t)$ [V]')
plt.plot(1000*tA7,iA7,label='$i_{A7} (t)$ [A]')
plt.grid()
plt.legend()
plt.xlabel('Zeit $t$ [ms]')
```


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

```python
# Helper function to draw regression line
def draw_result(x, y, xfit, yfit, cost=None, x_predict=None):
    plt.figure(figsize=(10,5))
    ax = plt.gca()
    ax.grid(color='#b7b7b7', linestyle='-', linewidth=0.5, alpha=0.5)
    plt.scatter(x,y, color='#333333', alpha=0.7)
    plt.plot(xfit,yfit, color='#333333')
    if x_predict:
        ax.axvline(x_predict, color='#121212', linestyle='--', linewidth=1, alpha=0.9)
        plt.scatter(x_predict,yfit[x_predict*100], s=100, c='#212121', alpha=0.7)
    if cost:
        ax.text(0, 10, f'error = {cost:.2f}',fontsize=12,color='#000000')
    plt.show()
```

```python
# generate random data
sample_size = 50
rng = np.random.RandomState(1)
# generate input and output data with shape = (SAMPLE, FEATURE)
x = np.array([10 * rng.rand(sample_size)]).T
y = 2 * x - 5 + np.array([rng.rand(sample_size)]).T
```

To predit the value of the incoming points, the simple solution is to approximate $y$ as a continuous linear function of $x$:
\begin{equation}
\hat{y} = f(x, \mathbf{w}) = \omega_0 + \omega_1x
\end{equation}

Objective: find $\mathbf{w}$ which minimize the error.
\begin{equation}
J(\mathbf{w}) = \frac{1}{2n}\sum_{i=1}^{N}(f(x_i, \mathbf{w}) - y_i)^2
\end{equation}

Let's start with random values of $\omega_0$ and $\omega_1$.

```python
# initilize weights with shape = (INPUT NODES, OUTPUT NODES)
weights = np.array([10 * rng.rand(2)]).T

xfit = np.linspace(0, 10, 1000)
yfit = weights[0] + weights[1] * xfit

draw_result(x, y, xfit, yfit)
```

## Linear regression

Optimize fit line with linear regression.

### 1. Feedforward
\begin{equation}
\mathbf{\hat{y}} = XW
\end{equation}

### 2. Compute cost function
\begin{equation}
J(\mathbf{y}, \mathbf{\hat{y}}) = \frac{1}{2m}(\mathbf{\hat{y}} - \mathbf{y})^T(\mathbf{\hat{y}} - \mathbf{y})
\end{equation}

### 3. Backpropagation
\begin{equation}
\frac{\partial J(\mathbf{y}, \mathbf{\hat{y}})}{\partial W}
= \frac{\partial J(\mathbf{y}, \mathbf{\hat{y}})}{\partial \mathbf{\hat{y}}} \cdot \frac{\partial \mathbf{\hat{y}}}{\partial W}
= \frac{1}{m}X^T(\mathbf{\hat{y}} - \mathbf{y})
\end{equation}

### 4. Gradient descent
\begin{equation}
W = W - \alpha  \frac{\partial J(\mathbf{y}, \mathbf{\hat{y}})}{\partial W}
\end{equation}

```python
class LinearRegression:
    """ Simple linear regression """
    def __init__(self):
        pass

    def _init_params(self, x, y, iterations, learning_rate, reg_factor):
        """ Initilize parameters.

        ----------
        W : ndarray, shape (n_features+1,)
            Coefficient vector
        """
        self._X = np.hstack([np.ones((x.shape[0], 1)), x])
        self._y = y
        self._learning_rate = learning_rate
        self._reg_factor = reg_factor
        self.weights_ = np.random.rand(self._X.shape[1],1)
        self.costs_ = np.zeros(iterations)

    def _feedforward(self):
        """ Computes np.dot(X, W). """
        self._y_hat = self._X.dot(self.weights_)

    def _backprop(self):
        """ Update weights. """
        m = len(self._y)
        # update weights with L2 regularization term
        _weights = self.weights_.copy()
        # ignore bias term
        _weights[0, 0] = 0
        self.weights_ -= self._learning_rate * self._X.T.dot(self._y_hat - self._y)/m + self._reg_factor/m*_weights

    def _get_cost(self):
        """ Compute loss. """
        m = len(self._y)
        errors = self._y-self._y_hat
        # cost function with L2 regularization term
        _weights = self.weights_.copy()
        # ignore bias term
        _weights[0, 0] = 0
        return 0.5/m * errors.T.dot(errors) + self._reg_factor/(2*m)*_weights.T.dot(_weights)

    def fit(self, x, y, iterations=1000, learning_rate=0.02, reg_factor=0.5):
        """ Fit model.

        ----------
        x : ndarray, shape (n_samples, n_features)
            Training data
        y : ndarray, shape (n_samples,)
            Target data
        """
        self._init_params(x, y, iterations, learning_rate, reg_factor)

        # train model
        for i in range(iterations):
            self._feedforward()
            self._backprop()
            self.costs_[i] = self._get_cost()
```

```python
# Linear function
lr = LinearRegression()
lr.fit(x, y)

Xfit = np.vstack((np.ones((xfit.shape[0], )), xfit)).T
yfit = Xfit.dot(lr.weights_)

draw_result(x, y, xfit, yfit, cost=lr.costs_[-1], x_predict=6)
```

```python
# Polynomial function
x_ = np.hstack([x, x**2])
y_ = 2 * x + 0.6 * x**2 - 0.08 * x**3 - 5 + np.array([rng.rand(sample_size)]).T

lr_ = LinearRegression()
lr_.fit(x_, y_, iterations=50000, learning_rate=0.001, reg_factor=0.005)

Xfit_ = np.vstack([np.ones((xfit.shape[0], )), xfit, xfit**2]).T
yfit_ = Xfit_.dot(lr_.weights_)

draw_result(x, y_, xfit, yfit_, cost=lr_.costs_[-1], x_predict=6)
```


# Conic Programming

**Originally Contributed by**: Arpit Bhatia

This tutorial is aimed at providing a simplistic introduction to conic programming using JuMP.

## What is a Cone?
A subset $C$ of a vector space $V$ is a cone if $\forall x \in C$ and positive scalars $\alpha$,
the product $\alpha x \in C$. A cone C is a convex cone if $\alpha x + \beta y \in C$,
for any positive scalars $\alpha, \beta$, and any $x, y \in C$.

## Conic Programming
Conic programming problems are convex optimization problems in which a convex function is minimized
over the intersection of an affine subspace and a convex cone.
An example of a conic-form minimization problems, in the primal form is:

$$
\begin{align}
& \min_{x \in \mathbb{R}^n} & a_0^T x + b_0 \\
& \;\;\text{s.t.} & A_i x + b_i & \in \mathcal{C}_i & i = 1 \ldots m
\end{align}
$$

The corresponding dual problem is:

$$
\begin{align}
& \max_{y_1, \ldots, y_m} & -\sum_{i=1}^m b_i^T y_i + b_0 \\
& \;\;\text{s.t.} & a_0 - \sum_{i=1}^m A_i^T y_i & = 0 \\
& & y_i & \in \mathcal{C}_i^* & i = 1 \ldots m
\end{align}
$$

where each $\mathcal{C}_i$ is a closed convex cone and $\mathcal{C}_i^*$ is its dual cone.

## Some of the Types of Cones Supported by JuMP

```julia
using JuMP
using ECOS
using LinearAlgebra
using Random

Random.seed!(1234);
```

    ┌ Info: Precompiling JuMP [4076af6c-e467-56ae-b986-b466b2749572]
    └ @ Base loading.jl:1260
    ┌ Info: Precompiling ECOS [e2685f51-7e38-5353-a97d-a921fd2c8199]
    └ @ Base loading.jl:1260

By this point we have used quite a few different solvers.
To find out all the different solvers and their supported problem types, check out the
[solver table](http://www.juliaopt.org/JuMP.jl/v0.19.0/installation/#Getting-Solvers-1) in the docs.

### Second-Order Cone
The Second-Order Cone (or Lorentz Cone) of dimension $n$ is of the form:

$$
Q^n = \{ (t,x) \in \mathbb{R}^\mbox{n} : t \ge ||x||_2 \}
$$

A Second-Order Cone rotated by $\pi/4$ in the $(x_1,x_2)$ plane is called a Rotated Second-Order Cone.
It is of the form:

$$
Q_r^n = \{ (t,u,x) \in \mathbb{R}^\mbox{n} : 2tu \ge ||x||_2^2, t,u \ge 0 \}
$$

These cones are represented in JuMP using the MOI sets `SecondOrderCone` and `RotatedSecondOrderCone`.

#### Example: Euclidean Projection on a hyperplane
For a given point $u_{0}$ and a set $K$, we refer to any point $u \in K$
which is closest to $u_{0}$ as a projection of $u_{0}$ on $K$.
The projection of a point $u_{0}$ on a hyperplane $K = \{u | p' \cdot u = q\}$ is given by

$$
\begin{align*}
& \min & ||u - u_{0}|| \\
& \;\;\text{s.t.} & p' \cdot u = q  \\
\end{align*}
$$

```julia
u0 = rand(10)
p = rand(10)
q = rand();
```

We can model the above problem as the following conic program:

$$
\begin{align*}
& \min & t \\
& \;\;\text{s.t.} & p' \cdot u = q \\
& & (t, u - u_{0}) \in Q^{n+1}
\end{align*}
$$

On comparing this with the primal form of a conic problem we saw above,

$$
\begin{align*}
& x = (t , u) &\\
& a_0 = e_1 &\\
& b_0 = 0 &\\
& A_1 = (0, p) &\\
& b_1 = -q &\\
& C_1 = \mathbb{R}_- &\\
& A_2 = 1 &\\
& b_2 = -(0, u_0) &\\
& C_2 = Q^{n+1} &
\end{align*}
$$

Thus, we can obtain the dual problem as:

$$
\begin{align*}
& \max & y_1 + (0, u_0)^T y_2 \\
& \;\;\text{s.t.} & e_1 - (0,p)^T y_1 - y_2 = 0 \\
& & y_1 \in \mathbb{R}_- \\
& & y_2 \in Q^{n+1}
\end{align*}
$$

```julia
model = Model(optimizer_with_attributes(ECOS.Optimizer, "printlevel" => 0))
@variable(model, u[1:10])
@variable(model, t)
@objective(model, Min, t)
@constraint(model, [t, (u - u0)...] in SecondOrderCone())
@constraint(model, u' * p == q)
optimize!(model)
```

    ECOS 2.0.5 - (C) embotech GmbH, Zurich Switzerland, 2012-15. Web: www.embotech.com/ECOS

    It     pcost       dcost      gap   pres   dres    k/t    mu     step   sigma     IR    |   BT
     0  +0.000e+00  -0.000e+00  +1e+01  4e-01  2e-04  1e+00  6e+00    ---    ---    1  1  - |  -  -
     1  +9.352e-02  +9.474e-02  +7e-01  4e-02  2e-05  7e-02  4e-01  0.9308  3e-04   2  2  2 |  0  0
     2  +7.130e-01  +7.349e-01  +7e-02  5e-03  2e-06  3e-02  5e-02  0.9427  7e-02   2  2  2 |  0  0
     3  +7.410e-01  +7.413e-01  +8e-04  6e-05  2e-08  3e-04  6e-04  0.9890  1e-04   2  2  2 |  0  0
     4  +7.413e-01  +7.413e-01  +8e-06  6e-07  2e-10  4e-06  6e-06  0.9890  1e-04   1  1  1 |  0  0
     5  +7.413e-01  +7.413e-01  +9e-08  7e-09  2e-12  4e-08  7e-08  0.9890  1e-04   1  1  1 |  0  0
     6  +7.413e-01  +7.413e-01  +1e-09  8e-11  2e-14  5e-10  8e-10  0.9890  1e-04   1  1  1 |  0  0

    OPTIMAL (within feastol=7.8e-11, reltol=1.4e-09, abstol=1.0e-09).
    Runtime: 0.000191 seconds.

```julia
@show objective_value(model);
@show value.(u);
```

    objective_value(model) = 0.7413476010760025
    value.(u) = [0.3124638243936739, 0.7621182340845496, 0.5377408631527775, 0.049622730071534914, 0.516584856932142, 0.8058883387736702, 0.08216859863776912, 0.019039831335664947, 0.22253665470728054, 0.21813451035590134]

```julia
e1 = [1.0, zeros(10)...]
dual_model = Model(optimizer_with_attributes(ECOS.Optimizer, "printlevel" => 0))
@variable(dual_model, y1 <= 0.0)
@variable(dual_model, y2[1:11])
@objective(dual_model, Max, q * y1 + dot(vcat(0.0, u0), y2))
@constraint(dual_model, e1 - [0.0, p...] .* y1 - y2 .== 0.0)
@constraint(dual_model, y2 in SecondOrderCone())
optimize!(dual_model)
```

    ECOS 2.0.5 - (C) embotech GmbH, Zurich Switzerland, 2012-15. Web: www.embotech.com/ECOS

    It     pcost       dcost      gap   pres   dres    k/t    mu     step   sigma     IR    |   BT
     0  +0.000e+00  -0.000e+00  +3e+00  4e-01  4e-01  1e+00  1e+00    ---    ---    1  1  - |  -  -
     1  -4.087e+00  -1.050e+00  +4e-01  2e-01  3e-01  4e+00  2e-01  0.8614  3e-03   1  1  1 |  0  0
     2  -1.100e+00  -1.173e+00  +1e-01  7e-02  3e-02  3e-02  5e-02  0.8459  1e-01   2  2  2 |  0  0
     3  -7.558e-01  -7.447e-01  +5e-03  3e-03  1e-03  1e-02  3e-03  0.9794  4e-02   2  2  2 |  0  0
     4  -7.415e-01  -7.414e-01  +5e-05  3e-05  1e-05  2e-04  3e-05  0.9890  1e-04   2  1  1 |  0  0
     5  -7.413e-01  -7.413e-01  +6e-07  3e-07  1e-07  2e-06  4e-07  0.9890  1e-04   2  1  1 |  0  0
     6  -7.413e-01  -7.413e-01  +7e-09  3e-09  2e-09  2e-08  4e-09  0.9890  1e-04   3  1  1 |  0  0

    OPTIMAL (within feastol=3.4e-09, reltol=8.9e-09, abstol=6.6e-09).
    Runtime: 0.000134 seconds.

```julia
@show objective_value(dual_model);
```

    objective_value(dual_model) = 0.7413476212168523

We can also have an equivalent formulation using a Rotated Second-Order Cone:

$$
\begin{align*}
& \min & t \\
& \;\;\text{s.t.} & p' \cdot u = q \\
& & (t, 1/2, u - u_{0})\in Q_r^{n+2}
\end{align*}
$$

```julia
model = Model(optimizer_with_attributes(ECOS.Optimizer, "printlevel" => 0))
@variable(model, u[1:10])
@variable(model, t)
@objective(model, Min, t)
@constraint(model, [t, 0.5, (u - u0)...] in RotatedSecondOrderCone())
@constraint(model, u' * p == q)
optimize!(model)
```

    ECOS 2.0.5 - (C) embotech GmbH, Zurich Switzerland, 2012-15. Web: www.embotech.com/ECOS

    It     pcost       dcost      gap   pres   dres    k/t    mu     step   sigma     IR    |   BT
     0  +0.000e+00  -3.294e-02  +9e+00  4e-01  2e-02  1e+00  5e+00    ---    ---    1  1  - |  -  -
     1  +4.307e-03  +4.596e-03  +5e-01  3e-02  1e-03  5e-02  3e-01  0.9467  2e-04   1  1  1 |  0  0
     2  +4.232e-01  +5.299e-01  +1e-01  2e-02  5e-04  1e-01  9e-02  0.9251  3e-01   2  2  2 |  0  0
     3  +5.336e-01  +5.401e-01  +4e-03  8e-04  2e-05  8e-03  4e-03  0.9590  1e-04   2  1  2 |  0  0
     4  +5.469e-01  +5.486e-01  +4e-04  1e-04  3e-06  2e-03  5e-04  0.9890  1e-01   2  2  2 |  0  0
     5  +5.495e-01  +5.496e-01  +7e-06  3e-06  6e-08  4e-05  1e-05  0.9811  6e-04   2  2  2 |  0  0
     6  +5.496e-01  +5.496e-01  +8e-08  3e-08  7e-10  4e-07  1e-07  0.9890  1e-04   2  1  1 |  0  0
     7  +5.496e-01  +5.496e-01  +9e-10  3e-10  7e-12  5e-09  1e-09  0.9890  1e-04   2  1  1 |  0  0

    OPTIMAL (within feastol=3.2e-10, reltol=1.6e-09, abstol=8.7e-10).
    Runtime: 0.000134 seconds.

```julia
@show value.(u);
```

    value.(u) = [0.3124638244248018, 0.7621182341177366, 0.5377408631710233, 0.049622730012400196, 0.5165848570203785, 0.8058883387998751, 0.08216859862006304, 0.019039831283705344, 0.22253665471226453, 0.2181345103716297]

The difference here is that the objective in the case of the Second-Order Cone is $||u - u_{0}||_2$,
while in the case of a Rotated Second-Order Cone is $||u - u_{0}||_2^2$.
However, the value of x is the same for both.

### Exponential Cone

An Exponential Cone is a set of the form:

$$
K_{exp} = \{ (x,y,z) \in \mathbb{R}^3 : y \exp (x/y) \le z, y > 0 \}
$$

It is represented in JuMP using the MOI set `ExponentialCone`.

#### Example: Entropy Maximization
As the name suggests, the entropy maximization problem consists of maximizing the entropy function,
$H(x) = -x\log{x}$ subject to linear inequality constraints.

$$
\begin{align*}
& \max & - \sum_{i=1}^n x_i \log x_i \\
& \;\;\text{s.t.} & \mathbf{1}' x = 1 \\
& & Ax \leq b
\end{align*}
$$

We can model this problem using an exponential cone by using the following transformation:

$$
t\leq -x\log{x} \iff t\leq x\log(1/x)  \iff (t, x, 1) \in K_{exp}
$$

Thus, our problem becomes,

$$
\begin{align*}
& \max & 1^Tt \\
& \;\;\text{s.t.} & Ax \leq b \\
& & 1^T x = 1 \\
& & (t_i, x_i, 1) \in K_{exp} && \forall i = 1 \ldots n \\
\end{align*}
$$

```julia
n = 15;
m = 10;
A = randn(m, n);
b = rand(m, 1);

model = Model(optimizer_with_attributes(ECOS.Optimizer, "printlevel" => 0))
@variable(model, t[1:n])
@variable(model, x[1:n])
@objective(model, Max, sum(t))
@constraint(model, sum(x) == 1.0)
@constraint(model, A * x .<= b )
# Cannot use the exponential cone directly in JuMP, hence we use MOI to specify the set.
@constraint(model, con[i = 1:n], [t[i], x[i], 1.0] in MOI.ExponentialCone())

optimize!(model);
```

    ECOS 2.0.5 - (C) embotech GmbH, Zurich Switzerland, 2012-15. Web: www.embotech.com/ECOS

    It     pcost       dcost      gap   pres   dres    k/t    mu     step   sigma     IR    |   BT
     0  +0.000e+00  -1.397e+01  +6e+01  1e+00  5e-01  1e+00  1e+00    ---    ---    0  0  - |  -  -
     1  -8.340e+00  -1.331e+01  +2e+01  3e-01  2e-01  4e-01  3e-01  0.7833  8e-02   1  1  1 |  2  1
     2  -4.641e+00  -6.976e+00  +5e+00  2e-01  9e-02  2e-01  1e-01  0.9791  4e-01   1  1  1 |  6  0
     3  -3.703e+00  -4.235e+00  +1e+00  4e-02  2e-02  5e-02  2e-02  0.7833  9e-03   1  1  1 |  1  1
     4  -3.051e+00  -3.294e+00  +4e-01  2e-02  9e-03  3e-02  8e-03  0.6266  5e-02   1  1  1 |  2  2
     5  -2.789e+00  -2.868e+00  +1e-01  8e-03  3e-03  8e-03  2e-03  0.7833  1e-01   1  1  1 |  3  1
     6  -2.741e+00  -2.775e+00  +5e-02  3e-03  1e-03  3e-03  1e-03  0.9791  4e-01   1  1  1 |  6  0
     7  -2.709e+00  -2.717e+00  +1e-02  8e-04  3e-04  8e-04  2e-04  0.7833  9e-03   1  1  1 |  1  1
     8  -2.704e+00  -2.706e+00  +3e-03  2e-04  7e-05  2e-04  5e-05  0.7833  1e-02   2  1  1 |  1  1
     9  -2.703e+00  -2.703e+00  +6e-04  5e-05  2e-05  5e-05  1e-05  0.7833  5e-02   1  1  1 |  2  1
    10  -2.702e+00  -2.703e+00  +3e-04  2e-05  7e-06  2e-05  5e-06  0.6266  5e-02   2  1  1 |  2  2
    11  -2.702e+00  -2.702e+00  +6e-05  4e-06  2e-06  4e-06  1e-06  0.7833  1e-02   1  0  0 |  1  1
    12  -2.702e+00  -2.702e+00  +2e-05  1e-06  4e-07  1e-06  3e-07  0.7833  5e-02   2  0  0 |  2  1
    13  -2.702e+00  -2.702e+00  +6e-06  4e-07  2e-07  4e-07  1e-07  0.6266  5e-02   2  0  0 |  2  2
    14  -2.702e+00  -2.702e+00  +1e-06  1e-07  4e-08  1e-07  3e-08  0.7833  1e-02   1  0  0 |  1  1
    15  -2.702e+00  -2.702e+00  +3e-07  2e-08  1e-08  2e-08  6e-09  0.7833  5e-02   1  0  0 |  2  1
    16  -2.702e+00  -2.702e+00  +1e-07  9e-09  4e-09  1e-08  2e-09  0.6266  5e-02   1  0  1 |  2  2
    17  -2.702e+00  -2.702e+00  +3e-08  2e-09  9e-10  2e-09  6e-10  0.7833  9e-03   0  0  0 |  1  1
    18  -2.702e+00  -2.702e+00  +7e-09  5e-10  2e-10  5e-10  1e-10  0.7833  9e-03   0  0  0 |  1  1

    OPTIMAL (within feastol=5.1e-10, reltol=2.7e-09, abstol=7.4e-09).
    Runtime: 0.000841 seconds.

```julia
@show objective_value(model);
```

    objective_value(model) = 2.7021992720612427

### Positive Semidefinite Cone
The set of Positive Semidefinite Matrices of dimension $n$ form a cone in $\mathbb{R}^n$.
We write this set mathematically as

$$
\mathcal{S}_{+}^n = \{ X \in \mathcal{S}^n \mid z^T X z \geq 0, \: \forall z\in \mathbb{R}^n \}.
$$

A PSD cone is represented in JuMP using the MOI sets
`PositiveSemidefiniteConeTriangle` (for upper triangle of a PSD matrix) and
`PositiveSemidefiniteConeSquare` (for a complete PSD matrix).
However, it is preferable to use the `PSDCone` shortcut as illustrated below.

#### Example: Largest Eigenvalue of a Symmetric Matrix
Suppose $A$ has eigenvalues $\lambda_{1} \geq \lambda_{2} \ldots \geq \lambda_{n}$.
Then the matrix $t I-A$ has eigenvalues $t-\lambda_{1}, t-\lambda_{2}, \ldots, t-\lambda_{n}$.
Note that $t I-A$ is PSD exactly when all these eigenvalues are non-negative,
and this happens for values $t \geq \lambda_{1} .$
Thus, we can model the problem of finding the largest eigenvalue of a symmetric matrix as:

$$
\begin{align*}
\lambda_{1} = \max t \\
\text { s.t. } t I-A \succeq 0
\end{align*}
$$

```julia
using LinearAlgebra
using SCS

A = [3 2 4;
     2 0 2;
     4 2 3]

model = Model(optimizer_with_attributes(SCS.Optimizer, "verbose" => 0))
@variable(model, t)
@objective(model, Min, t)
@constraint(model, t .* Matrix{Float64}(I, 3, 3) - A in PSDCone())

optimize!(model)
```

    ┌ Info: Precompiling SCS [c946c3f1-0d1f-5ce8-9dea-7daa1f7e2d13]
    └ @ Base loading.jl:1260

```julia
@show objective_value(model);
```

    objective_value(model) = 8.000000000000014

## Other Cones and Functions
For other cones supported by JuMP, check out the
[MathOptInterface Manual](http://www.juliaopt.org/MathOptInterface.jl/dev/apimanual/#Standard-form-problem-1).
A good resource for learning more about functions which can be modelled using cones is the
MOSEK Modeling Cookbook[[1]](#c1).

### References

1. MOSEK Modeling Cookbook — MOSEK Modeling Cookbook 3.1. Available at: https://docs.mosek.com/modeling-cookbook/index.html.

```julia

```


```python
import numpy as np
import pandas as pd
```

# MEAN

```python
array= np.array([100,150,200,250,360,490,500,600,671])
array.mean()
```

    369.0

# MEDIAN

```python
array= np.array([25,18,27,10,8,30,42,20,53])
np.median(array)
```

    25.0

# MODE

```python
array= np.array([1,1,2,3,4,5])
data= pd.Series(array)
data.mode()
```

    0    1
    dtype: int32

# STANDARD DEVIATION

```python
array= np.array([1,1,2,3,4,5])
data= pd.Series(array)
data.std()
```

    1.632993161855452

# QUARTILES

```python
array= np.array([25,18,30, 8, 15, 5, 10, 35, 40, 45])
data= pd.Series(array)
data.describe()
```

    count    10.000000
    mean     23.100000
    std      14.051097
    min       5.000000
    25%      11.250000
    50%      21.500000
    75%      33.750000
    max      45.000000
    dtype: float64

# Quartile deviation

```python
qd= (data.describe()[6]-data.describe()[4])/2
print("(q3-q1)/2= ", qd)
```

    (q3-q1)/2=  11.25

# Coefficient of quartile deviation

```python
coff= (data.describe()[6]-data.describe()[4])/(data.describe()[6]+data.describe()[4])
print("(q3-q1)/(q3+q1)= ", coff)
```

    (q3-q1)/(q3+q1)=  0.5

# MEAN DEVIATION

```python
array= np.array([100,150,200,250,360,490,500,600,671])
data= pd.Series(array)
mean= data.mean()
md= data.mad()
md
```

    174.44444444444446

# Co efficient of mean deviation

```python
comd= md/mean
comd
```

    0.47274917193616384

# Newton Gregory forward & Backward Interploatoin Formula

```python
def u_cal(u, n):
	temp = u;
	for i in range(1, n):
		temp = temp * (u - i)
	return temp;

def fact(n):
	f = 1
	for i in range(2, n + 1):
		f *= i
	return f
n = int(input("Enter n values"))
x= []
for i in range(0,n):
    ele= float(input("Enter x values"))
    x.append(ele)

print("X Values:")
print(x)
print()

y = [[0 for i in range(n)]
		for j in range(n)]
for i in range(0,n):
    y[i][0] = float(input("Enter y"))
print("Y Values:")
print(y)
print()

for i in range(1, n):
	for j in range(n - i):
		y[j][i] = y[j + 1][i - 1] - y[j][i - 1]

for i in range(n):
	print(x[i], end = "\t")
	for j in range(n - i):
		print(y[i][j], end = "\t")
	print("")

value = float(input("enter the value you need to find at:"))
sum = y[0][0]
u = (value - x[0]) / (x[1] - x[0])
for i in range(1,n):
	sum = sum + (u_cal(u, i) * y[0][i]) / fact(i)

print("\nValue at", value,
	"is", round(sum, 6))

```

    X Values:
    [5.0, 7.0, 11.0, 13.0, 17.0]

    Y Values:
    [[150.0, 0, 0, 0, 0], [392.0, 0, 0, 0, 0], [1452.0, 0, 0, 0, 0], [2366.0, 0, 0, 0, 0], [5202.0, 0, 0, 0, 0]]

    5.0	150.0	242.0	818.0	-964.0	3032.0
    7.0	392.0	1060.0	-146.0	2068.0
    11.0	1452.0	914.0	1922.0
    13.0	2366.0	2836.0
    17.0	5202.0

    Value at 9.0 is 1452.0

# Lagranges' Interploation Formula for unequal intervals

```python
n = int(input('Enter number of data points: '))
x = np.zeros((n))
y = np.zeros((n))
print('Enter data for x and y: ')
for i in range(n):
    x[i] = float(input( 'x['+str(i)+']='))
print(x)
for i in range(n):
    y[i] = float(input( 'y['+str(i)+']='))
print(y)

xp = float(input('Enter interpolation point: '))
yp = 0

for i in range(n):
    p = 1
    for j in range(n):
        if i != j:
            p = p * (xp - x[j])/(x[i] - x[j])
    yp = yp + p * y[i]

print('Interpolated value at %.3f is %.3f.' % (xp, yp))
```

    Enter data for x and y:
    [0. 2. 3. 6.]
    [ -4.   2.  14. 158.]
    Interpolated value at 4.000 is 40.000.

# Polynominal equations based on interpolations

```python
from sympy import simplify, symbols, expand, factor, sin, cos #..., lambdify, solve_poly_system
```

```python
%%writefile LagrangePolynomial.py
"""
From: https://gist.github.com/folkertdev/084c53887c49a6248839
A sympy-based Lagrange polynomial constructor.

Implementation of Lagrangian interpolating polynomial.
See:

   def lagrangePolynomial(xs, ys):

Given two 1-D arrays `xs` and `ys,` returns the Lagrange interpolating
polynomial through the points ``(xs, ys)``

Given a set 1-D arrays of inputs and outputs, the lagrangePolynomial function
will construct an expression that for every input gives the corresponding output.
For intermediate values, the polynomial interpolates (giving varying results
based  on the shape of your input).

The Lagrangian polynomials can be obtained explicitly with (see below):

   def polyL(xs,j):

as sympy polynomial, and

    def L(xs,j):

as Python functions.

This is useful when the result needs to be used outside of Python, because the
expression can easily be copied. To convert the expression to a python function
object, use sympy.lambdify.
"""
from sympy import symbols, expand, lambdify, solve_poly_system
#Python library for arithmetic with arbitrary precision
from mpmath import tan, e

import math

from operator import mul
from functools import reduce, lru_cache
from itertools import chain

# sympy symbols
x = symbols('x')

# convenience functions
product = lambda *args: reduce(mul, *(list(args) + [1]))

# test data
labels = [(-3/2), (-3/4), 0, 3/4, 3/2]
points = [math.tan(v) for v in labels]

# this product may be reusable (when creating many functions on the same domain)
# therefore, cache the result
@lru_cache(16)
def l(labels, j):
    def gen(labels, j):
        k = len(labels)
        current = labels[j]
        for m in labels:
            if m == current:
                continue
            yield (x - m) / (current - m)
    return expand(product(gen(labels, j)))

def polyL(xs,j):
    '''
    Lagrange polynomials as sympy polynomial
    xs: the n+1 nodes of the intepolation polynomial in the Lagrange Form
    j: Is the j-th Lagrange polinomial for the specific xs.
    '''
    xs=tuple(xs)
    return l(xs,j)

def L(xs,j):
    '''
    Lagrange polynomials as python function
    xs: the n+1 nodes of the intepolation polynomial in the Lagrange Form
    j: Is the j-th Lagrange polinomial for the specific xs.
    '''
    return lambdify(x, polyL(xs,j) )

def lagrangePolynomial(xs, ys):
    '''
    Given two 1-D arrays `x` and `w,` returns the Lagrange interpolating
    polynomial through the points ``(x, w)``.

    '''
    # based on https://en.wikipedia.org/wiki/Lagrange_polynomial#Example_1
    k = len(xs)
    total = 0

    # use tuple, needs to be hashable to cache
    xs = tuple(xs)

    for j, current in enumerate(ys):
        t = current * l(xs, j)
        total += t

    return total

def x_intersections(function, *args):
    "Finds all x for which function(x) = 0"
    # solve_poly_system seems more efficient than solve for larger expressions
    return [var for var in chain.from_iterable(solve_poly_system([function], *args)) if (var.is_real)]

def x_scale(function, factor):
    "Scale function on the x-axis"
    return functions.subs(x, x / factor)

if __name__ == '__main__':
    func = lagrangePolynomial(labels, points)

    pyfunc = lambdify(x, func)

    for a, b in zip(labels, points):
        assert(pyfunc(a) - b < 1e-6)
```

    Overwriting LagrangePolynomial.py

```python
%pylab inline
import pandas as pd
import numpy as np
import LagrangePolynomial as LP
from scipy import interpolate
```

    Populating the interactive namespace from numpy and matplotlib

```python
n=int(input("Enter the n values"))
x = np.zeros((n))
y = np.zeros((n))
for i in range(0,n):
    x[i]=float(input("Enter the x values"))
for i in range(0,n):
    y[i]=float(input("Enter the y values"))
print("X values:")
print(x)
print()

print("Y values:")
print(y)
print()
```

    X values:
    [1. 2. 3. 4. 5.]

    Y values:
    [ 1. -1.  1. -1.  1.]

```python
df=pd.DataFrame({ 'X':x,'Y':y})
df
```

      X
      Y

      0
      1.0
      1.0

      1
      2.0
      -1.0

      2
      3.0
      1.0

      3
      4.0
      -1.0

      4
      5.0
      1.0

```python
interpolate.lagrange?
```

    Signature: interpolate.lagrange(x, w)
    Docstring:
    Return a Lagrange interpolating polynomial.

    Given two 1-D arrays `x` and `w,` returns the Lagrange interpolating
    polynomial through the points ``(x, w)``.

    Warning: This implementation is numerically unstable. Do not expect to
    be able to use more than about 20 points even if they are chosen optimally.

    Parameters
    ----------
    x : array_like
        `x` represents the x-coordinates of a set of datapoints.
    w : array_like
        `w` represents the y-coordinates of a set of datapoints, i.e., f(`x`).

    Returns
    -------
    lagrange : `numpy.poly1d` instance
        The Lagrange interpolating polynomial.

    Examples
    --------
    Interpolate :math:`f(x) = x^3` by 3 points.

    >>> from scipy.interpolate import lagrange
    >>> x = np.array([0, 1, 2])
    >>> y = x**3
    >>> poly = lagrange(x, y)

    Since there are only 3 points, Lagrange polynomial has degree 2. Explicitly,
    it is given by

    .. math::

        \begin{aligned}
            L(x) &= 1\times \frac{x (x - 2)}{-1} + 8\times \frac{x (x-1)}{2} \\
                 &= x (-2 + 3x)
        \end{aligned}

    >>> from numpy.polynomial.polynomial import Polynomial
    >>> Polynomial(poly).coef
    array([ 3., -2.,  0.])
    File:      c:\users\lenovo\anaconda3\envs\myenv\lib\site-packages\scipy\interpolate\interpolate.py
    Type:      function

```python
P=interpolate.lagrange(df.X,df.Y)
print(P)
```

            4     3         2
    0.6667 x - 8 x + 33.33 x - 56 x + 31

# Inverse Interpolation

```python
n = int(input('Enter number of data points: '))
x = np.zeros((n))
y = np.zeros((n))
print('Enter data for x and y: ')
for i in range(n):
    x[i] = float(input( 'x['+str(i)+']='))
print(x)
for i in range(n):
    y[i] = float(input( 'y['+str(i)+']='))
print(y)

yp = float(input('Enter interpolation point: '))
xp = 0

for i in range(n):
    p = 1
    for j in range(n):
        if i != j:
            p = p * (yp - y[j])/(y[i] - y[j])
    xp = xp + p * x[i]

print('Interpolated value at %.3f is %.3f.' % (yp,xp))
```

    Enter data for x and y:
    [ 5.  6.  9. 11.]
    [12. 13. 14. 16.]
    Interpolated value at 15.000 is 11.500.


Function basis methods
======================

```
%matplotlib inline
```

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

matplotlib.rcParams.update({'font.size': 14})
```

Introduction to function basis methods
--------------------------------------

### Boundary Value Problems

Considering simple boundary value problem

$$y'' = f(x, y, y'), \quad y(a) = A, \,\, y(b) = B, \quad x \in [a,b].$$

Boundary conditions are only examples here.

Have considered the shooting method (accurate, efficient, may not work)
and the finite difference (relaxation) method (not particularly accurate
or efficient, nearly always works). Both methods require a *grid* with
$n$ points. Accuracy of method depends on $h \propto n^{-1}$.

Can instead use a function basis, independently of a grid.

### Function basis expansion

Aim to solve problem

$${\cal L} y = f,$$

where ${\cal L}$ is a differential operator.

Function basis methods *assume* the solution $y(x)$ can be written

$$y(x) = \sum_{j} c_j u_j (x);$$

constants $c_j$ are *basis coefficients*, $u_j(x)$ are *basis
functions*.

*Choose* “simple” basis functions $u_j$. Get *approximate* solution by
truncating series:

$$y(x) = \sum_{j}^n c_j u_j (x).$$

Additional conditions give linear system for unknowns ($c_j$), defining
solution everywhere.

Collocation methods
-------------------

Simplest function basis method uses a grid. *Collocation* method: fix
$c_j$ by insisting that BVP satisfied exactly at fixed set of points.

1.   Assume approximate solution has form ($n$ fixed)

    $$\sum_{j}^n c_j u_j (x).$$
2.   Boundary conditions $\implies$ two coefficients (e.g. $c_{0, 1}$).
3.  Evaluate ${\cal L} y$ at collocation points $\{x_j\}$ $\implies$ linear system.
4.  Solve the linear system to give the basis coefficients $c_j$.

### Example

We consider the problem (with exact solution $\exp(x)$)

$$y'' - y = 0, \quad y(0) = 1, \,\, y(1) = e.$$

Choose basis $u_j = x^j \implies y = \sum c_j x^j$. Boundary conditions:

$$c_0 = 1, \quad c_1 = e - \sum_{j \ne 1}^n c_j.$$

Differential operator ${\cal L} y = y'' - y$:

$$\sum_{j=2}^n c_j \left( j (j - 1) x^{j-2} \right) - \sum_{k=0}^n
c_k x^k = 0.$$

Linear system $A {\boldsymbol{c}} = {\boldsymbol{b}}$ ($\{ x_l\}$
collocation points)

$$A_{j l} = \left( j (j - 1) x_l^{j-2} \right) - x_l^j , \quad
{\boldsymbol{b}} \text{ from boundary conditions.}$$

When using only three polynomial basis functions we have

$$c_0 = 1, \quad c_1 = e - 1 - c_2.$$

We then evaluate the system at the collocation point $x = 1/2$ to get

$$\begin{aligned}
   && 0 & = 2 \cdot 1 \cdot \left(\tfrac{1}{2}\right)^0 c_2 - 1 -
\tfrac{1}{2} \left( e - 1 - c_2 \right) -
\left(\tfrac{1}{2}\right)^2 c_2 \\
   \Rightarrow && c_2 & = \tfrac{2}{9} (1 + e).
  \end{aligned}$$

This, combined with boundary conditions, gives approximate solution

$$y = 1 + \tfrac{1}{9} \left( ( 7e - 11) x + 2(1 + e) x^2 \right).$$

### Example: 3

With only a few points the method is very accurate.

Use Chebyshev collocation points

$$x_k = \tfrac{1}{2} \left( 1 + \cos \left( \frac{(k-1) \pi}{n-1}
\right) \right)$$
to check convergence with $n$.

Convergence is very fast.

```
x_exact = np.linspace(0.0, 1.0, 5000)
y_exact = np.exp(x_exact)
```

```
def CollocationPoly(nbasis):
    """Find the coefficients in the expansion for the above problem."""

    # Collocation points are the Chebyshev points
    x = 0.5 * (1.0 + np.cos(np.pi * np.array(range(nbasis-1,-1,-1)) / (nbasis-1)))

    b = np.zeros_like(x) # The RHS vector
    A = np.zeros((nbasis, nbasis)) # The matrix

    # Use the left boundary to fix the first coefficient
    b[0] = 1.0
    A[0, 0] = 1.0
    # Use the right boundary to fix the last coefficient
    b[-1] = np.exp(1.0)
    for j in range(nbasis):
        A[-1, j] = 1.0
    # Fill the rest of the matrix
    for i in range(1, nbasis-1):
        for j in range(nbasis):
            # The -y' term
            A[i, j] -= x[i]**(j)
        for j in range(2, nbasis):
            # The y'' term
            A[i, j] += j*(j-1)*x[i]**(j-2)

    # Solve for coefficients
    c = np.linalg.solve(A, b)

    return c
```

```
# Do the simple case with 3 basis functions
c3 = CollocationPoly(3)
y3 = np.zeros_like(x_exact)
for j in range(3):
    y3 += c3[j] * x_exact**j

# Plot result
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111)
ax.plot(x_exact, y_exact, 'k-', label = "Exact solution")
ax.plot(x_exact, y3, 'b--', label = "Using 3 collocation points")
ax.legend(loc=2)
ax.set_xlabel(r"$x$")
ax.set_ylabel(r"$y$");
```

```
# Check the convergence with resolution.
nbasis = range(3, 16)
collocation_error = np.zeros((len(nbasis),))
for i in range(len(nbasis)):
    c = CollocationPoly(nbasis[i])
    yn = np.zeros_like(x_exact)
    for j in range(nbasis[i]):
        yn += c[j] * x_exact**j
    collocation_error[i] = np.linalg.norm(yn - y_exact, 2)
# Plot errors
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111)
ax.semilogy(nbasis, collocation_error, 'kx')
ax.set_xlabel("Number of basis functions")
ax.set_ylabel(r"$\|$"+"Error"+r"$\|_2$");
```

Norm method
-----------

Collocation

1.  not independent of grid;

2.  convergence depends on location of points;

3.  BVP solved only at points, otherwise unconstrained.

Instead try to get best solution “on average”. Minimize average error in
some norm,

$$\| {\cal L} (y) (x) - f(x) \|.$$

Assume a function basis expansion

$$y = \sum_{j}^n c_j u_j (x).$$

Gives a minimization problem for the coefficients $c_j$.

### Example

For the example given above we have

$${\cal L} = y'' - y, \quad f = 0,$$

and hence we want to minimize

$$\| y'' - y \|.$$

Norm is over whole interval – so integrate. Typically use 2-norm:

$$\begin{aligned}
F(c_j) & = \| y'' - y \|_2^2 \\
& = \int_0^1 \left[ y'' - y \right]^2 \, \text{d}x.
  \end{aligned}$$

This is function of $c_j$ using function basis assumption.

### Example: 2

As normal fix two coefficients using boundary conditions. Using three
basis functions

$$c_0 = 1, \quad c_1 = e - 1 - c_2.$$

Explicitly computing the quadratic form gives us the “average error”

$$F(c_j) = \tfrac{47}{10} c_2^2 - \tfrac{13}{6} (1 + e) c_2 +
\tfrac{1+e+e^2}{3}.$$
To minimize, differentiate with respect to $c_2$ and set to zero, giving

$$c_2 = \tfrac{65}{282} (1 + e).$$

### Example: 3

Even with a few coefficients the result is very accurate.

Additional analysis required to set up general problem.

Ritz methods
------------

General framework: working in Hilbert space $L_2$ define *inner product*

$$< u, v > = \int_a^b u \cdot v \, \text{d} x.$$

Need ${\cal L}$ to be symmetric and positive definite.

Inner product can *measure distance* on $L_2$: i.e., measure the
distance to the “exact solution” of the ODE.

So define *energy* of the element $u$ as

$$< {\cal L}(u), u > \,\, \ge 0,$$

where inequality follows by integration by parts. Then to get solution
minimize

$$J(u) = < {\cal L}(u), u > - 2 < f, u >.$$

### Ritz method applied to a function basis

Can minimize the functional

$$J(y) = < {\cal L}(y), y > - 2 < f, y >$$

when $y$ is given with respect to a function basis,

$$y = \sum_j^n c_j u_j(x).$$

Linearity means this is equivalent to minimizing quadratic form

$$J(y) = \sum_{m,k}^n c_m c_k < {\cal L}(u_m), u_k > - 2 \sum_m c_m
< f, u_m >.$$

Know $u_j$, so re-express as a condition on $c_j$.

### Ritz method applied to a function basis

$$y = \sum_j^n c_j u_j(x).$$

$$J(y) = \sum_{m,k}^n c_m c_k < {\cal L}(u_m), u_k > - 2 \sum_m c_m
< f, u_m >.$$
Minimizing this functional requires

$$\begin{align}
&& \frac{\partial{}}{\partial{c_m}} J & = 0, & m = 1, \dots, n, \\
\Rightarrow && \sum_m^n c_m < {\cal L}(u_m), u_k > & = < f, u_k >,
& k = 1, \dots, n.
  \end{align}$$

This is just a linear system to solve.

### Example

For the boundary value problem

$$y'' = -\cos(x), \quad y(0) = 0 = y(\pi)$$

we have the exact solution $y(x) = \cos(x) + 2 x /\pi - 1$. Symmetry of
problem suggests the function basis

$$u_j = \sin(2 j x):$$

satisfies boundary conditions and symmetry.

We need to satisfy

$$\sum_m^n c_m < {\cal L}(u_m), u_k >  = < f, u_k >.$$

Vector of unknowns $c_m$; known matrix has elements $A_{m,k} = <
  {\cal L}(u_m), u_k >$; known vector has elements $< f, u_k >$.

### Example: 2

The *matrix* $A_{m,k} = < {\cal L}(u_m), u_k >$ has elements

$$\begin{aligned}
< {\cal L}(u_m), u_k > & = -\int_0^{\pi} 4 m^2 \sin(2 m x)
\sin(2 k
x) \, \text{d} x \\
& =
\begin{cases}
  -2 \pi k^2, & \text{if $m = k$}, \\
  0, & \text{if $m \ne k$}
\end{cases}.
\end{aligned}$$

The *vector* $< f, u_k >$ has elements

$$\begin{aligned}
< f, u_k > & = \int_0^{\pi} -\cos(x) \sin(2 k x) \, \text{d} x \\
& = \frac{4 k}{4 k^2 - 1}.
\end{aligned}$$

$$\begin{aligned}
< {\cal L}(u_m), u_k > & = \begin{cases}
  -2 \pi k^2, & \text{if $m = k$}, \\
  0, & \text{if $m \ne k$}
\end{cases}, \\
< f, u_k > & = \frac{4 k}{4 k^2 - 1}.
\end{aligned}$$

This gives a *diagonal* system which can be solved to give

$$c_m = \frac{2}{\pi m (4 m^2 - 1)}$$

and hence the approximation

$$y = \frac{2}{\pi} \sum_{m=1}^n \frac{\sin(2 m x)}{m (4 m^2 - 1)}$$

which converges to the Fourier series in the limit $n \rightarrow
\infty$.

### Example: 3

In fact even with very few coefficients the Ritz method is accurate.

It also converges quickly.

```
x_exact = np.linspace(0.0, np.pi, 5000)
y_exact = np.cos(x_exact) + 2.0 * x_exact / np.pi - 1.0
```

```
def RitzCoefficients(nbasis):
    """Returns the Ritz coeffcients for the problem above. Note: cheats and use the form computed analytically above."""

    A = np.zeros((nbasis,nbasis))
    b = np.zeros((nbasis,))
    for i in range(nbasis):
        b[i] = 4.0 * (i+1) / (4.0 * (i+1)**2 - 1.0)
        A[i, i] = 2.0 * np.pi * (i+1)**2
    c = np.linalg.solve(A, b)

    return c
```

```
# Plot the result with 3 coefficients
c3 = RitzCoefficients(3)
y3 = np.zeros_like(x_exact)
for j in range(3):
    y3 += c3[j] * np.sin(2.0*(j+1)*x_exact)

# Plot result
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111)
ax.plot(x_exact, y_exact, 'k-', label = "Exact solution")
ax.plot(x_exact, y3, 'b--', label = "Using 3 collocation points")
ax.legend(loc=1)
ax.set_xlabel(r"$x$")
ax.set_ylabel(r"$y$");
```

```
# Check the convergence with resolution.
nbasis = range(3, 33, 2)
collocation_error = np.zeros((len(nbasis),))
for i in range(len(nbasis)):
    c = RitzCoefficients(nbasis[i])
    yn = np.zeros_like(x_exact)
    for j in range(nbasis[i]):
        yn += c[j] * np.sin(2.0*(j+1)*x_exact)
    collocation_error[i] = np.linalg.norm(yn - y_exact, 2)
# Plot errors
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111)
ax.loglog(nbasis, collocation_error, 'kx')
ax.set_xlabel("Number of basis functions")
ax.set_ylabel(r"$\|$"+"Error"+r"$\|_2$");
```

Summary
=======

-   When extreme accuracy is essential then function basis methods are
often used.
-   Collocation methods are popular; given the right choice of basis and
collocation points the convergence can be faster than any
polynomial, giving floating point accuracy with a few dozen basis
functions at most.
-   Norm and Ritz type methods do not use a grid at all; this can make
them useful in complex domains, particular as a small part of a
larger method (see finite elements).
-   The complexity of these methods, particularly norm and Ritz, makes
the work involved in setting up the problem quite extreme. The
actual coding of the method is, however, very small for the
impressive accuracy.

Lectures/20 - Function Basis Methods.ipynb