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

g = 9.81
m = 1
k = 9
l = g /k

dt = 0.001

def acc_harmonic(theta):
    return -k * theta

def acc_pendulum(theta):
    return -k * np.sin(theta)

def energy_harmonic(theta, dtheta):
    return 0.5 * m * l**2 * dtheta**2 + 0.5 * m * g * l * theta**2

def energy_pendulum(theta, dtheta):
    return 0.5 * m * l**2 * dtheta**2 + m * g * l * (1. - np.cos(theta))

def velocity_verlet(A, E, theta, dtheta):
    a = A(theta)
    theta += dtheta * dt + 0.5 * a * dt * dt
    a_next = A(theta)
    dtheta += 0.5 * (a_next + a) * dt

    energy = E(theta, dtheta)
    return [theta, dtheta, energy]

def period(A, E, theta_i):
    theta = theta_i
    dtheta = 0
    time = 0

    def iterate():
        nonlocal theta, dtheta, time
        current = sign(theta)
        while sign(theta) == current:
            time += dt
            [theta, dtheta, energy] = velocity_verlet(A, E, theta, dtheta)

    iterate()
    start = time
    iterate()
    iterate()
    end = time
    period = end - start

    return period

def output():
  theta = np.linspace(0.01, np.pi/2, 20)
  period_pendulum, period_harmonic, period_perturbation = [], [], []
  for i in theta:
      period_harmonic.append(period(acc_harmonic, energy_harmonic, i))
      period_pendulum.append(period(acc_pendulum,energy_pendulum, i))
      period_perturbation.append(2*np.pi * np.sqrt(1/k) * (1 + i**2/16 +
                                 11* i **4/3072) + 173* i**6/737820)

  plt.figure()
  plt.plot(theta/np.pi, period_pendulum)
  plt.plot(theta/np.pi, period_harmonic)
  plt.plot(theta/np.pi, period_perturbation)
  plt.title("Comparison of Pendulum/Harmonic \n against Perturbation series", x = 0.3)
  plt.figlegend(('Pendulum', 'Harmonic', 'Perturbation series O(n^6)'))
  plt.xlabel("θ(0)/pi (Position)", fontsize = 12)
  plt.ylabel("T (Seconds)", fontsize = 12)
  plt.show()

output()


import math
import secrets
from typing import Tuple

import sympy

# MIT License

# Copyright (c) 2018 Pierre Bouillon

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""byte length for p and q generation
"""
BYTE_LEN = 32

def decrypt(keypair: Tuple[int, int], to_decrypt: list) -> str:
    """decrypt a message with the public key

    :param keypair: public key
    :param to_decrypt: ciphertext
    :return: the decripted message
    """
    pk, n = keypair
    # decrypting with: m = c^pk % n
    return ''.join([chr(pow(c, pk, n)) for c in to_decrypt])

def encrypt(keypair: Tuple[int, int], to_encrypt: str) -> list:
    """Encrypting the message with private key

    :param keypair: private key
    :param to_encrypt: message to encrypt
    :return: the encrypted message as a list of numbers
    """
    pk, n = keypair

    # encrypting with nb = c^pk % n
    return [pow(ord(c), pk, n) for c in to_encrypt]

def gen_keys(_p: int, _q: int) -> tuple:
    """Generating private and public keys

    :param _p: first prime number
    :param _q: second prime number
    :return: the public and private key pairs
    """
    # modulus for public and private keys
    n = _p * _q

    # totient
    # see https://simple.wikipedia.org/wiki/Euler's_totient_function
    phi = (_p - 1) * (_q - 1)

    # picking e > 1 corpime to phi
    # see https://simple.wikipedia.org/wiki/Coprime
    e = secrets.randbelow(phi) + 1
    while math.gcd(e, phi) != 1:
        e = secrets.randbelow(phi) + 1

    # evaluate d using Extended Euclidean algorithm
    # see: https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
    d = sympy.mod_inverse(e, phi)

    # (e, n) -> public key pair
    # (d, n) -> private key pair
    return (e, n), (d, n)

def get_rdm_prime_nb(to_ignore=None) -> int:
    """Generate prime numbers

    :param to_ignore: blacklisted numbers
    :return: the generated prime number
    """
    if to_ignore is None:
        to_ignore = []
    found = False

    nb = 0
    while not found:
        nb = secrets.randbits(BYTE_LEN)

        # the number shouldn't be in the ignored list
        if nb in to_ignore or not is_prime(nb):
            continue

        found = True

    return nb

def is_prime(nb: int) -> bool:
    """Check if a number is a prime number or not

    :param nb: the number to check
    :return: True if prime, False otherwise
    """
    # even numbers are not prime
    if nb % 2 == 0 and nb > 2:
        return False

    # checking all numbers up to the square root of the number
    # full explanation:
    # https://stackoverflow.com/questions/18833759/python-prime-number-checker/18833870#18833870
    return all(nb % i for i in range(3, int(nb ** .5) + 1, 2))

def power(x: int, n: int) -> int:
    """Evaluate the power of big numbers numbers
    see https://en.wikipedia.org/wiki/Exponentiation_by_squaring

    :param x: number
    :param n: power
    :return: the total of x ** n
    """
    if n < 0:
        x = 1 / x
        n *= -1

    if n == 0:
        return 1

    y = 1
    while n > 1:
        if n % 2 == 0:
            x *= x
            n /= 2
        else:
            y *= x
            x *= x
            n = (n - 1) * 2

    return x * y


'''
Collection of function useful
for regressions.
'''

import numpy as np

def check_data(x, y, cov_th=10):
    #pylint: disable=invalid-name
    '''
    Checks if the data has a covariance
    bigger than cov_th, and returns the
    data split in x and y

    arguments:
    - x: a list with the data x values
    - y: a list with the data y values
    - cov_th: threshold to use for the covariance

    returns:
    True if the covariance condition is satisfied
    '''

    cov = np.cov(x, y)

    if np.abs(cov[1, 1]) > cov_th and np.abs(cov[0, 1]) > cov_th:
        return True

    return False

def data_regression(x, y, deg=4, full=True):
    #pylint: disable=invalid-name
    '''
    Performs the data regression with a
    polynomial of order deg.

    arguments:
    - x: a list with the data x values
    - y: a list with the data y values
    - deg: the order of the polynomial

    returns:
    - a list with the polynomial coefficients
    - the chi squared
    - the p-value
    '''

    if check_data(x, y):
        coeff = np.polyfit(x, y, deg=deg)
        chi_squared = np.sum((np.polyval(coeff, x) - y) ** 2)
        ndf = len(x) - deg
        # p_value = 1 - stats.chi2.cdf(chi_squared, len(x)-deg)

        if full:
            return np.flip(coeff), chi_squared, ndf
        return np.flip(coeff)

    if full:
        return [], -1, -1
    return []


from scipy.linalg import logm
import scipy.integrate as scint
import numpy as np

def dmd(X, Xp, r=2):
    """simple dynamic mode decomposition (dmd) implementation"""
    # compute Atilde
    U, Sigma, V = np.linalg.svd(X, False)
    U, Sigma, V = U[:, :r], np.diag(Sigma)[:r, :r], V.conj().T[:, :r]
    Atilde = U.conj().T @ Xp @ V @ np.linalg.inv(Sigma)

    # eigenvalues of Atilde are same as A
    D, W = np.linalg.eig(Atilde)

    # recover U.shape[0] eigenfunctions of A
    phi = Xp @ V @ (np.linalg.inv(Sigma)) @ W
    return D, phi

def evolve_modes(mu, Phi, ic, sampling_period, tmax=20.0):
    """time evolve modes of the DMD"""
    b = np.linalg.pinv(Phi) @ np.array(ic)
    t = np.linspace(0, tmax, int(tmax/sampling_period))
    dt = t[2] - t[1]
    psi = np.zeros([len(ic), len(t)], dtype='complex')
    for i,ti in enumerate(t):
        psi[:,i] = np.multiply(np.power(mu, ti / dt), b)
    return psi

def learned_sys(Xc, Xcp, p, g, gradg, sampling_period):
    Yc, Ycp = np.hstack([g(x, p=p) for x in Xc.T]), np.hstack([g(x, p=p) for x in Xcp.T])
    A = Ycp @ np.linalg.pinv(Yc)
    B = logm(A) / sampling_period
    def sys(t, x):
        return np.real(np.linalg.pinv(gradg(x, p=p)) @ B @ g(x, p=p)).flatten()
    return sys

def learned_dmd_sys(Xc, Xcp, g, gradg, sampling_period, r=30):
    Yc, Ycp = np.hstack([g(x) for x in Xc.T]), np.hstack([g(x) for x in Xcp.T])
    mucy, Phicy = dmd(Yc, Ycp, r=r)
    A = Phicy @ np.diag(mucy) @ np.linalg.pinv(Phicy)
    B = logm(A) / sampling_period
    def sys(t, x):
        return np.real(np.linalg.pinv(gradg(x)) @ B @ g(x)).flatten()
    return sys

def get_linear_transform(Xc, Xcp, g, sampling_period, r=30, continuous=True):
    Yc, Ycp = np.hstack([g(x) for x in Xc.T]), np.hstack([g(x) for x in Xcp.T])
    mucy, Phicy = dmd(Yc, Ycp, r)
    A = Phicy @ np.diag(mucy) @ np.linalg.pinv(Phicy)
    if continuous:
        B = logm(A) / sampling_period
        return B
    else:
        return A


import numpy as np
import os

class Criterion(object):
    def __init__(self):
        self.logits = None
        self.labels = None
        self.loss = None

    def __call__(self, x, y):
        return self.forward(x, y)

    def forward(self, x, y):
        raise NotImplemented

    def derivative(self):
        raise NotImplemented

class SoftmaxCrossEntropy(Criterion):
    """
    Softmax loss
    """

    def __init__(self):
        super(SoftmaxCrossEntropy, self).__init__()
        self.logsum = None

    def forward(self, x, y):
        """
        Argument:
            x (np.array): (batch size, 10)
            y (np.array): (batch size, 10)
        Return:
            out (np.array): (batch size, )
        """
        self.logits = x
        self.labels = y
        g = np.max(x, axis = 1, keepdims= True)
        ds = x - g
        log = g + np.log(np.exp(ds).sum(axis = 1, keepdims = True))
        # self.logsum = x - np.log(np.exp(x - np.amax(x, axis=1))) - np.amax(x, axis=1)
        self.logsum = x - log
        self.loss = - np.sum(np.multiply(y, self.logsum), axis=1)
        return self.loss

    def derivative(self):
        """
        Return:
            out (np.array): (batch size, 10)
        """

        return np.exp(self.logits)/ np.exp(self.logits).sum(axis = 1, keepdims=True) - self.labels


__author__ = 'joseph'

import numpy as np

class ActivationFunction(object):
    def activation(self, x):
        pass

    def derivative(self, x):
        pass

class LinearActivation(ActivationFunction):
    def activation(self, x):
        return x

    def derivative(self, x):
        return np.ones(x.shape)

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def sig_deriv(x):
    s = sigmoid(x)
    return s * (1 - s)

class SigmoidActivation(ActivationFunction):
    def activation(self, x):
        return sigmoid(x)

    def derivative(self, x):
        return sig_deriv(x)

def relu(x):
    return x * (x > 0.0)

def relu_deriv(x):
    return x > 0.0

class ReluActivation(ActivationFunction):
    def activation(self, x):
        return relu(x)

    def derivative(self, x):
        return relu_deriv(x)

def leaky_relu(x):
    return (x * (x > 0.0)) + (0.01 * x * (x <= 0.0))

def leaky_relu_deriv(x):
    return (0.99 * (x > 0.0)) + 0.01

class LeakyReluActivation(ActivationFunction):
    def activation(self, x):
        return leaky_relu(x)

    def derivative(self, x):
        return leaky_relu_deriv(x)

def tanh_deriv(x):
    tanh_squared = np.tanh(x) ** 2.0
    return 1.0 - tanh_squared

class TanhActivation(ActivationFunction):
    def activation(self, x):
        return np.tanh(x)

    def derivative(self, x):
        return tanh_deriv(x)


"""
Supervised Anomaly Detection
implement an anomaly detection algorithm using a Gaussian model and apply it to detect failing servers on a network.
using a Gaussian model to detect if an unlabeled example from a dataset should be considered an anomaly.
* using a set of labeled validation data (where the true anomalies have been marked for us)

Steps:
1. Estimate X's Probability Distribution (prob_dist).
2. Calculate the Probability Density Function at X (for each and every value in X and X_val - p, p_val).
3. Determine the optimal probability threshold value (which defines what is considered an anomaly).

