![Callysto.ca Banner](https://github.com/callysto/curriculum-notebooks/blob/master/callysto-notebook-banner-top.jpg?raw=true)

# Tipping Point - Jobs

## Which job pays better?

## Congratulations!

So you've got a new job... Oh, wait, you have two job offers!?!  Well, congratulations twice!

Let's take a look at the two companies offering a job:

Job One            |  Job Two
:-------------------------:|:-------------------------:
![](images/Acme.png)  |  ![](images/Bigby.png)

Both companies **Acme** and **Bigby** are Electronic Supply companies, and are offering you a sales position.

**Which job should you take?** Well, let's look how much each job pays.

1. Acme offers a base salary of $\$$1200 per month, plus 3\% commission on sales over $\$$1000.
2. Bigby offers a base salary of $\$$250 per month, plus 18\% commission on all sales.

**Your Task:** Figure out which job pays better.

**Need a Hint?**

It seems clear that your salary will depend on how much you sell each month.

You can consider various sales amounts, and calculate how much you'd earn at either job with those same sales amounts.

Since this notebook module can interpret basic math operations, we can type in the formulas for salary.

Acme pays a base salary of $\$$1200 plus 3\% on sales, we can write this formula as

$$ Acme\ pay = 1200 + .03*(sales-1000). $$

Meanwhile, Bigby pays a base salary of $\$$250 plus 18\% on sales, we can write this formula as

$$ Bigby\ pay = 250 + .18*sales.$$

## Calculations with Python

Consider the examples below.

Let's start the calculation with no sales at all. You can do this in your head, but let's try it out in Python code.

## For zero sales
1200 + 0, 250 + 0

The above calculation tells us that for zero sales, Acme pays $\$$1200 and Bigby pays $\$$250. So Acme pays better in this case.

Let's next try sales of $\$$2000

## For 2000 dollars in sales
1200 + .03*(2000-1000), 250 + .18*2000

That is, for $\$$2000 in sales, Acme pays $\$$1230 and Bigby pays $\$$610. So Acme also pays better in this case.

Now let's be more ambitious, and say you can make $\$$10,000 in sales. How much will you make?

## for 10000 in sales
1200 + .03*(10000-1000), 250 + .18*10000

Here, for $\$$10000 in sales, Acme pays $\$$1470 and Bigby pays $\$$2050. So this time, Bigby pays better!

## Graphical Comparison

It seems that once your sales are high enough, Bigby pays better than Acme does.

Let's plot some curves to show this. We can use Python to make a quick plot, with a few lines of code as follows.

First, we load in some code packages for plotting, in the following three lines.

from numpy import linspace, maximum
from ipywidgets import interact
import plotly.graph_objects as go

Now we define a few variables for sales and company pay, then plot the data in a scatter plot graph.

sales = linspace(0,10000)
bigby = 250 + .18*sales
acme = 1200 + .03*maximum(sales-1000,0)
go.Figure(go.Scatter(x=sales, y=acme, name='Acme')).add_trace(go.Scatter(x=sales,y=bigby,name="Bigby"))

### Comments on the code

The variable **sales** is a list of possible sales values, from 0 to 10,000, set up using the **linspace** function.

The variable **bigby** is the same formula we used above, adding 18\% of sales to a base salary of $\$$250.

The variable **acme** is just a little more complicated. We use the **maximum** function to make sure we only get a commision on sales **over** $\$$1000.

The **go.Figure** command creates a scatter plot with the **Acme** data, overlaid with a second trace of the **Bigby** data.

With this plot, you can pan and zoom in on the lines, to investigate the data more directly.

## The Tipping Point

It seems clear from the calculations above, and from the graphs, that eventually Bigby pays more than Acme once the sales get high enough.

The **tipping point** is the point where the sales are big enough that Bigby pays more.

How can we find this tipping point?

One way is to simply use Python. Type in a value for **sales** in the code below, replacing the number **4000** with a number of your choice. Try to find the number where these two pay values are equal.

sales = 4000
1200 + .03*(sales-1000), 250 + .18*sales

✏ **Exercise:** Double click on this cell and enter the value(s) you find for **sales** where both pays are the same?

## Tipping point with interactive tools
Python lets you do this calculation automatically. Just type in the box below, and it will compute the Acme and Bigby pay. Again, try to find the value for **sales** so that the two pays are equal. **Note**, the following expressions use Python lambda functions and interactive Jupyter widgets; try these links to learn more about [lambda functions](https://realpython.com/python-lambda/) and [widgets](https://ipywidgets.readthedocs.io/en/latest/examples/Using%20Interact.html).

interact(lambda sales: [1200+.03*(float(sales)-1000),250+.18*float(sales)],sales='4000');

You can also use a slider in Python to play around with the sales, as follows:

Again, try to find the point where the two companies pay the same.

interact(lambda sales: [1200+.03*(sales-1000),250+.18*sales],sales=(6000,7000));

✏ **Exercise:** Double click on this cell and enter the value(s) you find for **sales** where both pays are the same?

## Tipping point by algebra

And a final way: set the two pay formulas to be equal, and solve for **sales**.

$$ 1200 + .03\times (sales - 1000) = 250 + .18 \times sales.$$

What do you find for **sales**?

If yoy prefer to **solve for x**, try this formula:

$$ 1200 + .03(x - 1000) = 250 + .18  x.$$

The solution for **x** is the dollar value of sales where Bigby and Acme pay you the same. Any more sales, and Bigby pays more.

✏ **Exercise:** Double click on this cell and enter your answer for **x** which solves this equation.

## Minimum wage

The minimum wage is the lowest pay a company can pay you, per hour, and stay within the law.

**Exercise:** How much do you need to sell, in order to get paid at least as much as a minimum wage salary?

This is a challenging question, and an important one before you take on a new job. You must consider:
- what is the minimum wage in your area?
- how many hours would you typically work in a year?

For instance, in Alberta, the minimum wage is $\$$15 per hour. Most people work about 40 hours a week, for 50 weeks a year, which is 2000 hours per year.

That results in an annual salary of $\$$30,000 per year, or $\$$2,500 per month.

$$ 2,500 = 30,000 \div 12.$$

Working at Bigby gives you more money, so let's find out how many dollars in sales at Bigby are needed to make the same as minimum wage.

Remembering our formulas above are for monthly pay, we solve:
$$ 2,500 = 250 + .18x.$$

**Exercise:** What do you find for the value of **x**?

## ✏ You can use this area to calculate the answer in Python. Solve for x in the above equation.

## Sanity check

You'll find $x$ is over $\$$10,000 per month. That means you'll need to sell over a hundred and twenty thousand dollars worth of electronics every year, just to make minimum wage.

Does that sound like a lot to sell?

**Exercise:** Exactly how much in sales **per year** do you need to make minimum wage, in Alberta?

## ✏ Use this area to calculate the answer in Python.
## Take the answer x from the last Exercise, and convert from monthly to yearly sales.

## Working hard at Acme

It seems obvious that Acme pays much less than Bigby, when the sales are large.  So let's check with this exercise.

**Exercise:** Following the calculations as above, how many dollars in sales are needed at Acme to match the minimum wage month of $\$$2,500?

**Do you need a hint?** Try solving this equation for **x** (and explain why this is the one you want):

$$ 2,500 = 1200 + .03(x-1000).$$

## ✏ Use this area to calculate your answer in Python.

**Exercise:** Given your answers, how much more do you need in sales at Acme to match the monthly minimum wage, than you need at Bigby? (Bigby should be looking pretty good to you now!)

## ✏ Use this area to calculate your answer in Python.

## Another province.

What is minimum wage in your province? (You can look it up on the internet.) For instance, in Saskatchewan, it is $\$$11 per hour. In Ontario, $\$$14 per hour.

**Exercise:**
- Imitating the calculations above, what is the monthly minimum wage salary in your province?
- How much product do you have to sell per month at Bigby, in order to match this month minimum wage salary?

## ✏ Use this area to calculate your answer in Python.

## Summary

- We learned that different jobs can have different pay rates.
- By creating formulas for the pay, we can determine which job pays better.
- We can make the comparison by individual calculations, by plotting data, or using some algebra.
- Knowing the minimum wage for your province gives you something to compare with, and perhaps aiming higher.

[![Callysto.ca License](https://github.com/callysto/curriculum-notebooks/blob/master/callysto-notebook-banner-bottom.jpg?raw=true)](https://github.com/callysto/curriculum-notebooks/blob/master/LICENSE.md)


"""
Filename: cartesian.py

Authors: Pablo Winant

Implements cartesian products and regular cartesian grids.
"""

import numpy
from numba import njit

def cartesian(nodes, order="C"):
    """Cartesian product of a list of arrays

    Parameters:
    -----------
    nodes: (list of 1d-arrays)
    order: ('C' or 'F') order in which the product is enumerated

    Returns:
    --------
    out: (2d-array) each line corresponds to one point of the product space
    """

    nodes = [numpy.array(e) for e in nodes]
    shapes = [e.shape[0] for e in nodes]

    n = len(nodes)
    l = numpy.prod(shapes)
    out = numpy.zeros((l, n))

    if order == "C":
        repetitions = numpy.cumprod([1] + shapes[:-1])
    else:
        shapes.reverse()
        sh = [1] + shapes[:-1]
        repetitions = numpy.cumprod(sh)
        repetitions = repetitions.tolist()
        repetitions.reverse()

    for i in range(n):
        _repeat_1d(nodes[i], repetitions[i], out[:, i])

    return out

def mlinspace(a, b, nums, order="C"):
    """Constructs a regular cartesian grid

    Parameters:
    -----------
    a: (1d-array) lower bounds in each dimension
    b: (1d-array) upper bounds in each dimension
    nums: (1d-array) number of nodes along each dimension
    order: ('C' or 'F') order in which the product is enumerated

    Returns:
    --------
    out: (2d-array) each line corresponds to one point of the product space
    """

    a = numpy.array(a, dtype="float64")
    b = numpy.array(b, dtype="float64")
    nums = numpy.array(nums, dtype="int64")
    nodes = [numpy.linspace(a[i], b[i], nums[i]) for i in range(len(nums))]

    return cartesian(nodes, order=order)

@njit(cache=True)
def _repeat_1d(x, K, out):
    """Repeats each element of a vector many times and repeats the whole result many times

    Parameters
    ----------

    x: (1d array)       vector to be repeated
    K: (int)            number of times each element of x is repeated (inner iterations)
    out: (1d array)     placeholder for the result

    Returns
    -------
    None
    """

    N = x.shape[0]
    L = out.shape[0] // (K * N)  # number of outer iterations
    # K                       # number of inner iterations

    # the result out should enumerate in C-order the elements
    # of a 3-dimensional array T of dimensions (K,N,L)
    # such that for all k,n,l, we have T[k,n,l] == x[n]

    for n in range(N):
        val = x[n]
        for k in range(K):
            for l in range(L):
                ind = k * N * L + n * L + l
                out[ind] = val


from math import pow
import matplotlib.pyplot as plt
import numpy as np

class Zero:
    def __init__(self, maturity):
        self.maturity = maturity

    def zero_ytm(self, price):
        return pow(100/price, 1/self.maturity)-1

    def price(self, ytm):
        return 100/pow(1+ytm, self.maturity)

# periods
maturities = [2, 5, 10, 30]

# yield
# https://www.bloomberg.com/markets/rates-bonds/government-bonds/us
yields = [0.0018, 0.0069, 0.0122, 0.0189]

bonds = [Zero(i) for i in maturities]
prices = [bonds[i].price(yields[i]) for i in range(len(yields))]
print(prices)

maturities.pop(2)
marketYield = yields.pop(2)
marketPrice = prices.pop(2)
print(marketPrice, marketYield)

# because pop 10Y so interp at 10
interpolatedPrice = np.interp(10, maturities, prices)
interpolatedYeild = np.interp(10, maturities, yields)

print(interpolatedPrice, interpolatedYeild)
print(bonds[2].price(interpolatedYeild))

'''
# show graph
plt.plot(maturities, yields)
plt.show()
'''


from numpy.linalg import svd
from sklearn.datasets import load_iris as iris
from matplotlib.pyplot import scatter, show, plot, title, xlabel, ylabel
from numpy import array, cov
from sklearn.preprocessing import scale
from sklearn.decomposition import PCA

class PCADimensionReduction:
    def __init__(self, new_dim):
        self.new_dim = new_dim
        self.percent = None

    def fit_transform(self, data):
        variance_matrix = cov(data.T)
        U, S, _ = svd(variance_matrix)
        new_data = data @ U[:, :self.new_dim]
        percent = sum(S[: self.new_dim]) / sum(S)
        self.percent = percent
        return new_data

    def get_percent(self):
        return self.percent

colors = array(['red', 'green', 'blue'])
data = scale(iris().data)

pca = PCADimensionReduction(new_dim=2)
transformed = pca.fit_transform(data)
scatter(transformed[:, 0], transformed[:, 1], c=colors[iris().target])
show()

# scikit-learn way:
sk_pca = PCA(n_components=2)
transformed = sk_pca.fit_transform(data)
scatter(transformed[:, 0], transformed[:, 1], c=colors[iris().target])
show()


import numpy as np

A=np.array([[1,-2j],[2j,5]])
print(A)

#A=L.dot(L^H) with A is positive definite matrix and L is lower triangular matrix.

L=np.linalg.cholesky(A)
print(L)
print(L.dot(L.T.conj()))

a=np.array([[4,12,-16],[12,37,-43],[-16,-43,98]])

L=np.linalg.cholesky(a)
print(L)
L_T=L.transpose()
print(L.dot(L_T))


"""
############### Pseudocode ##############
value = N
factors = list()
while N > 1:
    i = 0
    if sympy.isprime(i) and N % prime[i] == 0:
        factors.append(prime[i])
        N = int(N/prime[i])
    else:
        i += 1
return factors
"""
import sympy

def primeFact(N):
    factors = list()
    i = 2
    while N > 1:
        if sympy.isprime(i) and N % i == 0:
            factors.append(i)
            N = int(N/i)
        else:
            i += 1
    return factors

print(primeFact(int(input("Number: "))))


#!/usr/bin/env python

"""

an example of using the multigrid class to solve Laplace's equation.  Here, we
solve

u_xx = sin(x)
u = 0 on the boundary [0,1]

The analytic solution is u(x) = -sin(x) + x sin(1)

"""
#from io import *
import numpy
import multigrid
import pylab

# the analytic solution
def true(x):
    return -numpy.sin(x) + x*numpy.sin(1.0)

# the L2 error norm
def error(myg, r):

    # L2 norm of elements in r, multiplied by dx to
    # normalize
    return numpy.sqrt(myg.dx*numpy.sum((r[myg.ilo:myg.ihi+1]**2)))

# the righthand side
def f(x):
    return numpy.sin(x)

# test the multigrid solver
nx = 64

# create the multigrid object
a = multigrid.ccMG1d(nx,
                     xlBCtype="dirichlet", xrBCtype="dirichlet",
                     verbose=1)

# initialize the solution to 0
init = a.solnGrid.scratchArray()

a.initSolution(init)

# initialize the RHS using the function f
rhs = f(a.x)
a.initRHS(rhs)

# solve to a relative tolerance of 1.e-11
a.solve(rtol=1.e-11)

# alternately, we can just use smoothing by uncommenting the following
#a.smooth(a.nlevels-1,50000)

# get the solution
v = a.getSolution()

# compute the error from the analytic solution
b = true(a.x)
e = v - b

print " L2 error from true solution = %g\n rel. err from previous cycle = %g\n num. cycles = %d" % \
      (error(a.solnGrid, e), a.relativeError, a.numCycles)


from sympy import FiniteSet, pi
from fractions import Fraction

s = FiniteSet( 2, 4, 6 )
s

s = FiniteSet( 1, 1.5, Fraction( 1, 5 ) )
s

len( s )

4 in s
1 in s

s = FiniteSet()
s

members = [ 1, 2, 3 ]
s = FiniteSet( *members )
s

values = { 'a': 1, 'b': 2 }
s = sum(**values)
s

a = FiniteSet( 1, 2, 3 )

for member in a:
	print( member )

a = FiniteSet( 3, 4, 5 )
t = FiniteSet( 5, 4, 3 )

s == t

a = FiniteSet( 1 )
t = FiniteSet( 1, 2 )

s.is_subset( t )

t.is_subset( t )

s = FiniteSet( 1, 2, 3 )
ps = s.powerset()
ps

len( ps )

s = FiniteSet( 1 , 2, 3 )
t = FiniteSet( 1, 2, 3 )

s.is_proper_subset( t )

t = FiniteSet( 1, 2, 3, 4 )

t.is_proper_subset( s )
s.is_proper_subset( t )

s = FiniteSet( 1, 2, 3 )
t = FiniteSet( 2, 4, 6 )
s.union( t )

s = FiniteSet( 1, 2, 3 )
t = FiniteSet( 2, 4, 6 )
s.intersect( t )

s = FiniteSet( 1, 2, 3 )
t = FiniteSet( 2, 4, 6 )
u = FiniteSet( 3, 5, 7 )

s.union( t ).union( u )

s.intersect( t ).intersect( u )

s = FiniteSet( 1, 2 )
t = FiniteSet( 3, 4 )

p = s*t
p

for elem in p:
	print( elem )

len( p ) == len( s ) * len( t )

s = FiniteSet( 1, 2 )
p = s ** 3
p

for elem in p:
	print( elem )

def time_period( length ):
	g = 9.8
	T = 2 * pi * ( length ) ** 0.5
	return T

L = FiniteSet( 15, 18, 21, 22.5, 25 )

for l in L:
	t = time_period( l / 100 )
	print( 'Length {0} cm Time Period {1:.3f} s'.format( float( l ), float( t ) ) )


import math
import numpy as np
from pylab import *
from matplotlib import pyplot as plt

# start to calculate area of contour, use green theory
def area(vs):
    a = 0
    x0,y0 = vs[0]
    for [x1,y1] in vs[1:]:
        dx = x1-x0
        dy = y1-y0
        a += 0.5*(y0*dx - x0*dy)
        x0 = x1
        y0 = y1
    return a

#start to use green theory to get centroid of contour
def centroid_for_polygon(polygon,contourarea):
    imax = len(polygon)-1
    cx = 0
    cy = 0
    for i in range(0,imax):
        cx += (polygon[i][0] + polygon[i+1][0]) * ((polygon[i][0] * polygon[i+1][1]) - (polygon[i+1][0] * polygon[i][1]))
        cy += (polygon[i][1] + polygon[i+1][1]) * ((polygon[i][0] * polygon[i+1][1]) - (polygon[i+1][0] * polygon[i][1]))
    cx += (polygon[imax][0] + polygon[0][0]) * ((polygon[imax][0] * polygon[0][1]) - (polygon[0][0] * polygon[imax][1]))
    cy += (polygon[imax][1] + polygon[0][1]) * ((polygon[imax][0] * polygon[0][1]) - (polygon[0][0] * polygon[imax][1]))
    cx /= (contourarea * 6.0)
    cy /= (contourarea * 6.0)
    Coorcentroid = (cx,cy)
    return Coorcentroid

# find perimeter of contour
def find_perimeter(v):
    imax = len(v) - 1
    perimeter = 0
    for i in range(0, imax):
        perimeter += ((v[i][0] - v[i + 1][0]) ** 2 + (v[i][1] - v[i + 1][1]) ** 2) ** 0.5
    return perimeter

# find radial length of sample points
def find_radial_length(sample, center):
    radlen = list()
    smax = len(sample)
    for i in range(0, smax):
        samplen = ((sample[i][0] - center[0]) ** 2 + (sample[i][1] - center[1]) ** 2) ** 0.5
        radlen.append(samplen)
    minradial = min(radlen)
    maxradial = max(radlen)
    return radlen, minradial, maxradial

# find radius of whole points
def find_radius(v, center):
    imax = len(v)
    radiussum = 0
    radius_list = list()
    for i in range(0, imax):
        radius = ((v[i][0] - center[0]) ** 2 + (v[i][1] - center[1]) ** 2) ** 0.5
        radius_list.append(radius)
        radiussum += radius
    radius = float(radiussum) / float(imax)
    maxradius = max(radius_list)
    normalized_radius = float(radius) / float(maxradius)
    return normalized_radius, maxradius

# find curvature
def get_curvature(x, y):
    curvlist = list()
    dx = np.array(np.gradient(x))
    dy = np.array(np.gradient(y))

    d2x_dt2 = np.gradient(dx)
    d2y_dt2 = np.gradient(dy)

    for i in range(len(dx)):
        divi = (dx[i] * dx[i] + dy[i] * dy[i]) ** 1.5
        if divi != 0:
            curvature = np.abs(d2x_dt2[i] * dy[i] - dx[i] * d2y_dt2[i]) / divi
            curvature = curvature**2
            curvlist.append(curvature)
        else:
            curvature = np.abs(d2x_dt2[i] * dy[i] - dx[i] * d2y_dt2[i])
            curvature = curvature**2
            curvlist.append(curvature)
    return curvlist

# find bending energy
def BendingEnergy(sample,samplex,sampley):

    imax = len(sample)
    curvature = get_curvature(samplex,sampley)
    sumcurv = sum(curvature)
    be = float(sumcurv)/float(imax)
    return curvature,be

# random generate angle with 0.01 interval
delta = 0.01
x = np.arange(0, 2*pi, delta)
y = np.arange(0, 2*pi, delta)

# get radius of circle
r = 200

# get centroid axis of circle
xc = 100
yc = 100

# get circle points
for i in range(len(x)):
    x[i] = xc + r*cos(x[i])
    y[i] = yc + r*sin(y[i])

# construct circle points by combining two random x and y lists
v = np.column_stack((x,y))

figure()
plt.plot(x,y)

samplestep = 1
sample = list()
samplex = list()
sampley = list()

# select sample by interval of samplestep
randIndex = np.arange(0,len(v),samplestep)
for i in randIndex:
    samplex.append(v[i-1][0])
    sampley.append(v[i-1][1])
    sample.append(v[i-1])

contourarea = np.abs(area(v))
realarea = pi*(r**2)
print 'right area:', realarea
print 'area:',contourarea,'\n'

Coorcentroid = centroid_for_polygon(v,contourarea)
realcentroid = (xc,yc)
print 'right centroid:',realcentroid
print 'centroid of contour:', Coorcentroid,'\n'

perimeter = find_perimeter(v)
realperimeter = 2*pi*r
print 'right perimeter:',realperimeter
print 'perimeter:', perimeter,'\n'

# find compactness
Compactness = float(perimeter**2)/float(contourarea)
realcompact = float(realperimeter**2)/float(realarea)
print 'right compactness', realcompact
print 'compactness:',Compactness, '\n'

radlen,minradial,maxradial = find_radial_length(sample,Coorcentroid)
print 'radial length list:',radlen
print 'minimum of radial length:',minradial
print 'maximum of radial length:',maxradial,'\n'

normradius,maxradius = find_radius(v,Coorcentroid)
print 'normalized radius:',normradius
print 'maximum radius:',maxradius,'\n'

# find difference between radial and radius
diff_rad = list()
numcom = 0
for i in range(0,len(sample)):
    normal_radial = float(radlen[i])/float(maxradial)
    compartworad = abs(normal_radial-normradius)
    diff_rad.append(compartworad)

    if compartworad <= 0.01:
        numcom +=1

#print 'difference between radial length and normalized radius:', diff_rad
print 'number of difference between radial length and normalized radius below 0.01:',numcom,'\n'

# find entropy of lesion contour
p = float(numcom)/float(len(sample))
#print p
if p >=1:
    Entropy = -(p*math.log(p))
elif p<=0:
    Entropy = -((1-p)*math.log(1-p))
else:
    Entropy = -(p*math.log(p)+(1-p)*math.log(1-p))
print 'entropy:',Entropy

# find ratio of minimum to maximum radial length
radialratio = float(minradial)/float(maxradial)
realradialratio = 1
print 'real radial ratio:',realradialratio
print 'ratio of minimum to maximum radial length:',radialratio, '\n'

curvature,bendingenergy = BendingEnergy(sample,samplex,sampley)
print 'curvature:',curvature
print 'bending energy:', bendingenergy

numsample = len(sample)
numv = len(v)
print 'number of samples:',numsample
print 'number of vertices:',numv
#show()


# -*- coding: utf-8 -*-
"""
uses the x, y data below to complete the tasks in parts 1-6 below.

1. Fit a linear curve to the data
2. Fit a cubic curve using the SciPy library
3. Find the area underneath the cubic curve over the domain of the data using the tools in SciPy
4. Plot the data, the linear fit, and the cubic fit in Matplotlib. Make sure to give the plot a title and an x and y label. Save this figure.
5. Put the area of the curve on the plot somewhere as text using Matplotlib
6. Use the Bayesian information criterion to justify which model (linear or cubic) is preferable. Include this justification in your journal entry for the week.

Created on Mon Jul 23 16:54:15 2018

@author: Sam Li
"""

import matplotlib.pyplot as plt
import numpy as np
from scipy.integrate import quad

def BIC(y, yhat, k, weight = 1):
    err = y - yhat
    sigma = np.std(np.real(err))
    n = len(y)
    B = n*np.log(sigma**2) + weight*k*np.log(n)
    return B

x = [ 1.,   1.5,  2.,   2.5,  3.,   3.5,  4.,   4.5,  5.,   5.5,  6.,   6.5,  7.,   7.5, 8.,   8.5,  9.,   9.5, 10. ]
y = [3.43, 4.94, 6.45, 9.22, 6.32, 6.11, 4.63, 8.95, 7.8, 8.35, 11.45, 14.71, 11.97, 12.46, 17.42, 17.0, 15.45, 19.15, 20.86]

plt.figure()
plt.scatter(x, y)

coefficients_lin = np.polyfit(x, y, 1)
y_lin = np.poly1d(coefficients_lin)

coefficients_cubic = np.polyfit(x, y, 3)
y_cubic = np.poly1d(coefficients_cubic)

area = quad(y_cubic, np.min(x), np.max(x))

plt.plot(x, y_lin(x), 'r-', label='Linear')
plt.plot(x, y_cubic(x), 'g-', label='Cubic, Area = ' + str(area[0]))
plt.fill_between(x, y_cubic(x), alpha='0.2', color='green')
plt.xlabel('X')
plt.ylabel('Y')
plt.legend()
plt.title('Linear and Cubic Fit')
plt.savefig('SamLi_01.png')

lin_bic = BIC(y,y_lin(x),2)
cub_bic = BIC(y, y_cubic(x), 4)
print('Linear BIC = ', lin_bic)
print('Cubic BIC = ', cub_bic)


from sympy import S, zeros, Matrix, nsimplify, factorial, maximum, Interval, symbols, Function, ones, Integral, solve, legendre, chebyshevt
from ..genericas import matriz_inversa

import numpy as np

def polinomio_lagrange_integracion(f, x_vals=None, grado=3, var=symbols('x'), I=[0, 1]):
    """Aplica el método de lagrange para integrar una función.

    Args:
        f (funcion): Función a integrar.
        x_vals (list, optional): Lista de nodos donde se evaluará el polinomio. Si None, selecciona tantos nodos como grado, de manera equidistante. Defaults to None.
        grado (int, optional): Número de nodos. Defaults to 3.
        var (variable, optional): Variable de integración. Defaults to symbols('x').
        I (list, optional): Intervalo de integración. Defaults to [0, 1].

    Returns:
        p (polinomio): Polinomio de integración
        val (float): Valor aproximado de la integral, tras la evaluación de p en los nodos.
    """

    ff = Function('f')

    if x_vals is None:
        x_vals = np.linspace(I[0], I[1], grado)

    G = ones(len(x_vals), len(x_vals))
    for row in range(1, len(x_vals)):
        for col in range(len(x_vals)):
            G[row, col] = x_vals[col] ** row

    x_mat = nsimplify(Matrix([(I[1] ** (i + 1) - I[0] ** (i + 1)) / (i + 1) for i in range(len(x_vals))]), rational=True)

    # Con esto creamos el vector de valores de A
    a = nsimplify(matriz_inversa(G) * x_mat, rational=True)

    p, val = S(0), S(0)
    for i in range(len(x_vals)):
        p += a[i] * ff(x_vals[i])
        val += a[i] * f.subs(var, x_vals[i])

    return p, val

def polinomio_newton_integracion(f, x_vals=None, grado=3, var=symbols('x'), I=[0, 1]):
    """Aplica el método de lagrange para integrar una función usando las funciones w de Newton.

    Args:
        f (funcion): Función a integrar.
        x_vals (list, optional): Lista de nodos donde se evaluará el polinomio. Si None, selecciona tantos nodos como grado, de manera equidistante. Defaults to None.
        grado (int, optional): Número de nodos. Defaults to 3.
        var (variable, optional): Variable de integración. Defaults to symbols('x').
        I (list, optional): Intervalo de integración. Defaults to [0, 1].

    Returns:
        p (polinomio): Polinomio de integración
        val (float): Valor aproximado de la integral, tras la evaluación de p en los nodos.
    """

    ff = Function('f')

    if x_vals is None:
        x_vals = np.linspace(I[0], I[1], grado)

    # Creamos la lista de omegas, que tienen la expresión [w_1, w_2, w_3, etc]
    lista_w = [1, (var - x_vals[0])]
    for x_val in x_vals[1:]:
        lista_w.append(lista_w[-1] * (var - x_val))

    G = zeros(len(x_vals), len(x_vals))
    x_mat = zeros(len(x_vals), 1)
    for row in range(len(x_vals)):
        for col in range(row, len(x_vals)):
            if row == 0:
                G[row, col] = 1
            else:
                G[row, col] = lista_w[row].subs(var, x_vals[col])

        x_mat[row, 0] = Integral(lista_w[row], (var, I[0], I[1])).doit()

    # Con esto creamos el vector de valores de A
    a = matriz_inversa(G) * x_mat

    p, val = S(0), S(0)
    for i in range(len(x_vals)):
        p += a[i] * ff(x_vals[i])
        val += a[i] * f.subs(var, x_vals[i])

    return p, val

def integral_positiva_newton(omega, I, var, x_vals):
    # Esta función la creo para que calcule la integral del valor absoluto. Como las integrales son de la forma (x-x0)(x-x1)(x-x2)(x-x3)...
    # con xi diferentes y en orden creciente, la función va a tomar un valor negativo si el número de términos es impar, y positivo si par
    # para a < x_0, y de ahí va a ir alternando signos. Como pueden surgir casos diferentes (que I[0] sea x_vals[0]) y la casuistica cambia
    # para cada x_i, x_i+1 tomaremos la integral y evaluaremos el signo, para ponerlo a la integral de nuevo.
    integral = S(0)

    x_vals_I = [I[0]] + x_vals + [I[1]]
    for i in range(len(x_vals_I) - 1):
        a, b = x_vals_I[i], x_vals_I[i + 1]
        integral_i = Integral(omega, (var, a, b)).doit()

        if integral_i > 0:
            signo = 1
        elif integral_i < 0:
            signo = -1
        else:  # a = b y la integral es cero
            continue
        integral += signo * integral_i

    return integral

def error_cuadratura_integral(f, var=symbols('x'), x_vals=None, I=[0, 1], grado=2):
    """Calcula el error máximo de cuadratura de integral en base al polinomio de aproximación

    Args:
        f (funcion): Función a integrar.
        var (variable, optional): Variable de integración. Defaults to symbols('x').
        x_vals (list, optional): Lista de nodos donde se evaluará el polinomio. Si None, selecciona tantos nodos como grado, de manera equidistante. Defaults to None.
        I (list, optional): Intervalo de integración. Defaults to [0, 1].
        grado (int, optional): Número de nodos. Defaults to 3.

    Returns:
        e (float): Error de aproximación
        max_diff_f: Valor de máximo de diferencia (entre el factorial)
        int_omega: Valor de la integral de Newton
    """
    if x_vals is None:
        x_vals = np.linspace(I[0], I[1], grado)

    # Hallamos el primer término, el del máximo de la derivada
    diff_f = f
    for _ in range(len(x_vals)):
        diff_f = diff_f.diff(var)

    max_diff_f = nsimplify(maximum(diff_f, var, Interval(I[0], I[1])) / factorial(len(x_vals)), rational=True)

    # Ahora hallamos el de la integral
    omega = S(1)
    for x_val in x_vals:
        omega *= (var - x_val)

    int_omega = nsimplify(integral_positiva_newton(omega, I, var, x_vals), rational=True)

    e = max_diff_f * int_omega

    return e, max_diff_f, int_omega

def formulas_newton_cotes(f, orden=1, x_vals=None, y_vals=None, I=[0, 1], var=symbols('x')):
    """Calcula las fórmulas de Newton-Cotes para la resolución de la integral.

    Args:
        f (funcion): Función a integrar.
        orden (int): Orden de las fórmulas de Newton-Cotes.
        x_vals (list, optional): Lista de nodos donde se evaluará el polinomio. Si None, selecciona tantos nodos como grado, de manera equidistante. Defaults to None.
        y_vals (list, optional): Lista de valores de la función. Emplear si no hay una función dada.
        I (list, optional): Intervalo de integración. Defaults to [0, 1].
        var (variable, optional): Variable de integración. Defaults to symbols('x').

    Returns:
        Q (polinomio): Fórmula de Newton-Cotes simbólica.
        val (float): Evaluación de la fórmula de Newton-Cotes en los nodos de evaluación.
    """

    ff = Function('f')

    if x_vals is None:
        x_vals = np.linspace(I[0], I[1], orden + 1)
    if y_vals is None:
        y_vals = [f.subs(var, i) for i in x_vals]

    G = ones(len(x_vals), len(x_vals))
    for row in range(1, len(x_vals)):
        for col in range(len(x_vals)):
            G[row, col] = x_vals[col] ** row

    x_mat = nsimplify(Matrix([(I[1] ** (i + 1) - I[0] ** (i + 1)) / (i + 1) for i in range(len(x_vals))]), rational=True)

    # Con esto creamos el vector de valores de A
    a = nsimplify(matriz_inversa(G) * x_mat, rational=True)

    Q, val = S(0), S(0)
    for i in range(len(x_vals)):
        Q += a[i] * ff(nsimplify(x_vals[i], rational=True))
        val += nsimplify(a[i] * f.subs(var, x_vals[i]), rational=True)

    return Q, val

def error_trapecio(f, var=symbols('x'), I=[0, 1]):
    """Calcula el error máximo de cuadratura de integral usando las fórmulas de Newton-Cotes para el trapecio
    Args:
        f (funcion): Función a integrar.
        var (variable, optional): Variable de integración. Defaults to symbols('x').
        I (list, optional): Intervalo de integración. Defaults to [0, 1].

    Returns:
        e (float): Error de aproximación
        max_diff_f: Valor de máximo de diferencia (entre el factorial)
        int_omega: Valor de la integral de Newton
    """

    diff_f = f
    for _ in range(2):
        diff_f = diff_f.diff(var)

    max_diff_f = nsimplify(maximum(diff_f, var, Interval(I[0], I[1])), rational=True)

    factor_division = (I[1] - I[0]) ** 3 / 12

    e = max_diff_f * factor_division

    return e, max_diff_f, factor_division

def error_simpson(f, var=symbols('x'), I=[0, 1]):
    """Calcula el error máximo de cuadratura de integral usando las fórmulas de Newton-Cotes para Simpson
    Args:
        f (funcion): Función a integrar.
        var (variable, optional): Variable de integración. Defaults to symbols('x').
        I (list, optional): Intervalo de integración. Defaults to [0, 1].

    Returns:
        e (float): Error de aproximación
        max_diff_f: Valor de máximo de diferencia (entre el factorial)
        int_omega: Valor de la integral de Newton
    """

    diff_f = f
    for _ in range(4):
        diff_f = diff_f.diff(var)

    max_diff_f = nsimplify(maximum(diff_f, var, Interval(I[0], I[1])), rational=True)

    factor_division = ((I[1] - I[0]) / 2) ** 5 / 90

    e = max_diff_f * factor_division

    return e, max_diff_f, factor_division

def formulas_newton_cotes_m(f, orden=1, m=10, I=[0, 1], var=symbols('x')):
    """Calcula las fórmulas de Newton-Cotes para la resolución de la integral usando m particiones del intervalo.

    Args:
        f (funcion): Función a integrar.
        orden (int): Orden de las fórmulas de Newton-Cotes.
        m (int): Número de particiones del intervalo. Si m=1 la funcion es idéntica a formulas_newton_cotes
        I (list, optional): Intervalo de integración. Defaults to [0, 1].
        var (variable, optional): Variable de integración. Defaults to symbols('x').

    Returns:
        Q (polinomio): Fórmula de Newton-Cotes simbólica.
        val (float): Evaluación de la fórmula de Newton-Cotes en los nodos de evaluación.
    """

    lista_vals, lista_Q = [], []
    lista_m = np.linspace(I[0], I[1], m + 1)

    for m_i in range(m):
        m0, mf = lista_m[m_i], lista_m[m_i + 1]
        Q, val = formulas_newton_cotes(f, orden=orden, I=[m0, mf], var=var)
        lista_Q.append(Q)
        lista_vals.append(val)

    Q = sum(lista_Q)
    val = sum(lista_vals)

    return Q, val

def error_trapecio_m(f, var=symbols('x'), I=[0, 1], m=2):
    """Calcula el error máximo de cuadratura de integral usando las fórmulas de Newton-Cotes para el trapecio
    Args:
        f (funcion): Función a integrar.
        var (variable, optional): Variable de integración. Defaults to symbols('x').
        I (list, optional): Intervalo de integración. Defaults to [0, 1].
    m (int): Número de particiones del intervalo. Si m=1 la funcion es idéntica a error_trapecio
    Returns:
        e (float): Error de aproximación
        max_diff_f: Valor de máximo de diferencia (entre el factorial)
        int_omega: Valor de la integral de Newton
    """
    diff_f = f
    for _ in range(2):
        diff_f = diff_f.diff(var)

    a, b = I  # Me da fallo random si no hago esto!
    max_diff_f = nsimplify(maximum(diff_f, var, Interval(a, b)), rational=True)

    factor_division = (I[1] - I[0]) ** 3 / (12 * (m ** 2))

    e = max_diff_f * factor_division

    return e, max_diff_f, factor_division

def error_simpson_m(f, var=symbols('x'), I=[0, 1], m=2):
    """Calcula el error máximo de cuadratura de integral usando las fórmulas de Newton-Cotes para Simpson
    Args:
        f (funcion): Función a integrar.
        var (variable, optional): Variable de integración. Defaults to symbols('x').
        I (list, optional): Intervalo de integración. Defaults to [0, 1].
    m (int): Número de particiones del intervalo. Si m=1 la funcion es idéntica a error_trapecio
    Returns:
        e (float): Error de aproximación
        max_diff_f: Valor de máximo de diferencia (entre el factorial)
        int_omega: Valor de la integral de Newton
    """
    diff_f = f
    for _ in range(4):
        diff_f = diff_f.diff(var)

    a, b = I  # Me da fallo random si no hago esto!
    max_diff_f = nsimplify(maximum(diff_f, var, Interval(a, b)), rational=True)

    factor_division = (I[1] - I[0]) ** 5 / (90 * (m ** 4) * (2 ** 5))

    e = max_diff_f * factor_division

    return e, max_diff_f, factor_division

def formulas_gauss(f, I=[-1, 1], var=symbols('x'), grado=3, modo='legendre'):
    """Calcula las fórmulas de Newton-Cotes para la resolución de la integral nodos de Chebyshev o Legendre.

    Args:
        f (funcion): Función a integrar.
        I (list, optional): Intervalo de integración. Defaults to [0, 1].
        var (variable, optional): Variable de integración. Defaults to symbols('x').
        grado (int): Grado del polinomio de Legendre o Chebyshev

    Returns:
        Q (polinomio): Fórmula de Gauss simbólica.
        val (float): Evaluación de la fórmula de Gauss en los nodos de evaluación.
    """

    ff = Function('f')

    x_vals = []
    if modo == 'legendre':
        x_vals = sorted(solve(legendre(grado, var)))
    elif modo == 'chebyshev':
        x_vals = sorted(solve(chebyshevt(grado, var)))

    G = ones(len(x_vals), len(x_vals))
    for row in range(1, len(x_vals)):
        for col in range(len(x_vals)):
            G[row, col] = x_vals[col] ** row

    x_mat = nsimplify(Matrix([((1) ** (i + 1) - (-1) ** (i + 1)) / (i + 1) for i in range(len(x_vals))]), rational=True)

    # Con esto creamos el vector de valores de A
    a = nsimplify((I[1] - I[0]) / 2 * (matriz_inversa(G) * x_mat), rational=True)

    # Aquí efectuamos el cambio de variable:
    cambio_var = (I[1] - I[0]) / 2 * var + (I[1] + I[0]) / 2
    Q, val = S(0), S(0)
    for i in range(len(x_vals)):
        Q += a[i] * ff(nsimplify(cambio_var.subs(var, x_vals[i]), rational=True))
        val += nsimplify(a[i] * f.subs(var, cambio_var.subs(var, x_vals[i])), rational=True)

    return Q, val


# Gradient Descent, Newton Descent, Conjugate Descent
import numpy as np
from typing import *

class GradientDescent:
    def __init__(self, func: Callable, grad: Callable, lr: float, num_iters: int, threshold: float = 0.):
        """
        Initialize gradient descent.
        :param func: Function to minimize, should accept an np.ndarray and return a scalar.
        :param grad: Function that returns the gradient of func, should accept an np.ndarray and return an np.ndarray.
        :param lr: Learning rate.
        :param num_iters: Number of iterations to perform.
        :param threshold: Threshold for convergence and early stopping.
        """
        assert callable(func)
        assert callable(grad)
        assert lr > 0
        assert num_iters >= 0
        assert threshold >= 0
        self.f = func
        self.g = grad
        self.lr = lr
        self.num_iters = num_iters
        self.threshold = threshold

        # Data for plotting
        self.parameters = []
        self.function_values = []

    def optimize(self, init: np.ndarray):
        """
        Run gradient descent.
        :param init: Initial value for starting gradient descent. Should be able to be called with func and grad.
        """
        x = init
        prev_f_x = None
        for _ in range(self.num_iters):
            self.parameters.append(x)

            # Get the function value
            f_x = self.f(x)
            self.function_values.append(f_x)

            # Check for convergence
            if prev_f_x is not None and np.linalg.norm(prev_f_x - x) < self.threshold:
                break

            # Make a step towards the optimal solution
            x -= self.lr * self.g(x)

            # Prepare for the next iteration
            prev_f_x = f_x

class NewtonDescent:
    def __init__(self, func: Callable, grad: Callable, hess: Callable, num_iters: int, threshold: float = 0.):
        """
        Initialize newton descent.
        :param func: Function to minimize, should accept an np.ndarray and return a scalar.
        :param grad: Function that returns the gradient of func, should accept an np.ndarray and return an np.ndarray.
        :param hess: Function that returns the hessian of func, should accept an np.ndarray and return an np.ndarray.
        :param num_iters: Number of iterations to perform.
        :param threshold: Threshold for convergence and early stopping.
        """
        assert callable(func)
        assert callable(grad)
        assert callable(hess)
        assert num_iters >= 0
        assert threshold >= 0
        self.f = func
        self.g = grad
        self.h = hess
        self.num_iters = num_iters
        self.threshold = threshold

        # Data for plotting
        self.parameters = []
        self.function_values = []

    def optimize(self, init: np.ndarray):
        """
        Run newton descent.
        :param init: Initial value for starting newton descent. Should be able to be called with func, grad, and hess.
        """
        x = init
        prev_f_x = None
        for _ in range(self.num_iters):
            self.parameters.append(x)

            # Get the function value
            f_x = self.f(x)
            self.function_values.append(f_x)

            # Check for convergence
            if prev_f_x is not None and np.linalg.norm(prev_f_x - x) < self.threshold:
                break

            # Make a step towards the optimal solution
            x -= np.linalg.inv(self.h(x)) @ self.g(x)

            # Prepare for the next iteration
            prev_f_x = f_x

class ConjugateGradientDescent:
    def __init__(self, A: np.ndarray, b: np.ndarray, loss: Callable, lr: float, num_iters: int, threshold: float = 0.):
        """
        Initialize conjugate gradient descent, which solves the problem Ax = b.
        Implementation based on https://www.cs.cmu.edu/~quake-papers/painless-conjugate-gradient.pdf
        :param A: A, must be symmetric positive-definite.
        :param b: b
        :param loss: Optional function that converts A and b into a loss value. Called as func(A, x, b).
        :param lr: Learning rate.
        :param num_iters: Number of iterations to perform.
        :param threshold: Threshold for convergence and early stopping.
        """
        assert A == A.T  # Symmetric
        assert all(np.linalg.eigvals(A) > 0)  # PSD
        assert callable(loss)
        assert lr > 0
        assert num_iters >= 0
        assert threshold >= 0
        self.A = A
        self.b = b
        self.loss = loss
        self.lr = lr
        self.num_iters = num_iters
        self.epsilon = threshold

        # Data for plotting
        self.parameters = []
        self.loss_values = []

    def optimize(self, init: np.ndarray):
        """
        Run conjugate gradient descent.
        :param init: Initial value for starting conjugate gradient descent. Should be able to be called with func and
            grad.
        """
        x = init
        r = self.b - (self.A @ x)
        d = r
        delta_new = r.T @ r
        delta_0 = delta_new
        for i in range(self.num_iters):
            # Get the loss value
            self.parameters.append(x)
            loss = self.loss(self.A, x, self.b)
            self.loss_values.append(loss)

            # Check for convergence
            if delta_new < (self.epsilon ** 2) * delta_0:
                break

            # Make a step towards the optimal solution
            q = self.A @ d
            alpha = delta_new / (d.T @ q)
            x = x + (alpha * d)

            # Refresh r to avoid floating point error
            if i % 50 == 0:
                r = self.b - (self.A @ x)
            else:
                r = r - (alpha * q)

            # Prepare for the next iteration
            delta_old = delta_new
            delta_new = r.T @ r
            beta = delta_new / delta_old
            d = r + (beta * d)


import numpy as np

def sum_squared_error(y, t):
    return 0.5 * np.sum((y - t) ** 2)

if __name__ == "__main__":
    t = 2
    t_hot = np.zeros(10)
    t_hot[t] = 1
    y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
    y = np.array(y)
    print(sum_squared_error(y, t_hot))
    y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
    y = np.array(y)
    print(sum_squared_error(y, t_hot))


import numpy as np
def featureMap(x1, x2, m):
    degree = 6
    out = np.ones((m, 1))

    # Example for instance of features for only power of 3
    # x1^3, x1^2 * x2, x1 * x2^2, x2^3
    # We can see that powers x1 decreases from 3 to 0,
    # powers of x2 increases from 0 to 3
    # So first loop increases from 1 to degree
    # second loop increases from 0 to counter of first loop
    for i in range(1, degree+1):
        for j in range(i+1):
            new_feature_vec = (x1 ** (i-j)) * (x2 ** j)
            out = np.concatenate((out, new_feature_vec), axis=1)

    return out


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

def mandelbrot(x, y, threshold):

    # initial conditions
    c = complex(x, y)
    z = complex(0, 0)

    for i in range(threshold):
        z = z**2 + c
        if abs(z) > 4.:  # it diverged
            return i

    return threshold - 1  # it didn't diverge

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

x_start, y_start = -2, -1.5  # an interesting region starts here
width, height = 3, 3  # for 3 units up and right
density_per_unit = 250  # how many pixles per unit

# real and imaginary axis
re = np.linspace(x_start, x_start + width, width * density_per_unit )
im = np.linspace(y_start, y_start + height, height * density_per_unit)

fig = plt.figure(figsize=(10, 10))  # instantiate a figure to draw
ax = plt.axes()  # create an axes object

def animate(i):
    ax.clear()  # clear axes object
    ax.set_xticks([], [])  # clear x-axis ticks
    ax.set_yticks([], [])  # clear y-axis ticks

    X = np.empty((len(re), len(im)))  # re-initialize the array-like image
    threshold = round(1.15**(i + 1))  # calculate the current threshold

    # iterations for the current threshold
    for i in range(len(re)):
        for j in range(len(im)):
            X[i, j] = mandelbrot(re[i], im[j], threshold)

    # associate colors to the iterations with an iterpolation
    img = ax.imshow(X.T, interpolation="bicubic", cmap='magma')
    return [img]

anim = animation.FuncAnimation(fig, animate, frames=45, interval=120, blit=True)
anim.save('mandelbrot.gif',writer='imagemagick')


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Feb  5 12:34:29 2021

@author: xiongn
"""

import numpy as np
import matplotlib.pyplot as plt

class ProbabilityModel:

    # Returns a single sample (independent of values returned on previous calls).
    # The returned value is an element of the model's sample space.
    def sample(self):
        pass

# The sample space of this probability model is the set of real numbers, and
# the probability measure is defined by the density function
# p(x) = 1/(sigma * (2*pi)^(1/2)) * exp(-(x-mu)^2/2*sigma^2)
class UnivariateNormal(ProbabilityModel):

    # Initializes a univariate normal probability model object
    # parameterized by mu and (a positive) sigma
    def __init__(self,mu,sigma):
        self.mu = mu
        self.sigma = sigma

    def sample(self):
        u1 = np.random.uniform(0, 1)
        u2 = np.random.uniform(0, 1)
        z = self.mu + np.sqrt(-2 * np.log(u1)) * np.cos(2 * np.pi * u2) * self.sigma
        return z

    def plot_his(self):
        z = []
        for i in range(5000):
            z.append(self.sample())
        plt.hist(z)
        plt.show()

# The sample space of this probability model is the set of D dimensional real
# column vectors (modeled as numpy.array of size D x 1), and the probability
# measure is defined by the density function
# p(x) = 1/(det(Sigma)^(1/2) * (2*pi)^(D/2)) * exp( -(1/2) * (x-mu)^T * Sigma^-1 * (x-mu) )
class MultiVariateNormal(ProbabilityModel):

    # Initializes a multivariate normal probability model object
    # parameterized by Mu (numpy.array of size D x 1) expectation vector
    # and symmetric positive definite covariance Sigma (numpy.array of size D x D)
    def __init__(self,Mu,Sigma):
        self.Mu = Mu
        self.Sigma = Sigma

    def sample(self):
        L = np.linalg.cholesky(self.Sigma)
        u = []
        for i in range(self.Mu.size):
            u.append(UnivariateNormal(0, 1).sample())
        z = self.Mu + np.dot(L, u)
        return z

    def plot_his(self):
        x = []
        y = []
        for i in range(5000):
            z = self.sample()
            x.append(z[0])
            y.append(z[1])
        plt.scatter(x, y)
        plt.show()

# The sample space of this probability model is the finite discrete set {0..k-1}, and
# the probability measure is defined by the atomic probabilities
# P(i) = ap[i]
class Categorical(ProbabilityModel):

    # Initializes a categorical (a.k.a. multinom, multinoulli, finite discrete)
    # probability model object with distribution parameterized by the atomic probabilities vector
    # ap (numpy.array of size k).
    def __init__(self,ap):
        self.ap = ap

    def sample(self):
        cum = np.cumsum(self.ap)
        u = np.random.uniform(0, 1)
        for i in range(len(self.ap)):
            if u < cum[i]:
                return i

    def plot_his(self):
        z = []
        for i in range(5000):
            z.append(self.sample())
        plt.hist(z)
        plt.show()

# The sample space of this probability model is the union of the sample spaces of
# the underlying probability models, and the probability measure is defined by
# the atomic probability vector and the densities of the supplied probability models
# p(x) = sum ad[i] p_i(x)
class MixtureModel(ProbabilityModel):

    # Initializes a mixture-model object parameterized by the
    # atomic probabilities vector ap (numpy.array of size k) and by the tuple of
    # probability models pm
    def __init__(self,ap,pm):
        self.ap = ap
        self.pm = pm

    def sample(self):
        rand = np.random.uniform(0, 1);
        pts = self.pm[int(rand*len(self.ap))].sample();
        return pts;

    def plot(self):
        x = []
        y = []
        for i in range(5000):
            z = self.sample()
            x.append(z[0])
            y.append(z[1])
        plt.scatter(x, y)
        plt.show()

if __name__ == '__main__':
    U = UnivariateNormal(1, 1)
    U.plot_his()
    C = Categorical(np.array([0.1, 0.1, 0.3, 0.3, 0.2]))
    C.plot_his()
    M = MultiVariateNormal(np.array([1, 1]), np.array([[1., 0.5], [0.5, 1.]]))
    M.plot_his()
    cov = np.array([[1., 0], [0, 1.]])
    pm = [MultiVariateNormal(np.array([1, 1]), cov),MultiVariateNormal(np.array([-1, 1]), cov),MultiVariateNormal(np.array([1, -1]), cov),
          MultiVariateNormal(np.array([-1, -1]), cov)]
    pm=tuple(pm)
    Mix = MixtureModel(np.array([.25, .25, .25, .25]), pm)
    Mix.plot()


import numpy as np
from matplotlib import pyplot as plt

def cost(x, theta, y):
    return 1/(2*len(x)) * np.sum((np.dot(x, theta) - y) ** 2)

def gradient_descent(x, theta, y, alpha, iterations):
    j_vals = np.zeros((iterations, 1))
    new_theta = np.copy(theta)

    for i in range(0, iterations):
        new_theta -= alpha/x.shape[0] * np.dot(x.T, (np.dot(x, new_theta) - y))
        j_vals[i] = cost(x, new_theta, y)

    return new_theta, j_vals

def predict(x, theta):
    return np.dot(x, theta)[0, 0]

def normalize(x):
    return (x - x.mean())/x.std(), x.mean(), x.std()

def plot_cost(j_vals):
    print('Displaying cost history...')
    plt.plot(j_vals)
    plt.title('Cost History over 1500 iterations of GD')
    plt.legend('J')
    plt.xlabel('N° Iterations')
    plt.ylabel('Cost')
    plt.show()


# Constants for project 1
from numpy import array

FLOYD = 1/16*array([[0,0,7],
                    [3,5,1]])

STEVENSON = 1/200*array([[0 , 0, 0, 0, 0,32, 0],
                         [12, 0,26, 0,30, 0,16],
                         [0 ,12, 0,26, 0,12, 0],
                         [5 , 0,12, 0,12, 0, 5]])

BURKES = 1/32*array([[0,0,0,8,4],
                     [2,4,8,4,2]])

SIERRA = 1/32*array([[0,0,0,5,3],
                     [2,4,5,4,2],
                     [0,2,3,2,0]])

STUCKI = 1/42*array([[0,0,0,8,4],
                     [2,4,8,4,2],
                     [1,2,4,2,1]])

JARVIS = 1/48*array([[0,0,0,7,5],
                     [3,5,7,5,3],
                     [1,3,5,3,1]])

DISTS = [FLOYD,STEVENSON,BURKES,SIERRA,STUCKI,JARVIS]
NAMES = ['FLOYD','STEVENSON','BURKES','SIERRA','STUCKI','JARVIS']

def select_dist(name):
    dist = DISTS if name.lower()=='all' else [DISTS[NAMES.index(name.upper())]]
    return dist


import numpy as np
import math
class Model:
    def __init__(self,
            shape,
            activation_function,
            loss_function):

        self.shape = shape
        self.act_func = activation_function
        self.loss_func = loss_function

        self.layer = []
        for i in shape:
            self.layer.append( np.zeros(i)
                    .astype(np.longdouble) )
        self.layer = np.array(self.layer, dtype=object)

        self.weight = []
        self.bias = []
        for i in range(len(shape) - 1):
            self.weight.append( (np.random.random_sample(
                (shape[i], shape[i+1]) )*2 -1)
                .astype(np.longdouble) )

            self.bias.append((
                np.random.random_sample((shape[i+1]))*2 -1)
                .astype(np.longdouble))
        self.weight = np.array(self.weight, dtype=object)
        self.bias = np.array(self.bias, dtype=object)

    def print(self): #just a function to print the model's parameters in a better form
        for i in range(self.weight.shape[0]):
            print(self.weight[i], " ", self.bias[i], "\n")
        for i in self.layer:
            print(i)

    def forward(self, input): #(forward propagation)
        self.layer[0] = np.array(input, dtype=np.longdouble)
        self.layer[0] /= np.max(self.layer[0]) + 1e-08 #TODO
        last_index = 0
        for i in range(self.weight.shape[0]):
            last_index = i+1
            self.layer[i+1]= np.dot(self.layer[i], self.weight[i]) + self.bias[i]
            self.layer[i+1]= self.act_func[i](self.layer[i+1])
        return self.layer[last_index]

    def loss(self, input, target): #model built in loss function instead of using otim's loss function
        target = np.array(target, dtype=np.longdouble)
        return self.loss_func( self.forward(input), target, Derivative=False)


# Script to calculate slope using SciPy.

import os
import numpy as np
import scipy.ndimage
from osgeo import gdal
import ospybook as pb

in_fn = r"D:\osgeopy-data\Nepal\everest_utm.tif"
out_fn = r'D:\Temp\everest_slope_scipy2.tif'

# Write a function that calculates slope using a 3x3 window.
# This will be passed to the SciPy filter function below.
def slope(data, cell_width, cell_height):
    """Calculates slope using a 3x3 window.

    data        - 1D array containing the 9 pixel values, starting
                  in the upper left and going left to right and down
    cell_width  - pixel width in the same units as the data
    cell_height - pixel height in the same units as the data
    """
    rise = ((data[6] + (2 * data[7]) + data[8]) -
            (data[0] + (2 * data[1]) + data[2])) / \
           (8 * cell_height)
    run =  ((data[2] + (2 * data[5]) + data[8]) -
            (data[0] + (2 * data[3]) + data[6])) / \
           (8 * cell_width)
    dist = np.sqrt(np.square(rise) + np.square(run))
    return np.arctan(dist) * 180 / np.pi

# Read the data as floating point.
in_ds = gdal.Open(in_fn)
in_band = in_ds.GetRasterBand(1)
in_data = in_band.ReadAsArray().astype(np.float32)

cell_width = in_ds.GetGeoTransform()[1]
cell_height = in_ds.GetGeoTransform()[5]

# Pass your slope function, along with the pixel size, to the
# generic_filter function. This will apply your function to
# each window, and each time it will pass the extra arguments
# in to the function along with the data.
out_data = scipy.ndimage.filters.generic_filter(
    in_data, slope, size=3, mode='nearest',
    extra_arguments=(cell_width, cell_height))

# Save the output
pb.make_raster(in_ds, out_fn, out_data, gdal.GDT_Float32)
del in_ds


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

def calc_rotation_matrix_2d(theta):
    cos_theta = np.cos(theta)
    sin_theta = np.sin(theta)

    a11 = cos_theta
    a12 = -sin_theta
    a21 = sin_theta
    a22 = cos_theta

    return np.array([[a11, a12], [a21, a22]])

def calculate_angle_between_vectors(a, b):
    # Ref
    outer_cross = np.cross(a, b)
    dot_product = np.dot(a, b)
    theta = np.arctan2(outer_cross, dot_product)
    return theta

def calc_rotation_matrix(axis, theta):
        """
        axis: norm 1 rotation of axis vector
        theta: 0 to 2*pie rotation radian
        """
        axis = np.asarray(axis)
        axis = axis / np.linalg.norm(axis)
        n1, n2, n3 = axis

        cos_theta = np.cos(theta)
        sin_theta = np.sin(theta)

        a11 = cos_theta + n1*n1*(1 - cos_theta)
        a12 = n1*n2*(1 - cos_theta) - n3*sin_theta
        a13 = n1*n3*(1 - cos_theta) + n2*sin_theta
        a21 = n2*n1 * (1 - cos_theta) + n3*sin_theta
        a22 = cos_theta + n2*n2 * (1 - cos_theta)
        a23 = n2*n3*(1 - cos_theta) - n1*sin_theta
        a31 = n3*n1*(1 - cos_theta) - n2*sin_theta
        a32 = n3*n2*(1 - cos_theta) + n1*sin_theta
        a33 = cos_theta + n3*n3*(1 - cos_theta)

        return np.array([[a11, a12, a13], [a21, a22, a23], [a31, a32, a33]])

def rotate_vector_by_degree(axis, vector, degree):
    # this only works on 3D vector
    theta = np.deg2rad(degree)
    rot_mat = calc_rotation_matrix(axis, theta)
    translated_vector = np.matmul(rot_mat, vector)
    return translated_vector

def transform_axis_pointcloud(pointcloud, x_axis, y_axis, z_axis):

    mat = np.array([[x_axis[0], y_axis[0], z_axis[0]],
                    [x_axis[1], y_axis[1], z_axis[1]],
                    [x_axis[2], y_axis[2], z_axis[2]]])

    mat_inv = np.linalg.pinv(mat)
    pointcloud_transformed = np.matmul(mat_inv, pointcloud.T).T
    return pointcloud_transformed

def calculate_radian(vector1, vector2):
    dot = np.dot(vector1, vector2)
    norm = np.linalg.norm(vector1) * np.linalg.norm(vector2)
    cos = dot / norm
    return np.arccos(np.clip(cos, -1.0, 1.0))

def visualize_3D_pointcloud_2D(x, y, skeleton_points, figname, save_dir=None, xlim=None, ylim=None):
    fig, ax = plt.subplots()
    ax.scatter(x, y, color="black", s=1)
    if skeleton_points is not None:
        ax.scatter(skeleton_points[:, 0], skeleton_points[:, 1], color="blue", s=15)

    ax.set_aspect("equal", "datalim")
    if xlim is not None:
        ax.set_xlim(xlim)
    if ylim is not None:
        ax.set_ylim(ylim)

    plt.title(figname)
    if save_dir:
        img_name = figname + ".png"
        plt.savefig(os.path.join(save_dir, img_name))
    else:
        plt.show()
    plt.close()


import matplotlib.pyplot as plt
from math import sqrt
from scipy.stats import norm
import numpy as np

def ud_function(a, b, x):
    return (x - a) / (b - a) if a <= x < b else 0 if x < a else 1

def ud_density(a, b, x):
    return 1 / (b - a) if a <= x <= b else 0

def norm_function(x, mu, sigma):
    return norm.cdf(x, mu, sqrt(sigma))

def norm_density(x, mu, sigma):
    return norm.pdf(x, mu, sqrt(sigma))

def draw_graphics(x, y_function, y_density, name):
    fig, axs = plt.subplots(2, figsize=(6, 7))

    fig.suptitle(name)
    axs[0].plot(x, y_function, color='purple')
    axs[1].plot(x, y_density, color='purple')

    axs[0].set_xlabel('x')
    axs[0].set_ylabel('F(x)')

    axs[1].set_xlabel('x')
    axs[1].set_ylabel('f(x)')

    axs[0].grid(True)
    axs[1].grid(True)
    plt.show()

def main():
    a = float(input("Input a: "))
    b = float(input("Input b: "))
    delta = b - a
    x = np.arange(a - delta / 2, b + delta / 2, 0.001)
    y_function = [ud_function(a, b, _x) for _x in x]
    y_density = [ud_density(a, b, _x) for _x in x]
    draw_graphics(x, y_function, y_density, 'Равномерное распределение')

    mu = float(input("Input mu: "))
    sigma = float(input("Input sigma: "))
    x = np.arange(-10, 10, 0.001)
    y_function = norm_function(x, mu, sigma)
    y_density = norm_density(x, mu, sigma)

    draw_graphics(x, y_function, y_density, 'Нормальное распределение')

if __name__ == '__main__':
    main()


# --------------
# Code starts here

import numpy as np
from scipy.linalg import norm,eig

# Code starts here

# Adjacency matrix
adj_mat = np.array([[0,0,0,0,0,0,1/3,0],
                   [1/2,0,1/2,1/3,0,0,0,0],
                   [1/2,0,0,0,0,0,0,0],
                   [0,1,0,0,0,0,0,0],
                  [0,0,1/2,1/3,0,0,1/3,0],
                   [0,0,0,1/3,1/3,0,0,1/2],
                   [0,0,0,0,1/3,0,0,1/2],
                   [0,0,0,0,1/3,1,1/3,0]])

# Compute eigenvalues and eigencevectrs
eigenvalues,eigenvectors=eig(adj_mat)

# Eigen vector corresponding to 1
eigen_1=abs(eigenvectors[:,0])/norm(eigenvectors[:,0],1)

# most important page
page=np.where(eigen_1==np.amax(eigen_1))[0][0]+1
print(page)

# Code ends here

# --------------
# Code starts here

# Initialize stationary vector I
init_I=np.array([1,0,0,0,0,0,0,0])

# Perform iterations for power method
for i in range(10):
    init_I=np.dot(adj_mat,init_I)/norm(init_I,1)

power_page=np.where(init_I==np.amax(init_I))[0][0]+1
print(power_page)
# Code ends here

# --------------
# Code starts here

# New Adjancency matrix
# New Adjancency matrix
new_adj_mat = np.array([[0,0,0,0,0,0,0,0],
                   [1/2,0,1/2,1/3,0,0,0,0],
                  [1/2,0,0,0,0,0,0,0],
                   [0,1,0,0,0,0,0,0],
                   [0,0,1/2,1/3,0,0,1/2,0],
                   [0,0,0,1/3,1/3,0,0,1/2],
                   [0,0,0,0,1/3,0,0,1/2],
                   [0,0,0,0,1/3,1,1/2,0]])

# Initialize stationary vector I
new_init_I=np.array([1,0,0,0,0,0,0,0])

# Perform iterations for power method
for i in range(10):
    new_init_I=np.dot(new_adj_mat,new_init_I)/norm(new_init_I,1)

print(new_init_I)

# Code ends here

# --------------
# Alpha value
alpha = 0.85

# Code starts here

# Modified adjancency matrix
G=alpha*new_adj_mat + (1-alpha)*(1/len(new_adj_mat))*np.ones(new_adj_mat.shape)
# print(G)

# Initialize stationary vector I
final_init_I=np.array([1,0,0,0,0,0,0,0])

# Perform iterations for power method
for i in range(1000):
    final_init_I=np.dot(G,final_init_I)/norm(final_init_I,1)

print(final_init_I)

# Code ends here


import numpy as np
import math
import random
from itertools import repeat

def randomKmeans(samples, k, TOL, C):
	SS_Previous = 0
	samplesLength = len(samples)
	dim = len(samples[0])

	while 1:
		P = [[] for i in repeat(None, k)]
		# find the closest cluster centroid
		for i in range(samplesLength):
			minIdx = 0
			minVal = eucliDist(samples[i], C[0], dim)
			for j in range(1,k):
				dist = eucliDist(samples[i], C[j], dim)
				if (dist < minVal):
					minIdx = j
					minVal = dist
			# assign the point to the correct cluster
			P[minIdx].append(samples[i])

		# recalculating cluster centroid
		for j in range(k):
			coords = P[j]
			if(len(coords) == 0):
				coords = random.sample(samples, 1)
			zipped = zip(*coords)
			num = len(coords)
			C[j] = [math.fsum(dList)/num for dList in zipped]

		SS_Error = 0

		# calculating total clustering error
		for idx in range(k):
			for p_idx in range(len(P[idx])):
				SS_Error += sqEucliDist(P[idx][p_idx], C[idx], dim)

		# check if no change in SSE
		delta = abs(SS_Error - SS_Previous)

		if (delta < TOL):
			break

		SS_Previous = SS_Error

	return round(SS_Error, 4), C, P

# calculating Euclidean Distance
def eucliDist(sample, center, dim):
	distance = 0
	for x in range(dim):
		distance += pow((sample[x] - center[x]), 2)
	return pow(distance,0.5)

# calculating Squared Euclidean Distance
def sqEucliDist(sample, center, dim):
	sumsq = 0
	for x in range(dim):
		sumsq += pow((sample[x] - center[x]), 2)
	return sumsq


import superimport

import pyprobml_utils as pml
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
import scipy
#import scipy.special as sc
from scipy import stats
from sklearn.neighbors import KernelDensity
import math

S = np.array([[3.1653, -0.0262], [-0.0262, 0.6477]])
dof = 3
Sigma = S
nr = 3
nc = 3
nsamples = 9
Xs1 = np.linspace(0.1, 200, 2000)
Xs2 = np.linspace(0.1, 40, 400)
Xs3 = np.linspace(0.1, 10, 100)
Xsa = {0:Xs1, 1:Xs2}
Xsb = {0:Xs2, 1:Xs3}

def wishart_sample(dof, sigma, nsamples):
  d = np.size(sigma, 0)
  C = np.linalg.cholesky(Sigma)
  S = np.zeros((d, d, nsamples))
  for i in range(nsamples):
      t = np.random.randn(dof, d)
      Z = np.matmul(t, C)
      S[:, :, i] = np.matmul(Z.T, Z)
  return S

def gamma_log_prob(a, b, X):
    # https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.gamma.html
    # a=shape, b=rate=1/scale
  logZ = scipy.special.gammaln(a) - np.multiply(a, np.log(b))
  logp = np.multiply((a-1), np.log(X)) - np.multiply(b, X) - logZ
  return logp

def get_cov_ellipse(cov, centre, nstd):
    eigvals, eigvecs = np.linalg.eigh(cov)

    order = eigvals.argsort()[::-1]
    eigvals, eigvecs = eigvals[order], eigvecs[:, order]

    vx, vy = eigvecs[:,0][0], eigvecs[:,0][1]
    theta = np.arctan2(vy, vx)
    theta = math.degrees(theta)
    width, height = 2 * nstd * np.sqrt(eigvals)

    return width, height, theta

np.random.seed(4)
M = dof*Sigma
R = np.corrcoef(M)
S = wishart_sample(dof, Sigma, nsamples)

# Plots of some samples from Wishart distribution:
fig, ax = plt.subplots(nr, nc, figsize=(10,10))
mu = np.array([0, 0])
j = 0
for r in range(nr):
    for c in range(nc):
      w, h, theta = get_cov_ellipse(S[:, :, j], mu, nstd=3)
      j = j + 1
      ell = mpl.patches.Ellipse(xy=mu, width=w, height=h, angle = theta, ec='black', fc='none') #facecolor='none'
      ax[r, c].add_patch(ell)
      ax[r, c].set_aspect('equal')
      ax[r, c].set_xlim(-25, 25)
      ax[r, c].set_ylim(-25, 25)
      ax[r, c].autoscale()
      ax[r, c].plot(mu[0], mu[1], marker='x')
plt.tight_layout()
fig.suptitle("Wi(dof=%i, S), E=[9.5, -0.1; -0.1, 1.9], rho=-0.018" %dof)
xmin = -17
xmax = 17
ymin = -10
ymax = 10
custom_xlim = (xmin, xmax)
custom_ylim = (ymin, ymax)
plt.setp(ax, xlim=custom_xlim, ylim=custom_ylim)
pml.savefig('wishart_samples.pdf')
plt.show()

marg1a = dof / 2
marg1b = 1/(2*Sigma[0, 0])
marg2a = dof / 2
marg2b = 1/(2*Sigma[1, 1])

#Plots of marginals
logp = gamma_log_prob(marg1a, marg1b, Xs1)
expo = np.exp(logp)
plt.figure()
plt.plot(Xs1, expo)
plt.title("Marginal sigma1_squared ")
pml.savefig('wishart_sigma1.pdf')
plt.show()

logp = gamma_log_prob(marg2a, marg2b, Xs2)
expo = np.exp(logp)
plt.figure()
plt.plot(Xs2, expo)
plt.title("Marginal sigma2_squared ")
pml.savefig('wishart_sigma2.pdf')
plt.show()

# Plot of correlation coefficient
n = 1000
Rs = wishart_sample(dof, Sigma, nsamples)
for s in range(nsamples):
        Rs[:, :, s] = np.corrcoef(Rs[:, :, s])    #cov2cor(Rs(:, :, s));
data = np.squeeze(Rs[0, 1, :])
kde = KernelDensity(bandwidth=1.0, kernel='gaussian')
kde.fit(data.reshape(-1, 1))
x = np.linspace(data.min()-2, data.max()+2, 100)
logprob = kde.score_samples(x[:, None])
plt.figure()
plt.title('Rho')
plt.plot(x, np.exp(logprob))
pml.savefig('wishart_rho.pdf')
plt.show()


import numpy as np

def integer_sequences(L, S, nondecr=False, m=None, M=None):
    """
    Generate sequences of non-negative integers.

    Parameters:
    L:         the length of the sequences
    S:         the sum of the integers in each sequence

    Optional parameters:
    nondecr:   (boolean) return only non-decreasing sequences (default: False)
    m:         tuple of length L; gives lower bounds for coefficients of list (default: None)
    M:         tuple of length L; gives upper bounds for coefficients of list (default: None)
    """

    # If M and m are not given, use the following defaults.
    if M is None:
        M = (S,)*L
    if m is None:
        m = (0,)*L

    # If length=0 and sum=0 then yield the empty tuple.
    # Otherwise, yield nothing.
    if L==0:
        if S==0:
            yield tuple()
    # If length=1 and sum lies within given boundaries, yield 1-tuple.
    elif L==1:
        if m[0]<=S<=M[0]:
            yield (S,)
    # If length>1, then loop through possible values for first coefficient,
    # and recursively generate (L-1)-tuples that give the remaining coefficients.
    elif L>1:
        for first in range(m[0], min(S,M[0])+1):
            m_next = m[1:] if nondecr==False else (first,)+m[2:]
            for tail in integer_sequences(L=L-1, S=S-first, nondecr=nondecr, m=m_next, M=M[1:]):
                yield (first,)+tail

def matrices_from_degree_sequence(deg_seq=tuple()):
    """
    Generate all matrices that can occur as adjacency matrices
    of r-marked graphs whose degree_sequence() equals deg_seq.

    These are symmetric (r×r)-matrices with non-negative integer coefficients
    with even coefficients on the diagonal, whose row sum equals deg_seq.
    """
    L = len(deg_seq)

    M = np.zeros((L,L), dtype=int)
    if L==0:
        yield M
    elif L>0:
        # Range over possible values for top left entries:
        for top_left in range(0,deg_seq[0]+1,2):
            M[0,0] = top_left
            # Range over sequences that make up the rest of the first row:
            for top_row_rest in integer_sequences(L=L-1, S=deg_seq[0]-top_left, M=deg_seq[1:]):
                M[0,1:] = M[1:,0] = top_row_rest
                # Compute the row sum of the remaining bottom right square
                row_sum_rem = tuple(deg_seq[i] - M[0,i] for i in range(1,L))

                # Loop over all possible bottom right squares:
                for BRS in matrices_from_degree_sequence(deg_seq=row_sum_rem):
                    M[1:,1:]=BRS
                    yield M.copy()

def increasing_injections(r,s,m=1):
    """
    Generates all increasing tuples of length r
    with values in {1, ..., s}.

    Optional argument:
    m: minimum value of first entry (default=1, used for recursion)
    """
    if r==0:
        yield tuple()
    elif r==1:
        for i in range(m,s+1):
            yield (i,)
    else:
        for first in range(m,s-r+2):
            for tail in increasing_injections(r-1,s,m=first+1):
                yield (first,)+tail


import numpy as np
import pandas as pd
import scipy.stats
def drawdown(return_series: pd.Series):
    """
    Takes a Time Series of asset returns and computes and returns a dataframe that contains
    1) Wealth Idex
    2) Previous Peaks
    3) Percentage of drawdowns
    """
    wealth_index = 1000*(1+return_series).cumprod()
    previous_peaks = wealth_index.cummax()
    drawdowns = (wealth_index-previous_peaks)/previous_peaks
    return pd.DataFrame(dict(Wealth = wealth_index, Peaks = previous_peaks, Drawdowns = drawdowns))

def get_ffme_returns():
    """
    Load the Farma-French Dataset for the returns of the top and bottom deciles by Market Cap
    """
    me_m = pd.read_csv('Portfolios_Formed_on_ME_monthly_EW.csv', header=0, index_col=0, na_values = -99.99)
    rets = me_m[['Lo 10', 'Hi 10']]
    rets.columns = ['SmallCap', 'LargeCap']
    rets = rets/100
    rets.index = pd.to_datetime(rets.index, format="%Y%m").to_period('M')
    return rets

def get_hfi_returns():
    """
    Load the Edhec Hedge Fund Dataset for the returns of the top and bottom deciles by Market Cap
    """
    hfi = pd.read_csv('edhec-hedgefundindices.csv', header=0, index_col=0, parse_dates=True)
    hfi = hfi/100
    hfi.index = hfi.index.to_period('M')
    return hfi

def semideviation(r):
    """
    Returns the semideviation aka negative semideviation of r
    r must be a series or a dataframe
    """
    is_negative = r<0  #produced the mask
    return r[is_negative].std(ddof=0)

"""
semideviation can also be done in a more efficient way
def semideviation(r):
    return r[r<0].std(ddof=0)
"""

def skewness(r):
    """
    Alternative method to scipy.stats.skew()
    Computes the skewness of the supplied Series or Dataframe
    Retruns a float or a series
    """
    demeaned_r = r - r.mean()
    #use the population standard deviation so set dof=0
    sigma_r = r.std(ddof=0)
    exp = (demeaned_r**3).mean()
    return exp/(sigma_r**3)

def kurtosis(r):
    """
    Alternative method to scipy.stats.kurtosis()
    Computes the kurtosis of the supplied Series or Dataframe
    Retruns a float or a series
    """
    demeaned_r = r - r.mean()
    #use the population standard deviation so set dof=0
    sigma_r = r.std(ddof=0)
    exp = (demeaned_r**4).mean()
    return exp/(sigma_r**4)

def is_normal(r, level = 0.01):
    """
    Applies the Jarque-Bera test to determine if a series is normal or not
    Test is applied at the 1% level by default
    Returns true if the hypothesis of normality is accepted otherwise returns False
    """
    statistic, p_value = scipy.stats.jarque_bera(r)
    return p_value>level

def var_historic(r, level=5):
    """
    Returns the historic value at risk at a specified level
    i.e returns the number such that the 'level' percent of the returns
    fall below that number, and the (100-level) percent are above
    """
    if isinstance(r, pd.DataFrame):
        return r.aggregate(var_historic, level=level) #calls this function for every column if the series is a dataframe
    elif isinstance(r, pd.Series):
        return -np.percentile(r, level)
    else:
        raise TypeError('Expected r to be a series or a dataframe')

from scipy.stats import norm
def var_gaussian(r, level=5, modified=False):
    """
    Returns the parametric Gaussian VaR of a series or dataframe
    """
    #compute the z score
    z = norm.ppf(level/100)
    if modified:
        s = skewness(r)
        k= kurtosis(r)
        z = (z+
        (z**2 - 1)*s/6 +
        (z**3 - 3*z)*(k-3)/24 -
        (2*z**3 - 5*z)*(s**2)/36)

    return -(r.mean() + z*r.std(ddof=0))

def cvar_historic(r, level=5):
    """
    Computes the conditional VaR of series or dataframe
    """
    if isinstance(r, pd.Series):
        is_beyond = r <= -var_historic(r, level=level)
        return -r[is_beyond].mean()
    elif isinstance(r, pd.DataFrame):
        return r.aggregate(cvar_historic, level=level)
    else:
        raise TypeError('Expected r to be series or dataframe')


# coding: utf-8

# ### DEMOPT04
# # Maximization of banana function by various methods

# $$f(x,y)=-100*(y-x^2)^2-(1-x)^2$$
# starting at \[0,1\].
#

# In[1]:

import numpy as np
from compecon import OP
np.set_printoptions(4, linewidth=120, suppress=True)
import matplotlib.pyplot as plt

from compecon.demos import demo

# In[2]:

''' Set up the problem '''
x0 = [1, 0]
banana = OP(lambda x: -100 * (x[1] - x[0] ** 2)**2 - (1 - x[0]) ** 2,
            x0, maxit=250, print=True, all_x=True)

# In[3]:

x = banana.qnewton()
J = banana.jacobian(x)
E = np.linalg.eig(banana.hessian(x))[0]
print('x = ', x, '\nJ = ', J, '\nE = ', E)

# In[4]:

''' Plots options '''
steps_options = {'marker': 'o',
                 'color': (0.2, 0.2, .81),
                 'linewidth': 1.0,
                 'markersize': 3,
                 'markerfacecolor': 'white',
                 'markeredgecolor': 'red'}

contour_options = {'levels': -np.exp(np.arange(7,0.25,-0.5)),
                   'colors': '0.25',
                   'linewidths': 0.5}

# In[5]:

''' Data for coutours '''
n = 40  # number of grid points for plot per dimension
xmin = [-0.7, -0.2]
xmax = [ 1.2,  1.2]

X0, X1 = np.meshgrid(*[np.linspace(a, b, n) for a, b in zip(xmin, xmax)])
Y = banana.f([X0.flatten(), X1.flatten()])
Y.shape = (n, n)

fig = plt.figure(figsize=[12,4])

for it, method in enumerate(banana.search_methods.keys()):
    ''' Solve problem with given method '''
    print('\n\nMaximization with method ' + method.upper())
    x = banana.qnewton(SearchMeth=method)
    print('x =', x)

    ''' Plot the result '''
    demo.subplot(1, 3, it + 1, method.upper(),'x','y')

    plt.contour(X0, X1, Y, **contour_options)
    plt.plot(*banana.x_sequence, **steps_options)
    plt.plot(1, 1, 'r*', markersize=15)
    plt.title(method.upper() + " search")
    plt.xlabel('x', verticalalignment='top')
    plt.ylabel('y', verticalalignment= 'bottom')
    plt.axis((xmin[0], xmax[0], xmin[1], xmax[1]))

plt.show()

# ## Using Scipy
#
# As of this version of CompEcon, the Nelder Mead method has not been implemented. However, we can still use it with the help of the **scipy.optimize.minimize** function. To this end, we must rewrite the banana function (change its sign) so that we switch from our original maximization problem to one of minimization.
#
#
#

# In[6]:

from scipy.optimize import minimize

# In[7]:

x0 = [1, 0]
def banana2(x):
    return 100 * (x[1] - x[0] ** 2)**2 + (1 - x[0]) ** 2

# In[8]:

res = minimize(banana2, x0, method='Nelder-Mead')
print(res)

demo.savefig([fig])


"""

Path Planning with Bezier curve.

author: Atsushi Sakai(@Atsushi_twi)

"""

import scipy.special
import numpy as np
import matplotlib.pyplot as plt

show_animation = True

def calc_4points_bezier_path(sx, sy, syaw, ex, ey, eyaw, offset):
    """
    Compute control points and path given start and end position.

    :param sx: (float) x-coordinate of the starting point
    :param sy: (float) y-coordinate of the starting point
    :param syaw: (float) yaw angle at start
    :param ex: (float) x-coordinate of the ending point
    :param ey: (float) y-coordinate of the ending point
    :param eyaw: (float) yaw angle at the end
    :param offset: (float)
    :return: (numpy array, numpy array)
    """
    dist = np.sqrt((sx - ex) ** 2 + (sy - ey) ** 2) / offset
    control_points = np.array(
        [[sx, sy],
         [sx + dist * np.cos(syaw), sy + dist * np.sin(syaw)],
         [ex - dist * np.cos(eyaw), ey - dist * np.sin(eyaw)],
         [ex, ey]])

    path = calc_bezier_path(control_points, n_points=100)

    return path, control_points

def calc_bezier_path(control_points, n_points=100):
    """
    Compute bezier path (trajectory) given control points.

    :param control_points: (numpy array)
    :param n_points: (int) number of points in the trajectory
    :return: (numpy array)
    """
    traj = []
    for t in np.linspace(0, 1, n_points):
        traj.append(bezier(t, control_points))

    return np.array(traj)

def bernstein_poly(n, i, t):
    """
    Bernstein polynom.

    :param n: (int) polynom degree
    :param i: (int)
    :param t: (float)
    :return: (float)
    """
    return scipy.special.comb(n, i) * t ** i * (1 - t) ** (n - i)

def bezier(t, control_points):
    """
    Return one point on the bezier curve.

    :param t: (float) number in [0, 1]
    :param control_points: (numpy array)
    :return: (numpy array) Coordinates of the point
    """
    n = len(control_points) - 1
    return np.sum([bernstein_poly(n, i, t) * control_points[i] for i in range(n + 1)], axis=0)

def bezier_derivatives_control_points(control_points, n_derivatives):
    """
    Compute control points of the successive derivatives of a given bezier curve.

    A derivative of a bezier curve is a bezier curve.
    See https://pomax.github.io/bezierinfo/#derivatives
    for detailed explanations

    :param control_points: (numpy array)
    :param n_derivatives: (int)
    e.g., n_derivatives=2 -> compute control points for first and second derivatives
    :return: ([numpy array])
    """
    w = {0: control_points}
    for i in range(n_derivatives):
        n = len(w[i])
        w[i + 1] = np.array([(n - 1) * (w[i][j + 1] - w[i][j])
                             for j in range(n - 1)])
    return w

def curvature(dx, dy, ddx, ddy):
    """
    Compute curvature at one point given first and second derivatives.

    :param dx: (float) First derivative along x axis
    :param dy: (float)
    :param ddx: (float) Second derivative along x axis
    :param ddy: (float)
    :return: (float)
    """
    return (dx * ddy - dy * ddx) / (dx ** 2 + dy ** 2) ** (3 / 2)

def plot_arrow(x, y, yaw, length=1.0, width=0.5, fc="r", ec="k"):  # pragma: no cover
    """Plot arrow."""
    if not isinstance(x, float):
        for (ix, iy, iyaw) in zip(x, y, yaw):
            plot_arrow(ix, iy, iyaw)
    else:
        plt.arrow(x, y, length * np.cos(yaw), length * np.sin(yaw),
                  fc=fc, ec=ec, head_width=width, head_length=width)
        plt.plot(x, y)

def main():
    """Plot an example bezier curve."""
    start_x = 10.0  # [m]
    start_y = 1.0  # [m]
    start_yaw = np.radians(180.0)  # [rad]

    end_x = -0.0  # [m]
    end_y = -3.0  # [m]
    end_yaw = np.radians(-45.0)  # [rad]
    offset = 3.0

    path, control_points = calc_4points_bezier_path(
        start_x, start_y, start_yaw, end_x, end_y, end_yaw, offset)

    # Note: alternatively, instead of specifying start and end position
    # you can directly define n control points and compute the path:
    # control_points = np.array([[5., 1.], [-2.78, 1.], [-11.5, -4.5], [-6., -8.]])
    # path = calc_bezier_path(control_points, n_points=100)

    # Display the tangent, normal and radius of cruvature at a given point
    t = 0.86  # Number in [0, 1]
    x_target, y_target = bezier(t, control_points)
    derivatives_cp = bezier_derivatives_control_points(control_points, 2)
    point = bezier(t, control_points)
    dt = bezier(t, derivatives_cp[1])
    ddt = bezier(t, derivatives_cp[2])
    # Radius of curvature
    radius = 1 / curvature(dt[0], dt[1], ddt[0], ddt[1])
    # Normalize derivative
    dt /= np.linalg.norm(dt, 2)
    tangent = np.array([point, point + dt])
    normal = np.array([point, point + [- dt[1], dt[0]]])
    curvature_center = point + np.array([- dt[1], dt[0]]) * radius
    circle = plt.Circle(tuple(curvature_center), radius,
                        color=(0, 0.8, 0.8), fill=False, linewidth=1)

    assert path.T[0][0] == start_x, "path is invalid"
    assert path.T[1][0] == start_y, "path is invalid"
    assert path.T[0][-1] == end_x, "path is invalid"
    assert path.T[1][-1] == end_y, "path is invalid"

    if show_animation:  # pragma: no cover
        fig, ax = plt.subplots()
        ax.plot(path.T[0], path.T[1], label="Bezier Path")
        ax.plot(control_points.T[0], control_points.T[1],
                '--o', label="Control Points")
        ax.plot(x_target, y_target)
        ax.plot(tangent[:, 0], tangent[:, 1], label="Tangent")
        ax.plot(normal[:, 0], normal[:, 1], label="Normal")
        ax.add_artist(circle)
        plot_arrow(start_x, start_y, start_yaw)
        plot_arrow(end_x, end_y, end_yaw)
        ax.legend()
        ax.axis("equal")
        ax.grid(True)
        plt.show()

def main2():
    """Show the effect of the offset."""
    start_x = 10.0  # [m]
    start_y = 1.0  # [m]
    start_yaw = np.radians(180.0)  # [rad]

    end_x = -0.0  # [m]
    end_y = -3.0  # [m]
    end_yaw = np.radians(-45.0)  # [rad]

    for offset in np.arange(1.0, 5.0, 1.0):
        path, control_points = calc_4points_bezier_path(
            start_x, start_y, start_yaw, end_x, end_y, end_yaw, offset)
        assert path.T[0][0] == start_x, "path is invalid"
        assert path.T[1][0] == start_y, "path is invalid"
        assert path.T[0][-1] == end_x, "path is invalid"
        assert path.T[1][-1] == end_y, "path is invalid"

        if show_animation:  # pragma: no cover
            plt.plot(path.T[0], path.T[1], label="Offset=" + str(offset))

    if show_animation:  # pragma: no cover
        plot_arrow(start_x, start_y, start_yaw)
        plot_arrow(end_x, end_y, end_yaw)
        plt.legend()
        plt.axis("equal")
        plt.grid(True)
        plt.show()

if __name__ == '__main__':
    main()
    #  main2()


import numpy as np

def normalize(x: np.ndarray) -> np.ndarray:
    assert x.ndim == 1, 'x must be a vector (ndim: 1)'
    return x / np.linalg.norm(x)

def look_at(
    eye,
    target,
    up,
) -> np.ndarray:
    """Returns transformation matrix with eye, at and up.
    Parameters
    ----------
    eye: (3,) float
        Camera position.
    target: (3,) float
        Camera look_at position.
    up: (3,) float
        Vector that defines y-axis of camera (z-axis is vector from eye to at).
    Returns
    -------
    T_cam2world: (4, 4) float (if return_homography is True)
        Homography transformation matrix from camera to world.
        Points are transformed like below:
            # x: camera coordinate, y: world coordinate
            y = trimesh.transforms.transform_points(x, T_cam2world)
            x = trimesh.transforms.transform_points(
                y, np.linalg.inv(T_cam2world)
            )
    """
    eye = np.asarray(eye, dtype=float)

    if target is None:
        target = np.array([0, 0, 0], dtype=float)
    else:
        target = np.asarray(target, dtype=float)

    if up is None:
        up = np.array([0, 0, -1], dtype=float)
    else:
        up = np.asarray(up, dtype=float)

    assert eye.shape == (3,), 'eye must be (3,) float'
    assert target.shape == (3,), 'target must be (3,) float'
    assert up.shape == (3,), 'up must be (3,) float'

    # create new axes
    z_axis = normalize(target - eye)
    x_axis = normalize(np.cross(up, z_axis))
    y_axis = normalize(np.cross(z_axis, x_axis))

    # create rotation matrix: [bs, 3, 3]
    R = np.vstack((x_axis, y_axis, z_axis))
    t = eye

    T_cam2world = np.zeros([4,4])
    T_cam2world[:3,:3] = R.T
    T_cam2world[:3, 3] = t
    T_cam2world[3,3] = 1.
    return T_cam2world


"""
Given an integer n, your task is to count how many strings of length n can be formed under the following rules:
    Each character is a lower case vowel ('a', 'e', 'i', 'o', 'u')
    Each vowel 'a' may only be followed by an 'e'.
    Each vowel 'e' may only be followed by an 'a' or an 'i'.
    Each vowel 'i' may not be followed by another 'i'.
    Each vowel 'o' may only be followed by an 'i' or a 'u'.
    Each vowel 'u' may only be followed by an 'a'.
Since the answer may be too large, return it modulo 10^9 + 7.
Example:
        Input - n = 1
        Output - 5
        Explanation - All possible strings are: "a", "e", "i" , "o" and "u".

        Input - n = 2
        Output - 10

Constraints:
        Time complexity - O(N) [ o(5N) ]
        Space complexity - O(N) [ o(5N) ]
        1 <= n <= 2 * 10^4

Hints:
      Use dynamic programming.
      Let dp[i][j] be the number of strings of length i that ends with the j-th vowel.
      Deduce the recurrence from the given relations between vowels.
"""

import numpy as np
class Solution:
    def countVowelPermutation(self, n: int) -> int:
        dp = np.zeros((n, 5), dtype="Int64")
        rules = {0: [1], 1: [0, 2], 2: [0, 1, 3, 4], 3: [2, 4], 4: [0]}

        for i in range(n):
            for j in range(5):
                if i == 0:
                    dp[i][j] = 1
                    continue
                for k in rules[j]:
                    dp[i][k] = (dp[i - 1][j]%1000000007 + dp[i][k]%1000000007)%1000000007

        return sum(dp[n - 1])%1000000007


# Question 1 Lab 04
# AB Satyaprakash 180123062

# imports ----------------------------------------------------------------------
from math import sqrt
import matplotlib.pyplot as plt
import numpy as np

# ------------------------------------------------------------------------------
# (a) Plotting the Markowitz efficient frontier and
# (b) Tabulating Return, Risk and Weights for 10 different values on the efficient frontier
# Given Mean vector and Covariance matrix
m = np.array([0.1, 0.2, 0.15])
C = np.array([[0.005, -0.010, 0.004],
              [-0.010, 0.040, -0.002],
              [0.004, -0.002, 0.023]])
u = np.array([1, 1, 1])

m_trans = m.transpose()
C_trans = C.transpose()
u_trans = u.transpose()
C_inv = np.linalg.inv(C)

ret, sig = np.zeros(100), np.zeros(100)
print(' Return - Risk   -   W1    -    W2    -    W3\n')

# Finding and tabulating the values
for i in range(1, 101):
    ret[i-1] = i*0.005
    y = np.array([[ret[i-1]], [1]])
    M = np.array([[m.dot(C_inv).dot(m_trans), u.dot(C_inv).dot(m_trans)],
                  [m.dot(C_inv).dot(u_trans), u.dot(C_inv).dot(u_trans)]])
    lam = 2*(np.linalg.inv(M).dot(y))
    w = (lam[0][0]*(m.dot(C_inv))+lam[1][0]*(u.dot(C_inv)))/2
    sig[i-1] = sqrt(w.dot(C).dot(w.transpose()))
    if i % 10 == 0:
        print('  {}   {}    {}    {}     {}  '.format(
            '%.2f' % ret[i-1], '%.4f' % sig[i-1], '%.4f' % w[0], '%.4f' % w[1], '%.4f' % w[2]))

# Plotting the efficient frontier
plt.plot(sig, ret, color='red')
plt.grid()
plt.title('Markowitz Efficient Frontier for the given data')
plt.xlabel('Value of Risk (σ)')
plt.ylabel('Value of Return (μ)')
plt.show()

# (c) risk = 15%, calculate the maximum and minimum return and corresponding portfolios
A = u.dot(C_inv).dot(u_trans)
B = u.dot(C_inv).dot(m_trans)
V = m.dot(C_inv).dot(m_trans)
d = A*V - B*B
risk = 0.15

# Maximum return
ret_max = (2*B+sqrt(4*B*B-4*A*(V-d*risk*risk)))/(2*A)
y = np.array([[ret_max], [1]])
M = np.array([[m.dot(C_inv).dot(m_trans), u.dot(C_inv).dot(m_trans)],
              [m.dot(C_inv).dot(u_trans), u.dot(C_inv).dot(u_trans)]])
lam = 2*(np.linalg.inv(M).dot(y))
w = (lam[0][0]*(m.dot(C_inv))+lam[1][0]*(u.dot(C_inv)))/2
print('\n\nMaximum Return Portfolio for 15% risk --')
print('Return = {}% and Weights : {},  {},  {}'.format('%.4f' %
                                                       (ret_max*100), '%.4f' % w[0], '%.4f' % w[1], '%.4f' % w[2]))

# Minimum return
ret_min = (2*B-sqrt(4*B*B-4*A*(V-d*risk*risk)))/(2*A)
y = np.array([[ret_min], [1]])
M = np.array([[m.dot(C_inv).dot(m_trans), u.dot(C_inv).dot(m_trans)],
              [m.dot(C_inv).dot(u_trans), u.dot(C_inv).dot(u_trans)]])
lam = 2*(np.linalg.inv(M).dot(y))
w = (lam[0][0]*(m.dot(C_inv))+lam[1][0]*(u.dot(C_inv)))/2
print('\n\nMinimum Return Portfolio for 15% risk --')
print('Return = {}% and Weights : {},  {},  {}'.format('%.4f' %
                                                       (ret_min*100), '%.4f' % w[0], '%.4f' % w[1], '%.4f' % w[2]))

# (d) return = 18%, calculate the minimum risk portfolio
y = np.array([[0.18], [1]])  # using return = 18%
M = np.array([[m.dot(C_inv).dot(m_trans), u.dot(C_inv).dot(m_trans)],
              [m.dot(C_inv).dot(u_trans), u.dot(C_inv).dot(u_trans)]])
lam = 2*(np.linalg.inv(M).dot(y))
w = (lam[0][0]*(m.dot(C_inv))+lam[1][0]*(u.dot(C_inv)))/2
risk = sqrt(w.dot(C).dot(w.transpose()))
print('\n\nMinimum Risk Portfolio for 18% return --')
print('Risk = {}% and Weights : {},  {},  {}'.format('%.4f' %
                                                     (risk*100), '%.4f' % w[0], '%.4f' % w[1], '%.4f' % w[2]))

# (e) Given µrf = 10%, compute the market portfolio and plot the CML
wm = ((m-0.1*u).dot(C_inv))/((m-0.1*u).dot(C_inv).dot(u_trans))
um = wm.dot(m_trans)
sigm = sqrt(wm.dot(C).dot(wm.transpose()))

print('\nFor 10% risk-free return --')
print('Weights : {},  {},  {}'.format('%.4f' % wm[0], '%.4f' % wm[1], '%.4f' % wm[2]))
print('Return on market portfolio = {}%'.format('%.4f' % (um*100)))
print('Risk on market portfolio = {}%\n'.format('%.4f' % (sigm*100)))

x = np.arange(0, 1.1, 0.1)
y = 0.1 + ((um-0.1)/sigm)*x
plt.scatter(sigm, um, marker=(5, 2), color='blue', label='Market Portfolio')
plt.scatter([0], [0.1], marker='o', color='green', label='Zero Risk Portfolio')
plt.plot(x, y, color='green', label='CML')
plt.plot(sig, ret, color='red', label='Markowitz Efficient Frontier')
plt.title('Capital Market Line and Markowitz Efficient Frontier')
plt.xlabel('Value of Risk (σ)')
plt.ylabel('Value of Return (μ)')
plt.legend()
plt.grid()
plt.show()

plt.plot(x, y, color='green')
plt.title('Capital Market Line')
plt.xlabel('Value of Risk (σ)')
plt.ylabel('Value of Return (μ)')
plt.grid()
plt.show()

# (f) Find 2 portfolios (having both risky and risk free assets) with risk = 10% and 25%.
y = 0.1+(um-0.1)/sigm*0.1
rf = (y-um)/(0.1-um)
risked = (1-rf)*wm

print('\nPortfolio for 10% risk --')
print('Risk-Free Asset : {}'.format('%.4f' % rf))
print('Risky Assets Weights : {},  {},  {}'.format('%.4f' %
                                                   risked[0], '%.4f' % risked[1], '%.4f' % risked[2]))

y = 0.1+(um-0.1)/sigm*0.25
rf = (y-um)/(0.1-um)
risked = (1-rf)*wm

print('\nPortfolio for 25% risk --')
print('Risk-Free Asset : {}'.format('%.4f' % rf))
print('Risky Assets Weights : {},  {},  {}'.format('%.4f' %
                                                   risked[0], '%.4f' % risked[1], '%.4f' % risked[2]))

# Question 1 ends --------------------------------------------------------------

Semester 6/MA 374 (Financial Engg. Lab)/Lab 4/180123062_AB_q1.py


# Python code for Mandelbrot Fractal

# Import necessary libraries
from PIL import Image, ImageEnhance
from PIL import Image
import numpy as np
import colorsys
import time
import os

# setting the width and height of the output image
# 460 480
# 720 576
# 720 480
# 1280 720
# 1440 1080
# 1920 1080
# 2048 1080
# 3840 2160
# 4096 2160

WIDTH = 1280
HEIGHT = 720

# a function to return a tuple of colors
# as integer value of rgb

# def rgb(i):
#     red = int(i / 255.0)
#     green = int(1.0+i/10.0)
#     blue = int(0.5*i/100.0)
#     color = (red, green, blue)
#     return color

def rgb(i):
    color = 255 * np.array(colorsys.hsv_to_rgb(i / 255.0, i/64, i/128.0))
    return tuple(color.astype(int))

# function defining a mandelbrot
def mandelbrot(x, y, dimension):
    c = complex(x, y)
    z = 0
    for i in range(1, 100):
        if abs(z) > 2:
            return rgb(i)
        # z =complex(abs(z.real),abs(z.imag))**2+c #Burning ship
        # z = z.conjugate()**2+c
        z = complex(z.real,abs(z.imag))**2+c
        # z = z**dimension+ c #Mandelbrot
    return (0, 0, 0)

# Main function

def evaluateMandelbrot(dimension):
    # get the path to the current python file directory
    path = os.path.dirname(os.path.realpath(__file__))

    # creating the new image in RGB mode
    img = Image.new('RGB', (WIDTH, HEIGHT))
    pixels = img.load()

    start = time.time()
    for x in range(img.size[0]):

        # displaying the progress as percentage
        print("{:.2f} %".format(x / WIDTH * 100.0))

        for y in range(img.size[1]):
            pixels[x, y] = mandelbrot((x - (0.65 * WIDTH)) / (0.2*WIDTH),
                                      (y - (0.5*HEIGHT))/(0.3*HEIGHT), dimension)

    #image brightness enhancer
    enhancer = ImageEnhance.Contrast(img)

    factor = 10 #gives original image
    img = enhancer.enhance(factor)

    # to display the created fractal after
    # completing the given number of iterations
    img.save(path + "/mandelbrot.png")

    print("Done!")

if __name__ == '__main__':
    evaluateMandelbrot(2)


# coordinates.py
# Jacob Hummel
import numpy as np

#===============================================================================
def cartesian_to_spherical(coords):
    coords['r_sph'] = np.sqrt(np.square(coords.x) + np.square(coords.y) + np.square(coords.z))
    coords['theta_sph'] = np.arccos(coords.z/coords.r_sph)
    coords['phi_sph'] = np.arctan2(coords.y,coords.x)

def spherical_to_cartesian(r, theta, phi):
    x = r * np.sin(theta) * np.cos(phi)
    y = r * np.sin(theta) * np.sin(phi)
    z = r * np.cos(theta)
    return x,y,z

def cartesian_to_cylindrical(coords):
    coords['r_cyl'] = np.sqrt(np.square(coords.x) + np.square(coords.y))
    coords['theta_cyl'] = np.arctan2(coords.y,coords.x)
    #return coords.drop(['x', 'y', 'z'], axis=1)

def cylindrical_to_cartesian(r, theta, z):
    x = r * np.cos(theta)
    y = r * np.sin(theta)
    return x,y,z

def define_unit_vectors(pos):
    e1 = np.zeros_like(pos)
    e2 = np.zeros_like(pos)
    e3 = np.zeros_like(pos)
    e1[...,0] = 1.
    e2[...,1] = 1.
    e3[...,2] = 1.
    return e1, e2, e3

def cartesian_to_spherical_unit_vectors(coords):
    unit_x, unit_y, unit_z = define_unit_vectors(coords)
    r,theta,phi = cartesian_to_spherical(coords[...,0],coords[...,1],coords[...,2])
    r = r[...,np.newaxis]
    theta = theta[...,np.newaxis]
    phi = phi[..., np.newaxis]
    unit_r = coords / r
    unit_phi = unit_y * np.cos(phi) - unit_x * np.sin(phi)
    unit_theta = (unit_x * np.cos(theta) * np.cos(phi)
                  + unit_y * np.cos(theta) * np.sin(phi)
                  - unit_z * np.sin(theta))
    return unit_r, unit_theta, unit_phi

def spherical_to_cartesian_unit_vectors(sph):
    unit_r, unit_theta, unit_phi = define_unit_vectors(sph)
    r = sph[...,0, np.newaxis]
    theta = sph[...,1, np.newaxis]
    phi = sph[...,2, np.newaxis]
    unit_x = (unit_r * np.sin(theta) * np.cos(phi)
              + unit_theta * np.cos(theta) * np.cos(phi)
              - unit_phi * np.sin(phi))
    unit_y = (unit_r * np.sin(theta) * np.sin(phi)
              + unit_theta * np.cos(theta) * np.sin(phi)
              + unit_phi * np.cos(phi))
    unit_z = unit_r * np.cos(theta) - unit_theta * np.sin(theta)
    return unit_x, unit_y, unit_z

def cartesian_to_spherical_velocities(coords, uvw):
    unit_r, unit_theta, unit_phi = cartesian_to_spherical_unit_vectors(coords)
    # np.einsum('ij,ij->i',...) is einstein notation for dot product.
    vr = np.einsum('ij,ij->i', uvw, unit_r)
    vtheta = np.einsum('ij,ij->i', uvw, unit_theta)
    vphi = np.einsum('ij,ij->i', uvw, unit_phi)
    return vr, vtheta, vphi

def spherical_to_cartesian_velocities(sph, vsph):
    unit_x, unit_y, unit_z = spherical_to_cartesian_unit_vectors(sph)
    # np.einsum('ij,ij->i',...) is einstein notation for dot product.
    u = np.einsum('ij,ij->i', vsph, unit_x)
    v = np.einsum('ij,ij->i', vsph, unit_y)
    w = np.einsum('ij,ij->i', vsph, unit_z)
    return u, v, w

def rotation_matrix(axis, angle):
    if axis == 'x':
        rot = ((1., 0., 0.),
               (0., np.cos(angle), -np.sin(angle)),
               (0., np.sin(angle), np.cos(angle)))
    elif axis == 'y':
        rot = ((np.cos(angle), 0.,np.sin(angle)),
               (0., 1., 0.),
               (-np.sin(angle), 0., np.cos(angle)))
    elif axis == 'z':
        rot = ((np.cos(angle), -np.sin(angle), 0.),
               (np.sin(angle), np.cos(angle), 0.),
               (0., 0.,1.))
    else:
        u = axis[0]
        v = axis[1]
        w = axis[2]
        u2= u*u
        v2= v*v
        w2= w*w
        cos = np.cos(angle)
        sin = np.sin(angle)
        rot = ((u2 + (1-u2)*cos, u*v*(1-cos) - w*sin, u*w*(1-cos) + v*sin),
               (u*v*(1-cos) + w*sin, v2 + (1-v2)*cos, v*w*(1-cos) - u*sin),
               (u*w*(1-cos) - v*sin, v*w*(1-cos) + u*sin, w2 + (1-w2)*cos))
    rot = np.asarray(rot)
    return rot

def rotate(coords, axis, angle, verbose=False):
    rot = rotation_matrix(axis,angle)
    if verbose:
        print "Rotating about the {}-axis by {:6.3f} radians.".format(axis,angle)
        print "Rotation Matrix:"
        print rot
    coords[coords.columns] = np.dot(coords,rot)
    return coords