https://www.johnwittenauer.net/machine-learning-exercises-in-python-part-8/
"""

import numpy as np
from scipy.io import loadmat
import matplotlib.pyplot as plt
from scipy import stats

data = loadmat('../../datasets/per_type/matlab/ex8data1.mat')
X = data['X']  # (307, 2)
# Using a set of labeled validation data (where the true anomalies have been marked for us):
X_val = data['Xval']  # (307, 2)
y_val = data['yval']  # (307, 1)

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

# 1. Estimate the Probability Distribution of X:

# Calculate the Normal (Gaussian) Probability Distribution's parameters:
#   Mean (mu) and Variance (sigma), for each feature in the dataset -
mu = X.mean(axis=0)
sigma = X.var(axis=0)

# Construct the Normal (Gaussian) Probability Distribution:
prob_dist = stats.norm(mu, sigma)

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

# 2. Calculate the Probability Density Function at x:
#   this is the probability that X=x - the probability that the instance (x) belongs to X's probability distribution
#       (compares its distance from the mean, to X's "typical" distance from the mean).
p = prob_dist.pdf(X)
p_val = prob_dist.pdf(X_val)

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

# 3. Determine the optimal probability threshold value (which defines what is considered an anomaly):

def calc_f1_score(eps, p, y):
    """
    Calculates the F1 score:
        F1 = (2 * precision * recall) / (precision + recall)
           = TP / (TP + (FP + FN) / 2)
    tests the model's performance at identifying those anomalies given different threshold values.

    :param eps: epsilon - probability threshold value
    :param p: the dataset's probabilities (that the data belongs to the distribution)
    :param y: the dataset's true labels (0/1) - in this case if it's an anomaly or not
    :return: f1 score
    """
    y_pred = p < eps  # predicts if anomaly

    tp = np.sum(np.logical_and(y_pred == 1, y == 1)).astype(float)
    fp = np.sum(np.logical_and(y_pred == 1, y == 0)).astype(float)
    fn = np.sum(np.logical_and(y_pred == 0, y == 1)).astype(float)

    precision = 0 if tp + fp == 0 else tp / (tp + fp)
    recall = 0 if tp + fn == 0 else tp / (tp + fn)
    f1 = 0 if precision + recall == 0 else (2 * precision * recall) / (precision + recall)

    return f1

def get_optimal_prob_threshold_and_score(p_val, y_val):
    """
    Finds the optimal epsilon (Probability Threshold value)
    :param p_val: probability density values
    :param y_val: true labels (anomaly / not)
    :return: best_epsilon - best probability threshold value
             best_f1 - best epsilon's f1 score
    """
    best_epsilon = 0
    best_f1 = 0

    step = (p_val.max() - p_val.min()) / 1000
    for epsilon in np.arange(p_val.min(), p_val.max(), step):  # varying values of epsilon.

        f1 = calc_f1_score(epsilon, p_val, y_val)

        if f1 > best_f1:
            best_f1 = f1
            best_epsilon = epsilon

    return best_epsilon, best_f1

epsilon, f1 = get_optimal_prob_threshold_and_score(p_val, y_val)

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

# Apply the threshold to the dataset and visualize the results:
# The points in red are the ones that were flagged as outliers.
# Visually these seem pretty reasonable.
# The top right point that has some separation (but was not flagged) may be an outlier too, but it's fairly close.

# get the indexes of the values considered to be outliers:
# outliers_indices = np.where(p < epsilon)
outliers_mask = p < epsilon
outliers_indices_and = np.where(np.logical_and(outliers_mask[:, 0], outliers_mask[:, 1]))
outliers_indices_or = np.where(np.logical_or(outliers_mask[:, 0], outliers_mask[:, 1]))

# Visualization
fig, ax = plt.subplots(figsize=(12, 8))
ax.scatter(X[:, 0], X[:, 1])
# ax.scatter(X[outliers_indices[0], 0], X[outliers_indices[0], 1], s=50, color='r', marker='o')
ax.scatter(X[outliers_indices_or, 0], X[outliers_indices_or, 1], s=50, color='r', marker='o')
plt.savefig('/results/anom_det_sup.png')
plt.show()


from sympy.physics.continuum_mechanics.beam import Beam
from sympy import *
from sympy.plotting import plot, PlotGrid
import matplotlib.pyplot as plt

# https://docs.sympy.org/latest/modules/physics/continuum_mechanics/beam_problems.html#example-7

def beam_me_up(rxn, __L, __E, __I, __F, color):

    ## sign convention
    # upward forces and clockwise moment are positive

    # symbols
    L = symbols('L', positive=True)
    E, I, F = symbols('E I F')

    ## definition

    # beam
    b = Beam(L, E, I)

    if rxn == 'fixed-fixed':

        # beam reactions
        R1, R2 = symbols('R1  R2')
        M1, M2 = symbols('M1, M2')
        b.apply_load(R1, 0, -1)
        b.apply_load(M1, 0, -2)
        b.apply_load(R2, L, -1)
        b.apply_load(M2, L, -2)

        # beam load
        b.apply_load(-F, L / 2, -1)

        # beam boundary conditions
        b.bc_deflection = [(0, 0), (L, 0)]
        b.bc_slope = [(0, 0), (L, 0)]

        # solve
        b.solve_for_reaction_loads(R1, R2, M1, M2)

    elif rxn == 'simple-simple':

        # beam reactions
        R1, R2 = symbols('R1  R2')
        b.apply_load(R1, 0, -1)
        b.apply_load(R2, L, -1)

        # beam load
        b.apply_load(-F, L / 2, -1)

        # beam boundary conditions
        b.bc_deflection = [(0, 0), (L, 0)]

        # solve
        b.solve_for_reaction_loads(R1, R2)

    else :
        print("No command defined!")

    # print results
    print('Reaction Loads:')
    pprint(b.reaction_loads)

    print('Load:')
    pprint(b.load)

    print('Max Deflection:')
    pprint(b.max_deflection())

    ## plotting

    # free body diagram
    # https: // docs.sympy.org / latest / modules / physics / continuum_mechanics / beam.html  # sympy.physics.continuum_mechanics.beam.Beam.draw
    # **Note:**: load F will not be rendered correctly if < -1, therefore use subs to make shear, bending, and moment diagrams
    #fbd = b.draw()
    #fbd.show()

    # shear, slope, moment, deflection
    #ax0 = b.plot_loading_results(subs={L: __L, E: __E, I: __I, F: __F})
    #ax1 = b.plot_shear_force()
    #ax2 = b.plot_slope()
    #ax3 = b.plot_bending_moment()
    #ax4 = b.plot_deflection()

    # how to access via backend
    # https://newbedev.com/display-two-sympy-plots-as-two-matplotlib-subplots
    #fig = ax0._backend.fig
    #ax0._backend = ax0.backend(ax0)
    #ax0._backend.process_series()
    #ax0._backend.ax[0].scatter([0, 60, 120], [0, 0, 0], marker='x', color='r')

    # extracting sympy plot data from beam.py for plotting outside of this function
    ax1 = plot(b.shear_force().subs({L: __L, E: __E, I: __I, F: __F}), (b.variable, 0, __L),
               title="Shear Force", line_color=color, xlabel=r'$\mathrm{x}$', ylabel=r'$\mathrm{V\quad(lb)}$', show=False)
    ax2 = plot(b.bending_moment().subs({L: __L, E: __E, I: __I, F: __F}), (b.variable, 0, __L),
               title="Bending Moment", line_color=color, xlabel=r'$\mathrm{x}$', ylabel=r'$\mathrm{M\quad(lb \cdot in)}$', show=False)
    ax3 = plot(b.slope().subs({L: __L, E: __E, I: __I, F: __F}), (b.variable, 0, __L),
               title="Slope", line_color=color, xlabel=r'$\mathrm{x}$', ylabel=r'$\theta$', show=False)
    ax4 = plot(b.deflection().subs({L: __L, E: __E, I: __I, F: __F}), (b.variable, 0, __L),
               title="Deflection", line_color=color, xlabel=r'$\mathrm{x}$', ylabel=r'$\delta\quad(in)$', show=False)

    return ax1, ax2, ax3, ax4

# https://stackoverflow.com/q/63483960
def move_sympyplot_to_axes(p, ax):
    # move axes
    backend = p.backend(p)
    backend.ax = ax
    backend._process_series(backend.parent._series, ax, backend.parent)
    # remove top and right spines
    backend.ax.spines['top'].set_color('none')
    backend.ax.spines['right'].set_color('none')
    # move left and bottom spine to left and bottom of axis extents, previously 'bottom' was set to 'zero'
    backend.ax.spines['left'].set_position(('axes', 0))
    backend.ax.spines['bottom'].set_position(('axes', 0))
    # correct ylabel position, align vertically to center
    ax.yaxis.set_label_coords(-0.1, 0.5)
    plt.close(backend.fig)

## MAIN

beam = {
        "L": {
            "Name": "Length",
            "Value": 120,
            "Unit": "in"
        },
        "Ix": {
            "Name": "Moment of Inertia",
            "Value": [6.04, 8.6, 12.83],
            "Unit": "in4"
        },
        "E": {
            "Name": "Modulus of Elasticity",
            "Material": ["Aluminum", "Steel"],
            "Value": [9900E3, 2.901E7],
            "Unit": "lb/in2"
        },
        "F": {
            "Name": "Point Loads",
            "Value": [2120, 2200],
            "Unit": "lb"
        },
        "RXN": {
            "Name": "Reaction",
            "Value": ["fixed-fixed", "simple-simple"]
        }
}

for i, E in enumerate(beam['E']['Value']):

    MATERIAL = beam['E']['Material'][i]
    L = beam['L']['Value']
    F = beam['F']['Value'][i]

    for j, RXN in enumerate(beam['RXN']['Value']):

        Ix = beam['Ix']['Value']
        colors = ['red', 'blue', 'green']
        p = []

        title = f'Material = {MATERIAL}, Constraints = {RXN}'
        print(title)
        for k, I in enumerate(Ix):

            obj = beam_me_up(RXN, L, E, I, F, colors[k])
            p.append(obj)

            # https://www.spanco.com/blog/understanding-overhead-crane-deflection-and-criteria/
            # I think I could've used the b.max_deflection() formula with subs, but screw it!
            if RXN == 'fixed-fixed':
                delta = (F * L**3) / (192 * E * I)
            elif RXN == 'simple-simple':
                delta = (F * L ** 3) / (48 * E * I)
            allowable = L/450
            passed = False
            if delta < allowable:
                passed = True

            if k == 0:
                print(f'| Ix | δmax | δallowable | Pass |')
                print(f'|----|-----------------------|-----------------------------|------|')

            print(f'| {I:10.2f} | {delta:10.2f} | {allowable:10.2f} | {passed} |')

        # matplotlib overlplotting
        fig, axes = plt.subplots(nrows=4)
        dpi = fig.get_dpi()
        fig.set_size_inches(800.0 / float(dpi), 600.0 / float(dpi))

        for P in p:
            move_sympyplot_to_axes(P[0], axes[0])
            move_sympyplot_to_axes(P[1], axes[1])
            move_sympyplot_to_axes(P[2], axes[2])
            move_sympyplot_to_axes(P[3], axes[3])

        # requirement
        axes[3].axhline(-allowable, linestyle='-', linewidth='1.0', color='magenta', zorder=0)

        # grid/limits/labels
        for ax in axes:
            ax.set_axisbelow(True)
            ax.minorticks_on()
            ax.grid(which='major', linestyle='-', linewidth='0.5', color='gray')
            ax.grid(which='minor', linestyle=':', linewidth='0.5', color='gray')
            ax.set_xlim([0, L])
            #ylabel = ax.yaxis.get_label()
            #ylabel.set_verticalalignment('center')

        # legend
        handles = axes[0].get_legend_handles_labels()[0]  # return first value of function with [0]
        labels = [str(Ix) for Ix in Ix]  # convert list of floats to list of strings
        axes[0].legend(handles, labels, loc='right', title='Moment of Inertia (Ix)', ncol=3)

        # voila
        fig.tight_layout()
        fig.suptitle(title, fontsize=10, x=0.5, y=0.05, color='gray')
        filename = f'./img/result_{MATERIAL.lower()}_{RXN}.png'
        fig.savefig(filename, dpi=100)
        plt.show()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time    : 2022/4/25 2:46 下午
# @File    : polynomial_regression.py
# @author  : Akaya
# @Software: PyCharm
# polynomial_regression  :
import numpy
import matplotlib.pyplot as plt
from sklearn.metrics import r2_score

x = [1, 2, 3, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 18, 19, 21, 22]
y = [100, 90, 80, 60, 60, 55, 60, 65, 70, 70, 75, 76, 78, 79, 90, 99, 99, 100]

mymodel = numpy.poly1d(numpy.polyfit(x, y, 3))

myline = numpy.linspace(1, 22, 100)

plt.scatter(x, y)
plt.plot(myline, mymodel(myline))
plt.show()

print("拟合度：", r2_score(y, mymodel(x)))


"""Assorted utilities for math"""
import math
import numpy as np

# Math
def vec(p1, p2):
    """ vector from p1 to p2 """
    if type(p1) != np.ndarray:
        p1 = np.array(p1)
    if type(p2) != np.ndarray:
        p2 = np.array(p2)
    return p2 - p1

def proj(vec1, vec2, scalar=False):
    # Project vec1 onto vec2. Returns a vector in the direction of vec2.
    scale = np.dot(vec1, vec2) / np.linalg.norm(vec2)
    if scalar:
        return scale
    else:
        return vec2 * scale

def R_x(th):
    return np.array([
        1, 0, 0, 0,
        0, np.cos(th), -np.sin(th), 0,
        0, np.sin(th), np.cos(th), 0,
        0, 0, 0, 1
    ]).reshape(4,4)

def R_y(th):
    return np.array([
        np.cos(th), 0, np.sin(th), 0,
        0, 1, 0, 0,
        -np.sin(th), 0, np.cos(th), 0,
        0, 0, 0, 1
    ]).reshape(4,4)

def R_z(th):
    return np.array([
        np.cos(th), -np.sin(th), 0, 0,
        np.sin(th), np.cos(th), 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    ]).reshape(4,4)

def T(dx, dy, dz):
    return np.array([
        1, 0, 0, dx,
        0, 1, 0, dy,
        0, 0, 1, dz,
        0, 0, 0, 1
    ]).reshape(4,4)

def to_radians(th):
    return th*np.pi / 180

def R_between(v1, v2):
    if len(v1) != 3 or len(v2) != 3:
        raise ValueError("Only applicable to 3D vectors!")
    v = np.cross(v1, v2)
    c = np.dot(v1, v2)
    s = np.linalg.norm(v)
    I = np.identity(3)

    vX = np.array([
        0, -v[2], v[1],
        v[2], 0, -v[0],
        -v[1], v[0], 0
    ]).reshape(3,3)
    R = I + vX + np.matmul(vX,vX) * ((1-c)/(s**2))
    return R

def approx_equal(v1, v2, epsilon=1e-6):
    if len(v1) != len(v2):
        return False
    for i in range(len(v1)):
        if abs(v1[i] - v2[i]) > epsilon:
            return False
    return True

def euclidean_dist(p1, p2):
    return math.sqrt(sum([(a - b)** 2 for a, b in zip(p1, p2)]))


import sympy as sp
import matplotlib.pyplot as plt

xy_values = []

#Initialize x and y values (make sure the X values are in order)
xy_values.append([0, 0])
xy_values.append([10, 227.04])
xy_values.append([15, 362.78])
xy_values.append([20, 517.35])
xy_values.append([22.5, 602.97])

#Initialize divided difference table
def init_table(xy_values):
    table = []

    for _ in range(len(xy_values)):
        temp = []
        for _ in range(len(xy_values) + 1):
            temp.append(-1)
        table.append(temp)

    #Insert x and y values to table
    for i in range(len(xy_values)):
        table[i][0] = xy_values[i][0]
        table[i][1] = xy_values[i][1]

    return table

#Do the divided difference table
def compute_table(table):
    y_bound = 1
    for col in range(2, len(table[0])):
        for row in range(y_bound, len(table)):
            try:
                delta = (table[row][col-1] - table[y_bound-1][col-1]) / (table[row][0] - table[y_bound-1][0])
            except:
                delta = 0
            #print(table[row][col-1], '-', table[y_bound-1][col-1], "divide", table[row][0], '-', table[y_bound-1][0], '=', delta)
            table[row][col] = delta
        y_bound += 1

#Get an values
def get_an_values(table):
    an = []
    col = 1
    for row in range(0, len(table)):
        an.append(table[row][col])
        col += 1
    return an

#create function and simplify it
def get_equation(an, x_symb, xy_values):
    func = 0
    for a in range(len(an)):
        product = an[a]
        for i in range(a):
            product *= (x_symb - xy_values[i][0])
        func += product

    func = sp.simplify(func)

    print(func)

    return func

#putting it together
x = sp.Symbol('x')
table = init_table(xy_values)
compute_table(table)
an = get_an_values(table)
func = get_equation(an, x, xy_values)

#Plotting
def graph_newton(xy_values, func, x_symb):
    #split n and y
    x_values = []
    y_values = []

    for i in range(len(xy_values)):
        x_values.append(xy_values[i][0])
        y_values.append(xy_values[i][1])

    #Generate x and y
    new_x_values = []
    new_y_values = []
    for i in range(int(min(x_values) * 100), int(max(x_values) * 100), 1):
        new_x_values.append(i/100)
        new_y_values.append(func.evalf(subs={x_symb:i/100}))

    plt.plot(x_values, y_values, 'o', label='data')
    plt.plot(new_x_values, new_y_values, '-', label='equation')
    plt.legend()
    plt.xlabel("X")
    plt.ylabel("Y")

    plt.show()

def graph_newton_with_interpolation(xy_values, func, x_symb, xVal, yVal):
    #split n and y
    x_values = []
    y_values = []

    for i in range(len(xy_values)):
        x_values.append(xy_values[i][0])
        y_values.append(xy_values[i][1])

    #Generate x and y
    new_x_values = []
    new_y_values = []
    for i in range(int(min(x_values) * 100), int(max(x_values) * 100), 1):
        new_x_values.append(i/100)
        new_y_values.append(func.evalf(subs={x_symb:i/100}))

    plt.plot(x_values, y_values, 'o', label='data')
    plt.plot(new_x_values, new_y_values, '-', label='equation')
    plt.plot([xVal], [yVal], '+', label="interpolated data")
    plt.legend()
    plt.xlabel("X")
    plt.ylabel("Y")

    plt.show()
graph_newton(xy_values, func, x)

def interpolate_newton_divided_difference(xSymb, xy_values):
    """
    Interpolate using Newton's Divided Difference method.

    Params:
    xSymb - A Sympy Symbol object
    xy_values - A list of your known data points.

    returns the equation of the line.
    """
    table = init_table(xy_values)
    compute_table(table)
    an = get_an_values(table)
    func = get_equation(an, xSymb, xy_values)
    return func


import numpy as np
from si.util.scale import StandardScaler as sc

__all__ = ['PCA']

class PCA:
	def __init__(self, n_comp, PCA_type='svd'):
		self.type = PCA_type
		if n_comp > 0 and isinstance(n_comp, int): self.n_comp = n_comp
		else: Warning('Number of components must an non negative integer.')

	def fit(self):
		pass

	def transform(self, dataset):
		scaled_feature = sc().fit_transform(dataset).X.T  # Normalização com a Classe Standard Scaler
		if self.type.lower() == 'svd': self.u, self.s, self.vh = np.linalg.svd(scaled_feature)
		else:
			self.cov_matrix = np.cov(scaled_feature)
			self.s, self.u = np.linalg.eig(self.cov_matrix)

		self.idxs = np.argsort(self.s)[::-1]  # gera um array com os indexes das colunas ordenadas por importância de compontes
		self.eigen_val, self.eigen_vect = self.s[self.idxs], self.u[:, self.idxs]  # colunas dos valores e dos vetores são reordenadas pelos indexes das colunas
		self.sub_set_vect = self.eigen_vect[:, :self.n_comp]  # gera um conjunto a partir dos vetores e values ordenados
		return scaled_feature.T.dot(self.sub_set_vect)

	def explained_variances(self):
		sum_ = np.sum(self.eigen_val)
		percentage = [i / sum_ * 100 for i in self.eigen_val]
		return np.array(percentage)

	def fit_transform(self, dataset):
		trans = self.transform(dataset)
		exp = self.explained_variances()
		return trans, exp


# Functions related with Shor algorithm

import numpy as np
from math import sqrt
from cmath import exp, pi

# Function f takes N and a as parameters and returns a function f_a,N(x) that returns a^x Mod N
def f(N, a):
	return lambda x: pow(a, x, N)

# Now using map we can apply a function f(N, a) to a list of values [x1, ..., xn].
# i. e.: list(map(f(15, 2), [0, 1, 2, 3]))

# We are trying to find the period of a function f(N, a). We are going to define a function that tries to find the period.
# We call period to the smallest r > 0 with a^r Mod N = 1.
# We won't use map because we don't need to store all the results in memory, we only need to check them one by one.
def findPeriod(N, a):
	f_aN = f(N, a)
	for i in range(1, N):
		if f_aN(i) == 1:
			return i
	return None

# Function that returns an array of nth roots of the unity
def nroots(n, rc = 14): # Rounds to 14 decimal places by default
	c = 2j * pi / n
	gen = (exp(k * c) for k in range(n))
	return [round(i.real, rc) + round(i.imag, rc) * 1j for i in gen]

# Function that creates a vandermonde matrix for the given values
def vandermonde(*args):
	return np.array([[pow(arg, i) for i in range(len(args))] for arg in args])

# Function that creates a vandermonde matrix of the given size with the roots of the unity
def vanderomega(size, rc = 14):
	return vandermonde(*nroots(size, rc))

def DFT(size, rc = 14):
	return 1/sqrt(size) * vanderomega(size, rc)


import cv2
import numpy as np
import matplotlib.pyplot as plt
import cmath

# Define pi
pi = cmath.pi

# Read image
img = cv2.imread('../Images and videos/target.jpg', 0)
cv2.imshow('image',img)

height,width = img.shape
print(height,width)

##---------------------------------Fourier Transform using formula ---------------------------------##

# (177 x 177)
templateM = np.zeros((height,height),dtype=complex)

# (284 x 284)
templateN = np.zeros((width,width),dtype=complex)

# intermediate
intermediate = np.zeros((height,width),dtype=float)

# Applying separability property
# Row transformation
'''
TemplateN shape = (284 x 284)
'''
for x in range(width):
    for y in range(width):
        templateN[x][y] = cmath.exp(-1j*2*pi*x*y/width)

print(templateN.shape)

# Column transformation
'''
TemplateM shape = (177 x 177)
'''
for x in range(height):
    for y in range(height):
        templateM[x][y] = cmath.exp(-1j*2*pi*x*y/height)

print(templateM.shape)

# Multiply row tranformation x original img x column transformation
'''
Intermediate shape = (284 x 284) * (284*177) * (177,177) = resultant shape (284,177)
'''
# Intermediate state (apply tranformation on original image)
intermediate = templateM.dot(img).dot(templateN)

#intermediate = np.array(intermediate,dtype=np.float32)
print(intermediate.shape)

# FFT Shift to make it into the center
ffts = np.fft.fftshift(intermediate);
#ffts = np.array(ffts,dtype=np.float32)

# Log transformation on ffts
def log_transformation(img):
    m = np.max(img)
    try:
        c = int(255/np.log(1+m))
        s = c*np.log(np.abs(1+img))
    except ZeroDivisionError:
        print("Encounter zero division error")

    lf = np.array(s,dtype=np.uint8)
    return lf

magnitude_spectrum = log_transformation(ffts)

# Display plots
'''
images = [img,intermediate,ffts,magnitude_spectrum]
titles = ['original image','Fourier transformed (intermediate)','shifted fourier (ffts)','Fourier transform using formula']
'''
images = [img,magnitude_spectrum]
titles = ['Original images','FT using Formula']
for i in range(2):
    plt.subplot(1,2,(i+1))
    plt.imshow(images[i],'gray')
    plt.xlabel(titles[i])

plt.show()

# Inverse fourier transform

templateM = np.zeros((height,height),dtype=complex)
templateN = np.zeros((width,width),dtype=complex)

# Modification in formula
for x in range(width):
    for y in range(width):
        templateN[x][y] = cmath.exp(1j*2*pi*x*y/width)

for x in range(height):
    for y in range(height):
        templateM[x][y] = cmath.exp(1j*2*pi*x*y/height)

# Intermediate state (apply tranformation on Fourier transformed image)
IFT = templateM.dot(intermediate).dot(templateN) / width*height
#IFT = np.array(IFT,dtype=np.float32)
Original_img = np.abs(IFT)

plt.subplot(131),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(132),plt.imshow(Original_img, cmap = 'gray')
plt.title('Image after IFT using formula'), plt.xticks([]), plt.yticks([])
plt.subplot(133),plt.imshow(Original_img)
plt.title('Result in JET'), plt.xticks([]), plt.yticks([])
plt.show()

##---------------------------------Fourier Transform in numpy ---------------------------------##

f = np.fft.fft2(img)
#f = np.array(f,dtype=np.float32)

fshift = np.fft.fftshift(f)
# fshift = np.array(fshift,dtype=np.float32)

magnitude_spectrum_2 = 20*np.log(np.abs(fshift))
'''
images = [img,f,fshift,magnitude_spectrum_2]
titles = ['original image','Fourier transformed','shifted fourier','Fourier transform using inbuilt function']
'''
images = [img,magnitude_spectrum_2]
titles = ['Original images','FT using inbuilt function']
for i in range(2):
    plt.subplot(1,2,(i+1))
    plt.imshow(images[i],'gray')
    plt.xlabel(titles[i])

plt.show()

# Inverse fourier tranformation using inbuilt method
f_ishift = np.fft.ifftshift(fshift)
img_back = np.fft.ifft2(f_ishift)
img_back = np.abs(img_back)

plt.subplot(131),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(132),plt.imshow(img_back, cmap = 'gray')
plt.title('Image after IFT using inbuilt method'), plt.xticks([]), plt.yticks([])
plt.subplot(133),plt.imshow(img_back)
plt.title('Result in JET'), plt.xticks([]), plt.yticks([])

plt.show()

cv2.waitKey(0)
cv2.destroyAllWindows()

src/19 - Fourier transform/Fourier_transformation_and_Inverse_fourier_tranformation.py


import numpy as np
import pylab

class Point:
    def __init__(self, x, y):
        self.x = float(x)
        self.y = float(y)

def get_spline(points):
    N = len(points) - 1
    w = [(points[i + 1].x - points[i].x) for i in range(N)]
    h = [(points[i + 1].y - points[i].y) / w[i] for i in range(N)]
    ftt = [0] + [3 * (h[i + 1] - h[i]) / (w[i + 1] + w[i]) for i in range(N - 1)] + [0]
    A = [(ftt[i + 1] - ftt[i]) / (6 * w[i]) for i in range(N)]
    B = [ftt[i] / 2 for i in range(N)]
    C = [h[i] - w[i] * (ftt[i + 1] + 2 * ftt[i]) / 6 for i in range(N)]
    D = [points[i].y for i in range(N)]
    return A, B, C, D

def print_spline(points, A, B, C, D):
    for i in range(len(points) - 1):
        func = str(points[i].x) + ' <= x <= ' + str(points[i + 1].x) + ' : f(x) = '
        components = []
        if A[i]:
            components.append(f"{round(A[i], 4)} * (x-{points[i].x})^3")
        if B[i]:
            components.append(f"{round(B[i], 4)} * (x-{points[i].x})^2")
        if C[i]:
            components.append(f"{round(C[i], 4)} * (x-{points[i].x})")
        if D[i]:
            components.append(str(round(D[i], 4)))
        if components:
            func += components[0]
            for i in range(1, len(components)):
                if components[i][0] == '-':
                    func += ' - ' + components[i][1:]
                else:
                    func += ' + ' + components[i]
            print(func)
        else:
            print(func + '0')

def build_interpolated_func(point, A, B, C, D):
     return lambda x: (A * ((x-point.x)**3)) + \
               (B * ((x-point.x)**2)) + \
               (C * (x-point.x)) + D

def build_all_interpolated_funcs(points, A, B, C, D):
    funcs = []
    for i in range(len(points) - 1):
        funcs.append(build_interpolated_func(points[i], A[i], B[i], C[i], D[i]))
    return funcs

def draw_splines(points, funcs):
    for i, func in enumerate(funcs):
        x_dots = np.linspace(points[i].x, points[i+1].x)
        y_dots = [func(x) for x in x_dots]
        pylab.plot(x_dots, y_dots, label=f"S{i+1}")
    for point in points:
        pylab.scatter(point.x, point.y, c='r')
    pylab.xlabel('X', fontsize=16)
    pylab.ylabel('Y', fontsize=16)
    pylab.xticks(fontsize=12)
    pylab.yticks(fontsize=12)
    pylab.grid()
    pylab.legend(fontsize=12)
    pylab.show()

def draw_spline_by_func(func):
    x_dots = np.linspace(-1, 2)
    y_dots = [func(x) for x in x_dots]
    pylab.plot(x_dots, y_dots, label="""analyzed function""")

    # half_x = np.arange(-1, 2.5, 0.5)
    # half_points = []
    # for x in half_x:
    #     half_points.append(Point(x, func(x)))
    # A, B, C, D = get_spline(half_points)
    # funcs = build_all_interpolated_funcs(half_points, A, B, C, D)
    # for i, func in enumerate(funcs):
    #     x_dots = np.linspace(half_points[i].x, half_points[i+1].x)
    #     y_dots = [func(x) for x in x_dots]
    #     pylab.plot(x_dots, y_dots, linestyle='dotted', c="g")
    # for point in half_points:
    #     pylab.scatter(point.x, point.y, c='r')

    quad_x = np.arange(-1, 2.2, 0.25)
    quad_points = []
    for x in quad_x:
        quad_points.append(Point(x, func(x)))
    A, B, C, D = get_spline(quad_points)
    funcs = build_all_interpolated_funcs(quad_points, A, B, C, D)
    for i, func in enumerate(funcs):
        x_dots = np.linspace(quad_points[i].x, quad_points[i + 1].x)
        y_dots = [func(x) for x in x_dots]
        pylab.plot(x_dots, y_dots, linestyle='dotted', c="y")
    for point in quad_points:
        pylab.scatter(point.x, point.y, c='b')

    pylab.xlabel('X', fontsize=16)
    pylab.ylabel('Y', fontsize=16)
    pylab.xticks(fontsize=12)
    pylab.yticks(fontsize=12)
    pylab.grid()
    pylab.legend(fontsize=12)
    pylab.show()


import numpy as np

p = np.array([2,3,1]) # Cria o vetor "p" do nosso exemplo.
q = np.array([1,2,-2]) # Cria o vetor "q" do nosso exemplo.

# Aplica a multiplicação cruzada com a função cross().
r = np.cross(p,q)

print(r)


"""
File: pylinex/basis/GramSchmidtBasis.py
Author: Keith Tauscher
Date: 3 Sep 2017

Description: File containing subclass of Basis which implements a set of basis
             vectors created through a Gram-Schmidt orthogonalization
             procedure.
"""
from __future__ import division
import numpy as np
from ..expander import NullExpander
from .Basis import Basis

def orthonormal_basis(seed_vectors, error=None):
    """
    Creates an orthonormal basis from the given seed vectors using a
    Gram-Schmidt orthogonalization procedure.

    seed_vectors: vectors with which to generate new modes using the
                  Gram-Schmidt orthogonalization procedure
    error: 1D numpy.ndarray containing error values with which to define
           'orthogonal'

    returns: a set of basis vectors. a numpy.ndarray of the same shape as the
             seed vectors
    """
    if type(error) is type(None):
        error = np.ones_like(seed_vectors[0])
    inverse_error = 1 / error
    covariance = np.diag(error ** 2)
    inverse_covariance = np.diag(inverse_error ** 2)
    correction_matrix = inverse_covariance.copy()
    def dot_product(u, v):
        return np.dot(u * inverse_error, v * inverse_error)
    basis = []
    for degree in range(len(seed_vectors)):
        pbv = seed_vectors[degree]
        cpbv = np.dot(pbv, correction_matrix)
        basis.append(np.dot(covariance, cpbv) / np.sqrt(np.dot(pbv, cpbv)))
        CinvF = np.reshape(np.dot(inverse_covariance, basis[-1]), (-1, 1))
        correction_matrix -= np.dot(CinvF, CinvF.T)
    return np.array(basis)

def orthonormal_polynomial_basis(num_points, max_degree, error=None):
    """
    Creates a new Legendre polynomial-like basis with the given number of
    points and basis vectors.

    num_points: number of points to include in the basis vectors
    max_degree: one less than the number of basis vectors desired
    error: 1D numpy.ndarray with which to define 'orthogonal'

    returns: 2D numpy.ndarray of shape (md+1,np) where md is the max_degree
             given and np is the num_points given
    """
    xs = np.linspace(-1, 1, num_points)
    seed_vectors = [(xs ** degree) for degree in range(max_degree + 1)]
    return orthonormal_basis(seed_vectors, error=error)

def orthonormal_harmonic_basis(num_points, max_degree, error=None):
    """
    Creates a new Fourier series-like basis with the given number of
    points and basis vectors.

    num_points: number of points to include in the basis vectors
    max_degree: determines number of basis vectors returned
    error: 1D numpy.ndarray with which to define 'orthogonal'

    returns: 2D numpy.ndarray of shape (2*md+1,np) where md is the max_degree
             given and np is the num_points given
    """
    xs = np.linspace(-np.pi, np.pi, num_points)
    num_basis = (2 * max_degree) + 1
    def vector(index):
        scaled_xs = (((index + 1) // 2) * xs)
        is_sin = bool(index % 2)
        if is_sin:
            return np.sin(scaled_xs)
        else:
            return np.cos(scaled_xs)
    seed_vectors = np.array([vector(i) for i in range(num_basis)])
    return orthonormal_basis(seed_vectors, error=error)

class GramSchmidtBasis(Basis):
    """
    Class representing a Basis object whose vectors are calculated through a
    Gram-Schmidt orthogonalization procedure.
    """
    def __init__(self, seed_vectors, error=None, expander=None,\
        translation=None):
        """
        Initializes the new GramSchmidtBasis object.

        seed_vectors: the vectors with which to generate further modes through
                      the Gram-Schmidt orthogonalization procedure
        error: the error with which to define 'orthogonal'
        expander: if None, no expansion is applied
                  otherwise, expander must be an Expander object.
        translation: if None, no constant additive is included in the results
                              of calling this Basis.
                     otherwise: should be a 1D numpy.ndarray of the same length
                                as the seed vectors. It is the (unexpanded)
                                result of calling the basis on a zero-vector of
                                parameters
        """
        if isinstance(seed_vectors, Basis):
            basis = orthonormal_basis(seed_vectors.basis, error=error)
        else:
            basis = orthonormal_basis(seed_vectors, error=error)
        Basis.__init__(self, basis, expander=expander, translation=translation)

    @property
    def even_subbasis(self):
        """
        Property storing the subbasis of this one which contains only the even
        functions (as long as the even-index seed vectors given to the
        initializer were even). Returns Basis object.
        """
        return self[::2]

    @property
    def odd_subbasis(self):
        """
        Property storing the subbasis of this one which contains only the odd
        functions (as long as the odd-index seed vectors given to the
        initializer were odd). Returns Basis object.
        """
        return self[1::2]


import math
import typing
from itertools import product

import numpy as np

from .matrix import Matrix, MatrixOperator

QBIT_MATRICES = {
    "0": [1.0, 0.0],
    "1": [0.0, 1.0],
    "+": [1 / math.sqrt(2.0), 1 / math.sqrt(2.0)],
    "-": [1 / math.sqrt(2.0), -1 / math.sqrt(2.0)],
}
EPSILON = 0.00001

class States:
    """Class for handling operations on qbit states."""

    @staticmethod
    def decode_state(qbit_representation: str) -> np.ndarray:
        """
        Convert string representation of qbit (ex. |01>, |+>, |->, |001>, |+++>) to matrix form.

        :param qbit_representation: string started with | and ended with >
                                     containing any number of 0, 1, + and -
        :return: matrix containing float values of qbit('s). Size of matrix is determined by
                 length of symbols. It will always contain pow(2, len(qbit_representation)-2)
        :raises ValueError: when qbit_representation does not have at least 1 character e.g. "|>"
        :raises RuntimeError: when possibilities matrix does not sum to 1
        """

        def strip_braket_signs():
            return qbit_representation[2:-1] if negative else qbit_representation[1:-1]

        if len(qbit_representation) < 3:
            raise ValueError("Qbit string representation has to have at least 1 character e.g. |1>")

        negative = qbit_representation[0] == "-"
        qbit_representation = strip_braket_signs()

        first_qbit = qbit_representation[0]
        current_matrix = Matrix(QBIT_MATRICES[first_qbit])
        qbit_representation = qbit_representation[1:]

        for qbit in qbit_representation:
            current_matrix = MatrixOperator.kronecker_product(current_matrix, Matrix(QBIT_MATRICES[qbit]))

        if negative:
            current_matrix = Matrix(np.negative(current_matrix.value))

        if 1 - np.sum(np.square(current_matrix.value)) > EPSILON:
            raise RuntimeError("Possibilities matrix does not sum to 1")
        return current_matrix.value

    @staticmethod
    def encode_state(matrix_representation: np.ndarray) -> typing.Optional[str]:
        """Convert matrix representation of qbit to string form.

        :param matrix_representation: single dimensional matrix with one column and amount of rows being power of two
        :return: string representation of qbit, if possible to describe without precision losses otherwise return None
        :raises ValueError: when no matching braket representation was found
        :raises RuntimeError: when more than one braket representation was found
        """
        braket_length = int(math.log2(matrix_representation.size))

        possible_braket_representations = [
            "|" + "".join(s) + ">" for s in product(QBIT_MATRICES.keys(), repeat=braket_length)
        ] + ["-|" + "".join(s) + ">" for s in product(QBIT_MATRICES.keys(), repeat=braket_length)]
        matches_found = []

        for braket in possible_braket_representations:
            if np.allclose(States.decode_state(braket), matrix_representation):
                matches_found.append(braket)
        if not matches_found:
            raise ValueError("No braket representation was found")

        if len(matches_found) > 1:
            raise RuntimeError("More than one braket representation was found")

        return matches_found[0]


import numpy as np

def softmax(L: np.ndarray) -> np.ndarray:
    assert len(L.shape) == 2

    # Numerically stable softmax function
    m = np.amax(L, axis=1)[:, None]
    X = np.exp(L - m)
    P = X / X.sum(axis=1)[:, None]

    return P


from scipy.spatial.distance import pdist, squareform
from scipy import exp
from scipy.linalg import eigh
import numpy as np

def rbf_kernel_pca(X, gamma, n_components):
    '''
    RBF kernel PCA implementation.

    Parameters
    --------------
    X: {NumPy ndarray}, shape = [n_samples, n_features]

    gamma: float
        Tuning parameter of the RBF kernel

    n_components: int
        Number of principal components to return

    Returns
    -----------
        X_pc: {NumPy ndarray}, shape = [n_samples, k_features]
            Projected dataset
    '''

    # Calculate pairwise squared Euclidean distances in the MxN dimensional dataset
    sq_dists = pdist(X, 'sqeuclidean')

    # Convert pairwise distances into a square matrix.
    mat_sq_dists = squareform(sq_dists)

    # Compute the symmetric kernel matrix.
    K = exp(-gamma * mat_sq_dists)

    # Center the kernel matrix.
    N = K.shape[0]
    one_n = np.ones((N,N)) / N
    K = K - one_n.dot(K) - K.dot(one_n) + one_n.dot(K).dot(one_n)

    # Obtaining eigenpairs from the centered kernel matrix
    # numpy.eigh returns them in sorted order.
    eigvals, eigvecs = eigh(K)

    # Collect the top k eigenvectors (projected samples)
    X_pc = np.column_stack((eigvecs[:,-i] for i in range(1, n_components+1)))

    # Collect the corresponding eigenvalues
    lambdas = [eigvals[-i] for i in range(1, n_components+1)]

    return X_pc, lambdas

def project_x(x_new, X, gamma, alphas, lambdas):
    pair_dist = np.array([np.sum((x_new-row)**2) for row in X])
    k = np.exp(-gamma*pair_dist)
    return k.dot(alphas / lambdas)

if __name__ == '__main__':
    from sklearn.datasets import make_moons
    X, y = make_moons(n_samples=100, random_state=123)
    import matplotlib.pyplot as plt
    # plt.scatter(X[y==0, 0], X[y==0, 1],
    #             color='red', marker='^', alpha=0.5)
    # plt.scatter(X[y==1,0], X[y==1, 1],
    #             color='blue', marker='o', alpha=0.5)
    # plt.show()

    from sklearn.decomposition import PCA
    scikit_pca = PCA(n_components=2)
    X_spca = scikit_pca.fit_transform(X)
    fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(7,4))
    ax[0].scatter(X_spca[y==0,0], X_spca[y==0,1], color='red', marker='^', alpha=0.5)
    ax[0].scatter(X_spca[y==1,0], X_spca[y==1,1], color='blue', marker='o', alpha=0.5)
    ax[1].scatter(X_spca[y==0,0], np.zeros((50,1))+0.02, color='red', marker='^', alpha=0.5)
    ax[1].scatter(X_spca[y==1,0], np.zeros((50,1))-0.02, color='blue', marker='o', alpha=0.5)
    ax[0].set_xlabel('PC1')
    ax[0].set_ylabel('PC2')
    ax[1].set_ylim([-1,1])
    ax[1].set_yticks([])
    ax[1].set_xlabel('PC1')
    plt.show()

    from matplotlib.ticker import FormatStrFormatter
    X_kpca, lambdas = rbf_kernel_pca(X, gamma=15, n_components=2)
    fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(7,4))
    ax[0].scatter(X_kpca[y==0,0], X_kpca[y==0,1], color='red', marker='^', alpha=0.5)
    ax[0].scatter(X_kpca[y==1,0], X_kpca[y==1,1], color='blue', marker='o', alpha=0.5)
    ax[1].scatter(X_kpca[y==0,0], np.zeros((50,1))+0.02, color='red', marker='^', alpha=0.5)
    ax[1].scatter(X_kpca[y==1,0], np.zeros((50,1))-0.02, color='blue', marker='o', alpha=0.5)
    ax[0].set_xlabel('PC1')
    ax[0].set_ylabel('PC2')
    ax[1].set_ylim([-1,1])
    ax[1].set_yticks([])
    ax[1].set_xlabel('PC1')
    ax[0].xaxis.set_major_formatter(FormatStrFormatter('%0.1f'))
    ax[1].xaxis.set_major_formatter(FormatStrFormatter('%0.1f'))
    plt.show()

    X_kpca, lambdas = rbf_kernel_pca(X, gamma=15, n_components=1)
    x_new = X[25]
    x_proj = X_kpca[25]
    x_reproj = project_x(x_new, X, gamma=15, alphas=X_kpca, lambdas=lambdas)
    plt.scatter(X_kpca[y==0, 0], np.zeros((50)), color='red', marker='^', alpha=0.5)
    plt.scatter(X_kpca[y==1, 0], np.zeros((50)), color='blue', marker='o', alpha=0.5)
    plt.scatter(x_proj, 0, color='black',
                label='original projection of point X[25]', marker='^', s=100)
    plt.scatter(x_reproj, 0, color='green',
                label='remapped point X[25]', marker='x', s=500)
    plt.legend(scatterpoints=1)
    plt.show()

    from sklearn.decomposition import KernelPCA
    scikit_kpca = KernelPCA(n_components=2, kernel="rbf", gamma=15)
    X_skernpca = scikit_kpca.fit_transform(X)
    plt.scatter(X_skernpca[y==0, 0], X_skernpca[y==0, 1],
                color='red', marker='^', alpha=0.5)
    plt.scatter(X_skernpca[y==1, 0], X_skernpca[y==1, 1],
                color='blue', marker='o', alpha=0.5)
    plt.xlabel('PC1')
    plt.ylabel('PC2')
    plt.show()


# -*- coding: utf-8 -*-
"""
Created on Mon Dec 14 10:01:06 2020

@author: Larisa
"""

import numpy as np
import matplotlib.pyplot as plt
import sympy as sym

# Interpolare Spline Liniară
def SplineLiniar(X, Y, x):
    n = len(X) - 1
    a = np.zeros((n, 1))
    b = np.zeros((n, 1))

    for i in range(n):
        a[i] = Y[i]
        b[i] = (Y[i + 1] - Y[i]) / (X[i + 1] - X[i])

    S = np.zeros((len(x), 1))
    for i in range(len(x)):
        for j in range(n):
            if x[i] >= X[j] and x[i] <= X[j + 1]:
                S[i] = a[j] + b[j] * (x[i] - X[j])
                break

    return S, a, b

def f(x):
    return np.sin(x)

def f1(x):
    return 1 / (1 + 25 * x**2)

n = 100
x_min = - np.pi / 2
x_max = np.pi / 2
X = np.linspace(x_min, x_max, n + 1)
Y = f1(X)
x_graf = np.linspace(x_min, x_max, 100)
y_graf = f1(x_graf)

S_graf, a, b = SplineLiniar(X, Y, x_graf)
print(S_graf)
print(f'a: \n{a}')
print(f'b: \n{b}')

plt.plot(x_graf, y_graf, color = 'red', linewidth = 3)
plt.plot(x_graf, S_graf, '--', color = 'blue', linewidth = 3)
plt.plot(X, Y, 'o', markersize = 10, markerfacecolor = 'yellow')
plt.show()

err_graf = abs(y_graf - S_graf.reshape(-1, ))
plt.plot(x_graf, err_graf, color = 'purple', linewidth = 3)
plt.show()

# Interpolare Spline Pătratică
def SplinePatratica(X, Y, x, dfa):
    n = len(X) - 1
    a = np.zeros((n, 1))
    b = np.zeros((n, 1))
    h = np.zeros((n, 1))
    c = np.zeros((n, 1))

    for i in range(n):
        a[i] = Y[i]
        h[i] = X[i + 1] - X[i]

    b[0] = dfa  # f prim de x1
    for i in range(n - 1):
        b[i + 1] = 2 / h[i] * (Y[i + 1] - Y[i]) - b[i]
        c[i] = 1 / h[i]**2 * (Y[i + 1] - Y[i] - h[i] * b[i])
    c[n - 1] = 1 / h[n - 1]**2 * (Y[n] - Y[n - 1] - h[n - 1] * b[n - 1])

    dS = np.zeros((len(x), 1))
    S = np.zeros((len(x), 1))
    for i in range(len(x)):
        for j in range(n):
            if x[i] >= X[j] and x[i] <= X[j + 1]:
                S[i] = a[j] + b[j] * (x[i] - X[j]) + c[j] * (x[i] - X[j]) ** 2
                dS[i] = b[j] + 2 * c[j] * (x[i] - X[j])
                break

    return S, dS, a, b, c

n = 2
x_min = - np.pi / 2
x_max = np.pi / 2
X = np.linspace(x_min, x_max, n + 1)
Y = f1(X)
x_graf = np.linspace(x_min, x_max, 100)
y_graf = f1(x_graf)

x = sym.symbols('x')
f_sym = f1(x)
d_sym = sym.diff(f_sym, x) # expresie simbolică
d = sym.lambdify(x, d_sym, 'numpy') # funcție
dfa = d(x_min)

S_graf, dS_graf, a, b, c = SplinePatratica(X, Y, x_graf, dfa)
print(S_graf)
print(f'a: \n{a}')
print(f'b: \n{b}')
print(f'c: \n{c}')

plt.plot(x_graf, y_graf, color = 'red', linewidth = 3)
plt.plot(x_graf, S_graf, '--', color = 'blue', linewidth = 3)
plt.plot(X, Y, 'o', markersize = 10, markerfacecolor = 'yellow')
plt.show()

err_graf = abs(y_graf - S_graf.reshape(-1, ))
plt.plot(x_graf, err_graf, color = 'purple', linewidth = 3)
plt.show()

plt.plot(x_graf, dS_graf, color = 'purple', linewidth = 3)
plt.show()

Calcul Numeric (CN)/Laborator/Laborator 9, 10/lab10.py


import numpy as np

def var_index_and_fractal_dim(low, high):
    """Calculates the variation index and fractal dimension of a financial time series.

    Arguments:
    low -- 1-D array like of lowest prices in the bars
    high -- 1-D array like of highest prices in the bars

    Returns:
    dict of
        v_indx -- variation index of the data
        fractal_dim -- fractal dimension of the data
        V_sigma -- variation with interval size sigma
        sigma -- actual interval size, units of sigma is index

    A minimum of 128 datapoints are recommended for best results.
    Implements method outlined in this paper: http://spkurdyumov.narod.ru/Dubovikov1.pdf
    """
    sigma = 2 ** np.arange(3,6)

    left_over = len(low) % (2 ** 5)
    low = low[left_over:]
    high = high[left_over:]

    lows = []
    highs = []

    for i in range(0, 3):
        rows = 2**(i+3)
        cols = len(low)/(2**(i+3))
        lows.append(np.reshape(low, (rows, cols), order='F'))
        highs.append(np.reshape(high, (rows, cols), order='F'))

    V_sigma = np.zeros(3)
    for i in range(0, 3):
        ampMin = np.min(lows[i], axis=0)
        print(ampMin.shape)
        ampMax = np.max(highs[i], axis=0)
        print(ampMax.shape)
        V_sigma[i] = np.sum(ampMax-ampMin)

    p = np.polyfit(np.log((2 ** np.arange(3,6))), np.log(V_sigma), 1)

    v_indx = -p[0]

    fractal_dim = v_indx + 1

    return {'v_indx': v_indx, 'fractal_dim': fractal_dim, 'V_sigma': V_sigma, 'sigma': sigma}


## module inversePower
''' lam,x = inversePower(a,s,tol=1.0e-6).
    Inverse power method for solving the eigenvalue problem
    [a]{x} = lam{x}. Returns 'lam' closest to 's' and the
    corresponding eigenvector {x}.
'''
import numpy as np
from LUdecomp import *
import math
from random import random
def inversePower(a,s,tol=1.0e-6):
    n = len(a)
    aStar = a - np.identity(n)*s  # Form [a*] = [a] - s[I]
    aStar = LUdecomp(aStar)       # Decompose [a*]
    x = np.zeros(n)
    for i in range(n):            # Seed [x] with random numbers
        x[i] = random()
    xMag = math.sqrt(np.dot(x,x)) # Normalize [x]
    x =x/xMag
    for i in range(50):           # Begin iterations
        xOld = x.copy()           # Save current [x]
        x = LUsolve(aStar,x)      # Solve [a*][x] = [xOld]
        xMag = math.sqrt(np.dot(x,x)) # Normalize [x]
        x = x/xMag
        if np.dot(xOld,x) < 0.0:  # Detect change in sign of [x]
            sign = -1.0
            x = -x
        else: sign = 1.0
        if math.sqrt(np.dot(xOld - x,xOld - x)) < tol:
            return s + sign/xMag,x
    print('Inverse power method did not converge')


#!/usr/bin/env python
import math
import numpy as np

# log Gaussian with mean -2.0 and var 3.0
def lgaussian(s):
  x0 = s["Parameters"][0]
  r = -0.5 * ((x0 + 2.0)**2 / (9.0)) - 0.5 * math.log(2 * math.pi * 9)
  s["logP(x)"] = r
  s["grad(logP(x))"] = [-(x0 + 2.0) / 9.0]
  s["H(logP(x))"] = [[-1.0 / 9.0]]

def lgaussianCustom(s):
  x0 = s["Parameters"][0]
  r = -0.5 * ((x0 + 2.0)**2 / (9.0)) - 0.5 * math.log(2 * math.pi * 9)
  s["logLikelihood"] = r

# log Gaussian in d dimension with mean 0 and var 1
def lgaussianxd(s, d):
  ss = 0.0
  for i in range(d):
    ss += s["Parameters"][i]**2
  r = -0.5 * ss
  s["logP(x)"] = r
  grad = []
  for i in range(d):
    grad.append(-s["Parameters"][i])
  s["grad(logP(x))"] = grad
  s["H(logP(x))"] = (-np.identity(d)).tolist()

def lgaussianxdCustom(s, d):
  ss = 0.0
  for i in range(d):
    ss += s["Parameters"][i]**2
  r = -0.5 * ss
  s["logLikelihood"] = r

# log exponential with mean 4
def lexponential(s):
  lam = 1. / 4.
  x0 = s["Parameters"][0]
  r = 0.0
  if (x0 < 0):
    r = -1000.0
  else:
    r = math.log(lam) - lam * x0
  s["logP(x)"] = r
  s["grad(logP(x))"] = [-lam]
  s["H(logP(x))"] = [[0.0]]

def lexponentialCustom(s):
  lam = 1. / 4.
  x0 = s["Parameters"][0]
  r = 0.0
  if (x0 < 0):
    r = -1000.0
  else:
    r = math.log(lam) - lam * x0
  s["logLikelihood"] = r

# log laplace with mean 4 and scale 1 (var 2 = 2*scale)
def llaplace(s):
  x0 = s["Parameters"][0]
  mu = 4.0
  scale = 1
  r = -math.log(2.0 * scale) - abs(x0 - mu) / scale
  s["logP(x)"] = r
  s["grad(logP(x))"] = [-np.sign(x0 - mu) / scale]
  s["H(logP(x))"] = [[0.0]]

def llaplaceCustom(s):
  x0 = s["Parameters"][0]
  mu = 4.0
  scale = 1
  r = -math.log(2.0 * scale) - abs(x0 - mu) / scale
  s["logLikelihood"] = r


import sympy as sp

# https://www.youtube.com/watch?v=5qJY-ZaKSic&t=320s&ab_channel=BriannoColler

parameters = sp.Matrix(sp.symbols("m_c, m_p, L, g"))
# parameters = sp.MatrixSymbol('parameters', 4, 1)
m_c, m_p, L, g = parameters

x, theta, x_dot, theta_dot, x_ddot, theta_ddot = sp.symbols("x, theta, x_dot, theta_dot, x_ddot, theta_ddot")
state_sim = sp.Matrix([x, theta, x_dot, theta_dot])
state_ctrl = sp.Matrix([theta, x_dot, theta_dot])
state_ctrl_2 = sp.Matrix([theta, theta_dot])

F = sp.symbols("F")
inputs = sp.Matrix([F])

t_sym = sp.symbols("t_sym")

sin = sp.sin
cos = sp.cos

# -m_p * g * sin(theta) == m_p*x_ddot*cos(theta) - m_p * L * theta_ddot
# F + m_p*L*theta_ddot*cos(theta) - m_p * L * theta_dot * theta_dot * sin(theta) == (m_c + m_p) * x_ddot

expr = sp.Matrix([
    m_p * g * sin(theta) + m_p*x_ddot*cos(theta) - m_p * L * theta_ddot,
    F + m_p*L*theta_ddot*cos(theta) - m_p * L * theta_dot * theta_dot * sin(theta) - (m_c + m_p) * x_ddot
])

res = sp.solve(expr, (x_ddot, theta_ddot))

state_sim_dot = sp.Array([x_dot, theta_dot, res[x_ddot], res[theta_ddot]])
state_ctrl_dot = sp.Array([theta_dot, res[x_ddot], res[theta_ddot]])
state_ctrl_2_dot = sp.Array([theta_dot, res[theta_ddot]])

model_sim_lnp = sp.lambdify((state_sim, t_sym, parameters, inputs), state_sim_dot, 'numpy')
model_ctrl_lnp = sp.lambdify((state_ctrl, t_sym, parameters, inputs), state_ctrl_dot, 'numpy')
model_ctrl_2_lnp = sp.lambdify((state_ctrl_2, t_sym, parameters, inputs), state_ctrl_2_dot, 'numpy')

print(state_sim_dot.__repr__())
print(state_ctrl_dot.__repr__())
print(state_ctrl_2_dot.__repr__())

stat_point_ctrl_2 = sp.solve(sp.Matrix(state_ctrl_2_dot))

# %%

import numpy as np

d_model_d_state_sim = sp.lambdify((state_sim, t_sym, parameters, inputs), sp.Matrix(state_sim_dot).jacobian(state_sim), 'sympy')
d_model_d_state_ctrl = sp.lambdify((state_ctrl, t_sym, parameters, inputs), sp.Matrix(state_ctrl_dot).jacobian(state_ctrl), 'sympy')
d_model_d_state_ctrl_2 = sp.lambdify((state_ctrl_2, t_sym, parameters, inputs), sp.Matrix(state_ctrl_2_dot).jacobian(state_ctrl_2), 'sympy')

d_model_d_inputs_sim = sp.lambdify((state_sim, t_sym, parameters, inputs), sp.Matrix(state_sim_dot).jacobian(inputs), 'sympy')
d_model_d_inputs_ctrl = sp.lambdify((state_ctrl, t_sym, parameters, inputs), sp.Matrix(state_ctrl_dot).jacobian(inputs), 'sympy')
d_model_d_inputs_ctrl_2 = sp.lambdify((state_ctrl_2, t_sym, parameters, inputs), sp.Matrix(state_ctrl_2_dot).jacobian(inputs), 'sympy')

y0 = sp.Array([
    0.0,
    0.0,
    0.0,
    0.0
])

u0 = sp.Array([
    F,
])

params = parameters

# params = sp.Array([
#     0.3,
#     1.0,
#     1.0,
#     9.81,
# ])

t_s = sp.Array([0.0])

A_sim = d_model_d_state_sim(y0, t_s, params, u0)
B_sim = d_model_d_inputs_sim(y0, t_s, params, u0)

A_ctrl = d_model_d_state_ctrl(y0[1:], t_s, params, u0)
B_ctrl = d_model_d_inputs_ctrl(y0[1:], t_s, params, u0)

A_ctrl_2 = d_model_d_state_ctrl_2([0, theta], t_s, params, sp.Array([stat_point_ctrl_2[0][F]]))
B_ctrl_2 = d_model_d_inputs_ctrl_2([0, theta], t_s, params, sp.Array([stat_point_ctrl_2[0][F]]))

# A_ctrl_2 = d_model_d_state_ctrl_2(y0[1:], t_s, params, u0)
# B_ctrl_2 = d_model_d_inputs_ctrl_2(y0[1:], t_s, params, u0)

print(A_ctrl.__repr__())
print(B_ctrl.__repr__())

A_sim_lnp = sp.lambdify((parameters,), A_sim, 'numpy')
B_sim_lnp = sp.lambdify((parameters,), B_sim, 'numpy')

A_ctrl_lnp = sp.lambdify((parameters,), A_ctrl, 'numpy')
B_ctrl_lnp = sp.lambdify((parameters,), B_ctrl, 'numpy')

# np.linalg.eig(np.array(A_sim).astype(np.float64))
# np.linalg.eig(np.array(A_ctrl).astype(np.float64))

# %% Codegen stuff starts here:
# https://www.sympy.org/scipy-2017-codegen-tutorial/notebooks/07-the-hard-way.html

# print(sp.printing.cxxcode(state_dot, assign_to="res", standard="C++11"))

# %%

# from sympy.printing.codeprinter import Assignment
# from sympy.printing.ccode import C99CodePrinter
# from sympy.printing.cxxcode import CXX17CodePrinter
#
# class CMatrixPrinter(CXX17CodePrinter):
#     def _print_ImmutableDenseMatrix(self, expr):
#         sub_exprs, simplified = sp.cse(expr)
#         lines = []
#         for var, sub_expr in sub_exprs:
#             lines.append('double ' + self._print(Assignment(var, sub_expr)))
#         M = sp.MatrixSymbol('M', *expr.shape)
#         return '\n'.join(lines) + '\n' + self._print(Assignment(M, simplified[0]))
#
#
# p = CMatrixPrinter()
# print(p.doprint(state_dot))

# %%

# from sympy.utilities.codegen import codegen
# from sympy.codegen.rewriting import optimize, optims_c99, create_expand_pow_optimization
#
# expand_opt = create_expand_pow_optimization(3)
#
# expand_opt(res[x_ddot])

# [(c_name, c_code), (h_name, c_header)] = \
#     codegen(('state_dot', state_dot), "C99", "test", header=False, empty=True)
#
# print("", c_code)


"""
Algorithms for performing numerical optimization of (typically) convex functions
by estimating descent directions and updating parameters

All inputs to the algorithms are expected to be/accept NumPy arrays.

Algorithms receive some function as a parameter i.e. g, h. These functions are expected to
correspond to "mathematical" functions and take as input a single numpy array, of any dimension. This
argument should correspond to the parameter that you are seeking to optimize i.e. Theta, etc.

Plan code accordingly. The factory pattern is very useful for creating functions that have the correct signature at
runtime.
"""
import numpy as np

def gradient_descent(g, initial_point, alpha, iterations):
    """
    Performs gradient descent on a function. Takes the gradient of that function
    at a given parameter point and updates that parameter by some small fraction of
    the gradient value.

        X_1 = X_0 - alpha*g(X_0)

    where alpha is some small value.
    :param g: a Function(array) defining the gradient of the optimization target with respect to the optimization parameter
    :param initial_point: an Array representing the start value for the optimization parameter
    :param alpha: some small value, typically 0.01 is a good starting point but for clarity, the user is expected to
    provide a value. For ease of use, you might wrap this function with some default argument for alpha
    :param iterations: the number of iterations of gradient descent to perform
    :return: X, an Array of the final parameter values obtained
    :return: history, a List[Array] of all values of x. Useful for plotting the algorithms performance in the parameter
    space
    """
    history = [initial_point]
    X = initial_point

    for i in range(iterations):
        g_i = g(X)
        X_prime = X - alpha*g_i
        history.append(X_prime)
        X = X_prime

    return X, history

def newton_descent(g, h, initial_point, alpha, iterations):
    """
    Performs newtonian descent on a function. Takes the gradient of that function at a given point, normalizes
    the gradient by the hessian of the function at that point, and then updates the parameter:

        X_1 = X_0 - alpha * h(x_0)^-1 * g(X_0)

    where alpha is typically 1.

    :param g: a Function(array) defining the gradient of the optimization target with respect to the optimization parameter
    :param h: a Function(array) defining the hessian of the optimization target with respect to the optimization parameter
    :param initial_point: an Array representing the start value for the optimization parameter
    :param alpha: typically 1 for newtonian descent.
    :param iterations: the number of iterations of gradient descent to perform
    :return: X, an Array of the final parameter values obtained
    :return: history, a List[Array] of all values of x. Useful for plotting the algorithms performance in the parameter
    space
    """
    history = [initial_point]
    X = initial_point

    for i in range(iterations):
        p_i = np.matmul(np.linalg.inv(h(X)), g(X))
        X_prime = X - alpha*p_i
        history.append(X_prime)
        X = X_prime

    return X, history

def conjugate_gradient(g, initial_point, alpha, iterations):
    # TODO
    pass


# -*- coding: utf-8 -*-
"""
Created on Wed Mar  6 15:12:50 2019

@author: miles

This function visualizes the mandelbrot set in modular arithmetic

The process is as follows:
    -Create a set of points evenly spaced around a circle
    -The size of this set is equal to the modulus ie if m = 10, then we have 10 points
    -We 'label' the points 0 to m - 1
    -We choose a value n, and for each value from 0 to m-1 we perform the operation i * n % m
    -We draw a line between the original point and the resulting value above
    -Example: let n = 2, and m = 10, thus for all i in {0,9} we do i * 2 % 10
    - 0 * 2 % 10 = 0, so for the first point we dont draw a line (or a line from 0 to 0)
    - 1 * 2 % 10 = 2, so from the point labled 1 we draw a line to the point labeled 2
    - 6 * 2 % 10 = 2, from the point labled 6 we would draw a line back to

"""

import seaborn as sb #For stylizing the plots
import matplotlib.pyplot as plt
import numpy as np

def mandelbrot_lines(r,m,n): # input: r = radius of the circle on which points will be place, m = modulus (also the number of points), n = value for multiplication

    theta = (360/m) * (np.pi/180) # plotting all points in polar cord, this is the value in rads that the theta for each point will be spaced by

    for i in range(m):
        x, y = -r * np.cos((i * theta)), -r * np.sin((i * theta)) # create a point at (x,y) with polar r,theta

        nextPoint = (i * n % m) # perform the operation on i to find the point that (x,y) will connect to
        finalX, finalY = -r * np.cos((nextPoint * theta)), -r * np.sin((nextPoint * theta)) #find the location of the connecting point

        plt.plot([x,finalX],[y,finalY]) #plot a line by the the two points in cart cord.

    plt.axis('off') # to display an image with out any axis or labeling
    plt.savefig('mandelbrotGraph5thRun'+str(m)+'.png',dpi = 200)
    plt.show()
    #To save the final plot uncomment the line below

mandelbrot_lines(1,150,2)

#for i in np.arange(0.0,31.0,0.1): # to see how the same changes as we change n at a continous rate
#    mandelbrot_lines(1,200,i)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Aug 12 13:34:49 2020

@author: lukepinkel
"""
import numba
import numpy as np
import scipy as sp
import scipy.special
SQRT2 = np.sqrt(2)
ROOT2PI = np.sqrt(2.0 * np.pi)

def poisson_logp(x, mu, logp=True):
     p = sp.special.xlogy(x, mu) - sp.special.gammaln(x + 1) - mu
     if logp==False:
         p = np.exp(p)
     return p

def log1p(x):
    return np.log(1+x)

def norm_cdf(x, mean=0.0, sd=1.0):
    z = (x - mean) / sd
    p = (sp.special.erf(z/SQRT2) + 1.0) / 2.0
    return p

def norm_pdf(x, mean=0.0, sd=1.0):
    z = (x - mean) / sd
    p = np.exp(-z**2 / 2.0) / (ROOT2PI * sd)
    return p

def get_part(arr, sol, size, step, maximum, res):
    if step==size:
        res.append(sol.copy())
    else:
        sol[step] = 1
        while sol[step]<=maximum:
            get_part(arr, sol, size, step+1, maximum, res)
            sol[step] += 1
        get_part(arr, sol, size, step+1, maximum+1, res)

def partition_set(n):
    size = n
    arr = np.arange(1, size+1)-1
    sol = np.zeros(size, dtype=int)
    res = []
    get_part(arr, sol, size, 0, 0, res)
    return res

@numba.jit(nopython=True)
def soft_threshold(x, t):
    y = np.maximum(np.abs(x) - t, 0) * np.sign(x)
    return y

@numba.jit(nopython=True)
def expit(x):
    u = np.exp(x)
    y = u / (1.0 + u)
    return y


import numpy as np
import scipy.special as ss

def cartToSph(x,y,z):
    '''
        [r, theta, phi] = cartToSph(x, y, z) converts the cartesian
        coordinate system to the spherical coordinate system according to
        the following definition:
        r       distance from the origin to the point in the interval
                [0, \infty)
        theta   elevation angle measured between the positive z-axis and
                the vector in the interval [0, pi]
        phi     azimuth angle measured between the positive x-axis and
                the vector in the interval [0, 2*pi)
    '''

    r = np.sqrt(x**2 + y**2 + z**2)
    theta = np.arctan2(np.sqrt(x**2 + y**2), z)
    phi = np.arctan2(y , x)

    return [r, theta, phi]

def sphToCart(r,theta,phi):
    '''
        [x,y,z] = sphToCart(r,theta,phi)
        for converting from spherical to cartesian coordinates
        r is radius,
        theta is angle of elevation (0 at positive z axis, pi at negative z axis)
        phi is angle of azimuth (0 at positive x axis, increase counterclockwise)
    '''
    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 get_legendre(n,m,x):
    '''
        Returns an array dimensions len(N) by 1 with the
        value of the m-th degree term of the n-th order
        associated legendre polynomial evaluated at x.

        Inputs:
            n: a sequence of integers
            m: a single integer, for now.
            x: the argument to legenre polynomial
        Output:
            P
    '''
    P = []
    for i in range(0,len(n)):
        # use scipy.special to computePmn(x)
        #into an m+1 x n+1 array for every value
        #0...m, 0...n.
        a,b = ss.lpmn(m,n[i],x)
        #select the value at the m,n of interest
        P.append(a[m,n[i]])
    return P

def norm(sensor_location):
    '''
        return the pythagorean distance from the sensor location
        to the origin.
    '''
    x = sensor_location[0]
    y = sensor_location[1]
    z = sensor_location[2]
    return np.sqrt(x**2 + y**2 + z**2)

if __name__ == "__main__":
    sensor_location = [0,0,-2000]
    [r,theta,phi] = cartToSph(sensor_location)
    print(r,theta,phi)


import numpy as np
import matplotlib.pyplot as plt

class omd:
    def __init__(self, dim, eta):
        self.dim = dim
        self.x_t = (1.0/dim)*np.ones([dim])
        self.t = 0
        self.eta = eta

    def step(self, loss_grad):

        self.t = self.t + 1

        # compute numerator
        y_t = self.x_t*np.exp(-1*self.eta*(loss_grad))

        # compute denominator
        normalizer = np.sum(np.abs(y_t))
        self.x_t = y_t/normalizer

        return self.x_t

def generate_simplex(dim):
    p = np.random.uniform(0,100, size=[dim])
    return p/np.sum(p)

def loss(x,y, l="l2"):
    if l == "l2":
        return 0.5*np.linalg.norm(x-y)
    elif l == "l1":
        return np.sum(np.abs(x-y))
    elif l == "kl":
        # sum over the support of y
        return np.sum(np.where(x != 0,(x-y) * np.log(x / y), 0))
    elif l == "linear":
        return np.sum(x-y)

def grad_loss(x,y, l="l2"):
    if l == "l2":
        return x - y
    elif l == "l1":
        return np.sum(np.sign(x-y))
    elif l == "kl":
        return np.ones(x.shape) - np.divide(y,x)#np.divide(x,y)+np.log(np.divide(x,y))
    elif l == "linear":
        return -1*x.shape[0]

if __name__ == "__main__":
    dim = 5          # dimension
    eta = 0.1                   # stepsize
    T = 700                     # number of steps
    losses = np.zeros([T,1])    # loss values placeholder
    threshold = 0.0001          # convergence threshold
    loss_func = "linear"                    # choose loss function

    p = generate_simplex(dim)
    # p = np.array([0.19775466, 0.16387309, 0.22701363, 0.10678895, 0.30456967])
    # p = p.T

    online_md = omd(dim, eta)
    # determine initial value
    x_init = online_md.x_t
    x_t = x_init

    for t in range(0,T):
        #print(x_t)
        x_t = online_md.step(grad_loss(x_t, p, loss_func))
        loss_t = loss(x_t, p, loss_func)
        losses[t] = loss_t

        # check for convergence
        norm_dist = np.linalg.norm(p - x_t)
        if norm_dist < threshold:
            print("solution converged at iter ", t)
            break

    plt.plot(losses)
    plt.ylabel("loss")
    plt.xlabel("iter")
    plt.savefig("plots/losses.png")

    print("initialization:\t\t", x_init)
    print("objective simplex:\t", p)
    print("learned simplex:\t", x_t)
    print("norm distance: ", norm_dist)


import numpy as np
import matplotlib.pyplot as plt

# Nenille Method
def Q(x):
    n = len(x_data)
    Q = np.zeros((n,n))

    for i in range(0, n):
        Q[i][0] = f_data[i]

        for j in range(1, i+1):
            Q[i][j] = ((x - x_data[i-j]) * Q[i][j-1] - (x - x_data[i]) * Q[i-1][j-1]) / (x_data[i] - x_data[i-j])

    return Q[i][i]

if __name__ == "__main__":
    # Some Data
    x_data = [-1.6, -1.0, -0.4, 0.2, 0.8, 1.4]
    f_data = [0.278037, 0.606531, 0.923116, 0.980199, 0.726149, 0.375311]

    print('\nComputated with Neville Method:')
    print(f'\tf(0) = {Q(0):.6f}')
    print(f'\tf(1) = {Q(1):.6f}')

    # Visualize Neville Polynomial
    x = np.linspace(-1.6, 1.4, 150)
    y = np.zeros(len(x))

    i = 0

    for point in x:
        y[i] += Q(point)
        i += 1

    plt.subplot(1, 1, 1)
    plt.plot(x, y, linewidth = 1, color = 'magenta', label = 'Neville Polynomial')
    plt.scatter(x_data, f_data, color = 'black', marker = ".", label = 'Sample Points')

    plt.legend(loc = 'lower center', prop = {'size': 12})
    plt.ylabel('$y$')
    plt.xlabel('$x$')
    plt.title('Mέθοδος Neville')
    plt.show()


import math
from pathlib import Path
from typing import List, Optional

import matplotlib.pyplot as plt
import numpy as np

from distributions.distribution import Distribution

class Gaussian(Distribution):
    """A package to calculate gaussian distribution and probabilities"""

    def __init__(self, numbers: Optional[List[float]] = None, mu: float = None,
                 sigma: float = None, from_file: bool = False) -> None:
        super().__init__(mu=mu, sigma=sigma)

        if mu is not None and sigma is not None and numbers is None:
            self.mu = mu
            self.sigma = sigma

        elif (mu is None and sigma is None) and numbers is not None:
            self.numbers = numbers
            self.mu = np.mean(self.numbers)
            self.sigma = np.std(self.numbers)

        elif from_file:
            self.data = []

        else:
            raise ValueError("""Cannot compute mean or sigma since either numbers
            need to be provided or mu and sigma needs to be provided""")

    def calculate_mean(self) -> float:
        """Function to calculate mean"""

        if self.data:
            return np.mean(self.data)
        else:
            return self.mu

    def calculate_std(self) -> float:
        """Function to calculate standard deviation"""

        if self.data:
            return np.std(self.data)
        else:
            return self.sigma

    def read_data_file(self, file_name: str = "") -> None:
        """
        Function to read in data file and then compute
        mean and standard deviation
        """

        if Path(file_name).is_file():
            with open(file_name, "r") as file:
                data_list = []
                line = file.readline()
                while line:
                    data_list.append(int(line))
                    line = file.readline()

            self.data = data_list
            self.mu = self.calculate_mean()
            self.sigma = self.calculate_std()

        else:
            raise FileNotFoundError(f"""{file_name} doesn't exist, please check
            the file or path""")

    def plot_histogram(self) -> None:
        """Function to plot histogram"""

        if self.data:
            plt.hist(self.data)
            plt.title("Histogram of data")
            plt.xlabel("data")
            plt.ylabel("count")
        else:
            raise ValueError("Histogram cannot be generated as no\
                data has been provided")

    def pdf(self, x: float) -> float:
        """Probability density function calculator
           for the gaussian distribution"""

        return (1.0 / (self.sigma * np.sqrt(2*math.pi))) * \
            np.exp(-0.5*((x - self.mu) / self.sigma) ** 2)

    def __add__(self, other):
        """Function to add two gaussians together"""

        result = Gaussian(mu=0, sigma=0)
        result.mu = self.mu + other.mu
        result.sigma = math.sqrt(self.sigma ** 2 + other.sigma ** 2)
        return result

    def __repr__(self):
        """Printing the result in the a long format"""

        return f"mean is {self.mu} and std is {self.sigma}"


import numpy as np
import collections

class K_Means:

    def __init__(self, k=2, tol = 0.001, max_iter = 300):
        self.k = k
        self.tol = tol
        self.max_iter = max_iter

    def fit(self,data):

        self.centroids = {}
        for i in range(self.k):
            self.centroids[i] = data[i]

        for i in range(self.max_iter):
            self.classification = collections.defaultdict(list)

            for featureset in data:
                distances = [np.linalg.norm(featureset - self.centroids[c]) for c in self.centroids]
                classification = distances.index(min(distances))
                self.classification[classification].append(featureset)

            prev = dict(self.centroids)

            for classification in self.classification:
                self.centroids[classification] = np.average(self.classification[classification], axis = 0)

            optimized = True

            for c in self.centroids:
                original_centroid = prev[c]
                current_centroid = self.centroids[c]
                if np.sum((current_centroid-original_centroid)/original_centroid*100.0) > self.tol:
                    optimized = False
            if optimized:
                break

    def predict(self,x):
        distances = [np.linalg.norm(x-self.centroids[centroid]) for centroid in self.centroids]
        classification = distances.index(min(distances))
        return classification

# def distEclud(vecA,vecB):
#     return np.sqrt(np.sum(np.power(vecA-vecB,2)))
#
# def randCent(dataset,k):
#     n = dataset.shape[1]
#     centroids = np.mat(np.zeros((k,n)))
#     for i in range(n):
#         minI = min(dataset[:,i])
#         rangeI = np.float(max(dataset[:,i]) - minI)
#         centroids[:,i] = minI + rangeI*np.random.rand(k,1)
#     return centroids
#
# def kMeans(dataset, k, distMeans = distEclud, createCent = randCent):
#     m = dataset.shape[0]
#     clusterAssment = np.mat(np.zeros((m,2))) # index of cluster, distance
#     centroids = createCent(dataset,k)
#     clusterChanged = True
#     while clusterChanged:
#         clusterChanged = False
#         for i in range(m):
#             minDist = float('-inf')
#             minIndex = -1
#             for j in range(k):
#                 distJI = distMeans(centroids[j,:], dataset[i,:])
#                 if distJI < minDist:
#                     minDist = distJI
#                     minIndex = j
#             if clusterAssment[i,0] != minIndex:
#                 clusterChanged = True
#         for cent in range(k):
#             ptsInClust = dataset[np.nonzero(clusterAssment[:,0]==cent)]
#             centroids[cent,:] = np.mean(ptsInClust, axis=0)
#     return centroids, clusterAssment


from scipy.stats import norm, poisson
import numpy as np

def weiner_process(m, N, t0, T):
	"""
	Function for modelling a Weiner process

		Parameters:

			m (int): number of sample paths
			N (int): number of approximation points
			t0 (float): simulation start time
			T (float): simulation end time

		Returns:

			trajectories (numpy.ndarray): a matrix with m lines and N columns where each line corresponds to a different
			sample path of a Weiner process from t0 to T, approximated at N points
			time (numpy.ndarray): an array equal to numpy.linspace(t0, T, N)
	"""

	dt = (T - t0) / (N - 1)
	trajectories = np.zeros((m, N))

	distr = norm(0, 1)

	for j in range(1, N):
		trajectories[:, j] = trajectories[:, j-1] + distr.rvs(size=m) * np.sqrt(dt)

	time = np.linspace(t0, T, N)
	return time, trajectories

def brownian_bridge(existent_points, new_time_points):
	"""
	Function for adding new approximation points to existing trajectory via the brownian bridge

		Parameters:

			existent_points (2-dim matrix (list or numpy.ndarray)): a matrix with the first line corresponding to
			time points and the second line corresponding to the process values at these time points
			new_time_points (iterable): list of new approximation points

		Returns:

			Two-dimensional numpy.ndarray with the first line corresponding to both existing and added time points
			and the second line corresponding to the process values at these time points
	"""
	if isinstance(existent_points, list):
		existent_points = np.array(existent_points)

	ep = existent_points[:, existent_points[0].argsort()]  # just in case
	new_time_points.sort()

	tn = ep[0][-1]
	for t in new_time_points:
		if t > tn:
			# append this point using the Weiner process definition

			Wtn = ep[1][-1]

			xi = norm(0, t - tn).rvs()
			Wt = Wtn + xi

			ep = np.c_[ep, [t, Wt]]
			tn = t
		else:
			# construct Brownian Bridge between a = W(t_{i - 1}) and b = W(t_i)

			i = np.searchsorted(ep[0], t)

			t1 = ep[0][i - 1]
			t2 = ep[0][i]

			if t == t1 or t == t2:
				continue

			a = ep[1][i - 1]
			b = ep[1][i]

			mu = a + (b - a) * (t - t1) / (t2 - t1)
			sigma = (t - t1) * (t2 - t) / (t2 - t1)

			Wt = norm(mu, sigma).rvs()

			ep = np.c_[ep[:, :i], [t, Wt], ep[:, i:]]

	return ep

def poisson_process(m, N, t0, T, lam):
	"""
	Function for modelling a Poisson process

		Parameters:

			m (int): number of sample paths
			N (int): number of approximation points
			t0 (float): simulation start time
			T (float): simulation end time
			lam (float): lambda parameter for Poisson process. Should be > 0

		Returns:

			time (numpy.ndarray): an array equal to numpy.linspace(t0, T, N)
			trajectories (numpy.ndarray): a matrix with m lines and N columns where each line corresponds to a different
			sample path of a Poisson process from t0 to T, approximated at N points

	"""
	dt = (T - t0) / N
	trajectories = np.zeros((m, N))

	p = poisson(dt * lam)

	for j in range(1, N):
		trajectories[:, j] = trajectories[:, j-1] + p.rvs(size=m)

	time = np.linspace(t0, T, N)
	return time, trajectories

def _ito_integration(f, a, b, k=2, step=0.1, m=1000):
	"""
	Function for modelling Ito's integrals and approximate calculation of its expectations

		Parameters:

			f (callable): integrand
			a (float): lower integration limit
			b (float): upper integration limit
			k (int): order of expectation
			step (float): integration step
			m (int): number of sample paths in Monte-Carlo simulation

		Returns:

			approximate value of k-th order expectation and matrix of sample paths
	"""
	n = int((b - a) / step)  # number of intervals
	t = np.linspace(a, b, n+1)

	_, trajectories = weiner_process(m, n+1, a, b)
	F = np.zeros((m, n))  # matrix of function values

	for i in range(m):
		F[i] = f(t[:-1], trajectories[i][:-1])

	I_f = np.zeros((m, n))
	for i in range(n):
		I_f[:, i] = F[:, i] * (trajectories[:, i + 1] - trajectories[:, i])

	return (I_f.sum(axis=1) ** k).sum() / m, trajectories

def ito_int_expect(f, a, b, k=2, step=0.1, m=1000):
	"""
	Returns approximate value of k-th order expectation of Ito's integral f(t, W(t))dW from a to b
	calculated using Monte-Carlo simulation.

		Parameters:

			f (callable): integrand
			a (float): lower integration limit
			b (float): upper integration limit
			k (int): order of expectation
			step (float): integration step
			m (int): number of sample paths in Monte-Carlo simulation

		Returns:

			expect(float): approximate value of k-th order expectation
	"""
	expect, _ = _ito_integration(f, a, b, k=k, step=step, m=m)
	return expect

def ito_int_paths(f, a, b, step=0.1, m=1000):
	"""
	Returns sample paths of Ito's integral f(t, W(t))dW

		Parameters:

			f (callable): integrand
			a (float): lower integration limit
			b (float): upper integration limit
			step (float): integration step
			m (int): number of sample paths to generate

		Returns:

			expect(float): array of time points and a matrix in which each line corres
	"""
	_, paths = _ito_integration(f, a, b, k=1, step=step, m=m)
	time = np.arange(a, b+step, step)
	return time, paths

def sde(dy, y0, t, step, nsamp=1000, method='euler'):
	"""
	Solves Ito's stochastic differential equations of the form "dy = f*dt + g*dW"
	using Monte-Carlo simulation and Milstein or Euler's method

		Parameters:

			dy (callable): function for calculation of dy; should take 3 arguments corresponding to y, dt and dW
			and return tuple with values of f and g for Euler's method or values of f, g, and partial derivative
			dg/dy for Milstein's method
			y0 (float): initial condition
			t (tuple <float, float>): starting and final time of the solution
			step (float): integration step
			nsamp (int), optional: number of sample paths used in Monte-Carlo simulation; default 1000
			method (str), optional: numerical method; either 'milstein' or 'euler' (default);
			Milstein's method is more precise, but requires additional derivation

		Returns:

			time: 1d numpy.ndarray with time points of approximation
			paths: matrix in which each row corresponds to a different solution sample path
	"""

	n = norm(0, step)
	N = int((t[1] - t[0]) / step)  # number of intervals

	paths = np.zeros((nsamp, N + 1))
	if method == 'euler':
		for j in range(nsamp):
			y = np.zeros(N + 1)
			y[0] = y0
			W = n.rvs(size=N)
			for i in range(N):
				f, g = dy(y[i], step, W[i])
				y[i + 1] = y[i] + f * step + g * W[i]
			paths[j] = y

	elif method == 'milstein':
		for j in range(nsamp):
			y = np.zeros(N + 1)
			y[0] = y0
			W = n.rvs(size=N)
			for i in range(N):
				f, g, dgdx = dy(y[i], step, W[i])
				y[i + 1] = y[i] + f * step + g * W[i] + 1 / 2 * g * dgdx * (W[i] - step) ** 2
			paths[j] = y
	time = np.arange(t[0], t[1] + step, step)
	return time, paths


import numpy as np

def euclidean_distance(features_1, features_2):
    dis = np.sqrt(np.sum(np.square(features_1 - features_2)))
    return dis

def cos_distance(features_1, features_2):
    dis = np.dot(features_1, features_2) / (np.linalg.norm(features_1) * (np.linalg.norm(features_2)))
    return dis

def pearson_correlation(features_1, features_2):
    features_1_ = features_1 - np.mean(features_1)
    features_2_ = features_2 - np.mean(features_2)
    dis = cos_distance(features_1_, features_2_)
    return dis

if __name__ == '__main__':
    a = np.array([1, 2, 3])
    b = np.array([5, 6, 7])
    print(np.dot(a, b))


#!/usr/bin/python3

# MIT License
#
# Copyright (c) 2021 Mario Blanco Dominguez, Juan Tecedor
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import numpy as np
import matplotlib.pyplot as plt
from pandas.io.parsers import read_csv
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter

def getMat(file_name):
    return read_csv(file_name, header=None).to_numpy().astype(float)

# Prediction of the value y. In this case using linear regression.
def hypothesis(X, theta_0, theta_1):
    return X * theta_1 + theta_0

# Cost function J(theta), measures how good are our guesses
def cost(X, Y, theta_0, theta_1):
    return (1 / (2 * len(X))) * (((hypothesis(X, theta_0, theta_1) - Y)**2).sum())

def gradientDescent(X, Y, iterations, alpha):
    theta_0 = theta_1 = 0
    costs = []

    for i in range(iterations):
        H = hypothesis(X, theta_0, theta_1)
        temp_0 = theta_0 - (alpha / len(X)) * (H - Y).sum()
        temp_1 = theta_1 - (alpha / len(X)) * ((H - Y) * X).sum()
        theta_0 = temp_0
        theta_1 = temp_1
        costs.append(cost(X, Y, theta_0, theta_1))

    plt.suptitle('Cost')
    plt.xlabel('Iterations')
    plt.ylabel('J(theta)')
    plt.plot(np.arange(0, iterations), costs)
    plt.savefig('costs.png')
    plt.show()
    return theta_0, theta_1

def plot_surfaces(theta_0_, theta_1_, theta_0_range, theta_1_range, X, Y):
    step = .1
    theta_0 = np.arange(theta_0_range[0], theta_0_range[1], step)
    theta_1 = np.arange(theta_1_range[0], theta_1_range[1], step)
    theta_0, theta_1 = np.meshgrid(theta_0, theta_1)
    Cost = np.empty_like(theta_0)
    for i_x, i_y in np.ndindex(theta_0.shape):
        Cost[i_x, i_y] = cost(X, Y, theta_0[i_x, i_y], theta_1[i_x, i_y])

    fig = plt.figure()
    ax = Axes3D(fig)
    ax.plot_surface(theta_0, theta_1, Cost, cmap=cm.coolwarm)
    plt.show()

    plt.contour(theta_0, theta_1, Cost, np.logspace(-2, 3, 20), colors='red')
    plt.scatter(theta_0_, theta_1_)
    plt.show()

def main():
    data = getMat('./ex1data1.csv')
    X = data[:, 0]
    Y = data[:, 1]
    alpha = 0.01
    iterations = 1500

    theta_0, theta_1 = gradientDescent(X, Y, iterations, alpha)
    x = np.linspace(min(X), max(X), 100)
    y = theta_0 + theta_1 * x

    plt.suptitle('Result')
    plt.xlabel('City population in 10k\'s')
    plt.ylabel('Income in 10k\'s')
    plt.plot(X, Y, 'x')
    plt.plot(x, y, label=('y = ' + str(theta_1) + 'x + ' + str(theta_0)))
    plt.legend()
    plt.savefig('result.png')
    plt.show()

    plot_surfaces(theta_0, theta_1, [-10, 10], [-1, 4], X, Y)

main()