"""
Assorted functions to work with binary vectors and matrices
"""

import numpy as np
from scipy import sparse

def mod10_to_mod2(dec, length=0):

    """Converts a decimal number to a binary number, with optional padding
    to produce a binary vector of a given length.

    Parameters
    ----------
    dec : int
        Decimal number (base 10).
    length : int, optional
        The length of the binary string. If the specified `length' is greater
        than the bit-length of the binary number the output is left-padded
        with zeros. The default `length' is set to zero.

    Returns
    -------
    list
        A binary integer list encoding the binary represenation of the
        inputted decimal number

    Examples
    --------
    >>> mod10_to_mod2(2,length=5)
    [0, 0, 0, 1, 0]
    """

    # Convert dec to a binary string, with <length> leading zeros
    bin_str = format(dec, '0{}b'.format(length))

    # Split that string into array, converting chars to ints
    return [int(b) for b in bin_str]

def mod2_to_mod10(binary_arr):

    """
    Converts binary number represented as a list to a decimal number.

    Parameters
    ----------
    binary_arr : list
        A binary number represented as the entries of a list

    Returns
    -------
    int
        The decimal representation of the inputted binary array

    Examples
    --------
    >>> mod2_to_mod10([0,0,0,1,0])
    2

    """

    bases = 2 ** np.arange(len(binary_arr))[::-1]
    return binary_arr @ bases

def row_echelon(matrix, full=False):

    """
    Converts a binary matrix to row echelon form via Gaussian Elimination

    Parameters
    ----------
    matrix : numpy.ndarry or scipy.sparse
        A binary matrix in either numpy.ndarray format or scipy.sparse
    full: bool, optional
        If set to `True', Gaussian elimination is only performed on the rows below
        the pivot. If set to `False' Gaussian eliminatin is performed on rows above
        and below the pivot.

    Returns
    -------
        row_ech_form: numpy.ndarray
            The row echelon form of input matrix
        rank: int
            The rank of the matrix
        transform_matrix: numpy.ndarray
            The transformation matrix such that (transform_matrix@matrix)=row_ech_form
        pivot_cols: list
            List of the indices of pivot num_cols found during Gaussian elimination

    Examples
    --------
    >>> H=np.array([[1, 1, 1],[1, 1, 1],[0, 1, 0]])
    >>> re_matrix=row_echelon(H)[0]
    >>> print(re_matrix)
    [[1 1 1]
     [0 1 0]
     [0 0 0]]

    >>> re_matrix=row_echelon(H,full=True)[0]
    >>> print(re_matrix)
    [[1 0 1]
     [0 1 0]
     [0 0 0]]

    """

    num_rows, num_cols = np.shape(matrix)

    # Take copy of matrix if numpy (why?) and initialise transform matrix to identity
    if isinstance(matrix, np.ndarray):
        the_matrix = np.copy(matrix)
        transform_matrix = np.identity(num_rows).astype(int)
    elif isinstance(matrix, sparse.csr.csr_matrix):
        the_matrix = matrix
        transform_matrix = sparse.eye(num_rows, dtype="int", format="csr")
    else:
        raise ValueError('Unrecognised matrix type')

    pivot_row = 0
    pivot_cols = []

    # Iterate over cols, for each col find a pivot (if it exists)
    for col in range(num_cols):

        # Select the pivot - if not in this row, swap rows to bring a 1 to this row, if possible
        if the_matrix[pivot_row, col] != 1:

            # Find a row with a 1 in this col
            swap_row_index = pivot_row + np.argmax(the_matrix[pivot_row:num_rows, col])

            # If an appropriate row is found, swap it with the pivot. Otherwise, all zeroes - will loop to next col
            if the_matrix[swap_row_index, col] == 1:

                # Swap rows
                the_matrix[[swap_row_index, pivot_row]] = the_matrix[[pivot_row, swap_row_index]]

                # Transformation matrix update to reflect this row swap
                transform_matrix[[swap_row_index, pivot_row]] = transform_matrix[[pivot_row, swap_row_index]]

        # If we have got a pivot, now let's ensure values below that pivot are zeros
        if the_matrix[pivot_row, col]:

            if not full:
                elimination_range = [k for k in range(pivot_row + 1, num_rows)]
            else:
                elimination_range = [k for k in range(num_rows) if k != pivot_row]

            # Let's zero those values below the pivot by adding our current row to their row
            for j in elimination_range:

                if the_matrix[j, col] != 0 and pivot_row != j:    ### Do we need second condition?

                    the_matrix[j] = (the_matrix[j] + the_matrix[pivot_row]) % 2

                    # Update transformation matrix to reflect this op
                    transform_matrix[j] = (transform_matrix[j] + transform_matrix[pivot_row]) % 2

            pivot_row += 1
            pivot_cols.append(col)

        # Exit loop once there are no more rows to search
        if pivot_row >= num_rows:
            break

    # The rank is equal to the maximum pivot index
    matrix_rank = pivot_row
    row_esch_matrix = the_matrix

    return [row_esch_matrix, matrix_rank, transform_matrix, pivot_cols]

def rank(matrix):
    """
    Returns the rank of a binary matrix

    Parameters
    ----------

    matrix: numpy.ndarray
        A binary matrix in numpy.ndarray format

    Returns
    -------
    int
        The rank of the matrix

    Examples
    --------
    >>> H=np.array([[1,0,0],[0,1,0],[0,0,1]])
    >>> print(rank(H))
    3

    >>> H=np.array([[1,1,0],[0,1,1],[1,0,1]])
    >>> print(rank(H))
    2

    """
    return row_echelon(matrix)[1]

def reduced_row_echelon(matrix):
    """
    Converts matrix to reduced row echelon form such that the output has
    the form rre=[I,A]

    Parameters
    ----------
    matrix: numpy.ndarray
        A binary matrix in numpy.ndarray format

    Returns
    -------
    reduced_row_echelon_from: numpy.ndarray
        The reduced row echelon form of the inputted matrix in the form rre=[I,A]
    matrix_rank: int
        The binary rank of the matrix
    transform_matrix_rows: numpy.ndarray
        The transformation matrix for row permutations
    transform_matrix_cols: numpy.ndarray
        The transformation matrix for the columns

    Examples
    --------
    >>> H=np.array([[0, 0, 0, 1, 1, 1, 1],[0, 1, 1, 0, 0, 1, 1],[1, 0, 1, 0, 1, 0, 1]])
    >>> rre=reduced_row_echelon(H)[0]
    >>> print(rre)
    [[1 0 0 1 1 0 1]
     [0 1 0 1 0 1 1]
     [0 0 1 0 1 1 1]]

    """

    num_rows, num_cols = matrix.shape

    # Row reduce matrix to calculate rank and find the pivot cols
    _, matrix_rank, _, pivot_columns = row_echelon(matrix)

    # Rearrange matrix so that the pivot columns are first
    info_set_order = pivot_columns + [j for j in range(num_cols) if j not in pivot_columns]
    transform_matrix_columns = (np.identity(num_cols)[info_set_order].astype(int)).T
    m_q = matrix @ transform_matrix_columns  # Rearranged M

    # Row reduce m_q
    reduced_row_echelon_form, _, transform_matrix_rows, _ = row_echelon(m_q, full=True)

    return [reduced_row_echelon_form, matrix_rank, transform_matrix_rows, transform_matrix_columns]

def nullspace(matrix):
    """
    Computes the nullspace of the matrix M. Also sometimes referred to as the kernel.

    All vectors x in the nullspace of M satisfy the following condition::

        Mx=0 \forall x \in nullspace(M)

    Notes
    -----
    Why does this work?

    The transformation matrix, P, transforms the matrix M into row echelon form, ReM::

        P@M=ReM=[A,0]^T,

    where the width of A is equal to the rank. This means the bottom n-k rows of P
    must produce a zero vector when applied to M. For a more formal definition see
    the Rank-Nullity theorem.

    Parameters
    ----------
    matrix: numpy.ndarray
        A binary matrix in numpy.ndarray format

    Returns
    -------
    numpy.ndarray
        A binary matrix where each row is a nullspace vector of the inputted binary
        matrix

    Examples
    --------
    >>> H=np.array([[0, 0, 0, 1, 1, 1, 1],[0, 1, 1, 0, 0, 1, 1],[1, 0, 1, 0, 1, 0, 1]])
    >>> print(nullspace(H))
    [[1 1 1 0 0 0 0]
     [0 1 1 1 1 0 0]
     [0 1 0 1 0 1 0]
     [0 0 1 1 0 0 1]]
    """

    transpose = matrix.T
    m, n = transpose.shape
    _, matrix_rank, transform, _ = row_echelon(transpose)
    nspace = transform[matrix_rank:m]
    return nspace

def row_span(matrix):
    """
    Outputs the span of the row space of the matrix i.e. all linear combinations of the rows

    Parameters
    ----------
    matrix: numpy.ndarray
        The input matrix

    Returns
    -------
    numpy.ndarray
        A numpy.ndarray matrix with rows reperesenting all linear combinations of the rows of
        the inputted matrix.

    Examples
    --------
    >>> H=np.array([[1,1,0],[0,1,1],[1,0,1]])
    >>> print(row_span(H))
    [[0 0 0]
     [0 1 1]
     [1 0 1]
     [1 1 0]]
    """

    span = []
    for row in matrix:
        temp = [row]
        for element in span:
            temp.append((row + element) % 2)
        span = list(np.unique(temp + span, axis=0))
    if span:
        return np.vstack(span)
    else:
        return np.array([])

def inverse(matrix):
    """
    Computes the left inverse of a full-rank matrix.

    Notes
    -----

    The `left inverse' is computed when the number of rows in the matrix
    exceeds the matrix rank. The left inverse is defined as follows::

        Inverse(M.T@M)@M.T

    We can make a further simplification by noting that the row echelon form matrix
    with full column rank has the form::

        row_echelon_form=P@M=vstack[I,A]

    In this case the left inverse simplifies to::

        Inverse(M^T@P^T@P@M)@M^T@P^T@P=M^T@P^T@P=row_echelon_form.T@P

    Parameters
    ----------
    matrix: numpy.ndarray
        The binary matrix to be inverted in numpy.ndarray format. This matrix must either be
        square full-rank or rectangular with full-column rank.

    Returns
    -------
    numpy.ndarray
        The inverted binary matrix

    Examples
    --------

    >>> # full-rank square matrix
    >>> mat=np.array([[1,1,0],[0,1,0],[0,0,1]])
    >>> i_mat=inverse(mat)
    >>> print(i_mat@mat%2)
    [[1 0 0]
     [0 1 0]
     [0 0 1]]

    >>> # full-column rank matrix
    >>> mat=np.array([[1,1,0],[0,1,0],[0,0,1],[0,1,1]])
    >>> i_mat=inverse(mat)
    >>> print(i_mat@mat%2)
    [[1 0 0]
     [0 1 0]
     [0 0 1]]

    """
    m, n = matrix.shape
    row_echelon_form, matrix_rank, transform, _ = row_echelon(matrix, True)
    if m == n and matrix_rank == m:
        return transform

    # compute the left-inverse
    elif m > matrix_rank and n == matrix_rank:  # left inverse
        return row_echelon_form.T @ transform % 2

    else:
        raise ValueError("This matrix is not invertible. Please provide either a full-rank square\
        matrix or a rectangular matrix with full column rank.")

def row_basis(matrix):
    """
    Outputs a basis for the rows of the matrix.

    Parameters
    ----------
    matrix: numpy.ndarray
        The input matrix

    Returns
    -------
    numpy.ndarray
        A numpy.ndarray matrix where each row is a basis element.

    Examples
    --------

    >>> H=np.array([[1,1,0],[0,1,1],[1,0,1]])
    >>> rb=row_basis(H)
    >>> print(rb)
    [[1 1 0]
     [0 1 1]]
    """
    return matrix[row_echelon(matrix.T)[3]]

if __name__ == "__main__":
    import doctest
    doctest.testmod(verbose=True)

    from ldpc.mod2 import rank, row_echelon,reduced_row_echelon,row_span, nullspace
    from ldpc.codes import ring_code,hamming_code
    from ldpc.code_util import codewords

    # H=ring_code(3)
    # H=hamming_code(3)
    # print([0,H])
    H=np.array([[0, 0, 0, 1, 1, 1, 1],[0, 1, 1, 0, 0, 1, 1],[1, 0, 1, 0, 1, 0, 1]])

    # print(reduced_row_echelon(H)[0])

    # H=np.array([[1,1,0],[0,1,1],[1,0,1]])

    print(codewords(H))


import os
from pathlib import Path
import numpy as np
from dtaidistance import alignment

directory = None

def test_sequences1():
    """Example from https://en.wikipedia.org/wiki/Needleman–Wunsch_algorithm. """
    s1 = "GATTACA"
    s2 = "GCATGCU"
    value, matrix = alignment.needleman_wunsch(s1, s2)
    algn, s1a1, s2a1 = alignment.best_alignment(matrix, s1, s2, gap='-')
    matrix_sol = [
        [-0., -1., -2., -3., -4., -5., -6., -7.],
        [-1.,  1., -0., -1., -2., -3., -4., -5.],
        [-2., -0., -0.,  1., -0., -1., -2., -3.],
        [-3., -1., -1., -0.,  2.,  1., -0., -1.],
        [-4., -2., -2., -1.,  1.,  1., -0., -1.],
        [-5., -3., -3., -1., -0., -0., -0., -1.],
        [-6., -4., -2., -2., -1., -1.,  1., -0.],
        [-7., -5., -3., -1., -2., -2., -0., -0.]]
    algn_sol1 = [['G', '-', 'A', 'T', 'T', 'A', 'C', 'A'], ['G', 'C', 'A', 'T', '-', 'G', 'C', 'U']]
    assert value == 0.0
    assert np.array_equal(matrix, matrix_sol)
    assert s1a1 == algn_sol1[0]
    assert s2a1 == algn_sol1[1]

if __name__ == "__main__":
    directory = Path(os.environ.get('TESTDIR', Path(__file__).parent))
    print(f"Saving files to {directory}")
    test_sequences1()


"""
Problem Statement

min

The tool min returns the minimum value along a given axis.

import numpy

my_array = numpy.array([[2, 5],
                        [3, 7],
                        [1, 3],
                        [4, 0]])

print numpy.min(my_array, axis = 0)         #Output : [1 0]
print numpy.min(my_array, axis = 1)         #Output : [2 3 1 0]
print numpy.min(my_array, axis = None)      #Output : 0
print numpy.min(my_array)                   #Output : 0
By default, the axis value is None. Therefore, it finds the minimum over all
the dimensions of the input array.

max

The tool max returns the maximum value along a given axis.

import numpy

my_array = numpy.array([[2, 5],
                        [3, 7],
                        [1, 3],
                        [4, 0]])

print numpy.max(my_array, axis = 0)         #Output : [4 7]
print numpy.max(my_array, axis = 1)         #Output : [5 7 3 4]
print numpy.max(my_array, axis = None)      #Output : 7
print numpy.max(my_array)                   #Output : 7
By default, the axis value is None. Therefore, it finds the maximum over all
the dimensions of the input array.

Task

You are given a 2-D array with dimensions NXM.
Your task is to perform the min function over axis 1 and then find the max
of that.

Input Format

The first line of input contains the space separated values of N and M.
The next N lines contains M space separated integers.

Output Format

Compute the min along axis 1 and then print the max of that result.

Sample Input

4 2
2 5
3 7
1 3
4 0
Sample Output

3
Explanation

The min along axis 1 = [2,3,1,0]
The max of [2,3,1,0] = 3
"""
import numpy

n, m = map(int, raw_input().split())
a = numpy.array([map(int, raw_input().split()) for i in range(n)],
                dtype=numpy.int)
min_array = numpy.min(a, axis=1)
print numpy.max(min_array)


import numpy as np
import pandas as pd

# Setting the random seed, feel free to change it and see different solutions.
np.random.seed(42)

def stepFunction(t):
    if t >= 0:
        return 1
    return 0

def prediction(X, W, b):
    return stepFunction((np.matmul(X, W) + b)[0])

def perceptronStep(X, y, W, b, learn_rate=0.01):
    '''
    :param X: data
    :param y: labels
    :param W: weights (array)
    :param b: bias
    :param learn_rate: learning_rate
    :return: updated weigths and bias
    '''
    for i in range(len(X)):
        y_hat = prediction(X[i], W, b)
        if y[i] - y_hat == 1:
            W[0] += X[i][0] * learn_rate
            W[1] += X[i][1] * learn_rate
            b += learn_rate
        elif y[i] - y_hat == -1:
            W[0] -= X[i][0] * learn_rate
            W[1] -= X[i][1] * learn_rate
            b -= learn_rate
    return W, b

def trainPerceptronAlgorithm(X, y, learn_rate=0.01, num_epochs=25):
    '''
    # This function runs the perceptron algorithm repeatedly on the dataset,
    # and returns a few of the boundary lines obtained in the iterations,
    # for plotting purposes.
    :param X: data
    :param y: labels
    :param learn_rate: learning rate
    :param num_epochs: number of epochs (iteration)
    :return: boundary lines
    '''
    x_min, x_max = min(X.T[0]), max(X.T[0])
    y_min, y_max = min(X.T[1]), max(X.T[1])

    W = np.array(np.random.rand(2, 1))
    b = np.random.rand(1)[0] + x_max
    # These are the solution lines that get plotted below.
    boundary_lines = []
    for i in range(num_epochs):
        # In each epoch, we apply the perceptron step.
        W, b = perceptronStep(X, y, W, b, learn_rate)
        boundary_lines.append((-W[0] / W[1], -b / W[1]))
    return boundary_lines

data = pd.read_csv('data.csv')

data.columns = ['x1', 'x2', 'label']
values = np.array(data[["x1","x2"]])
labels = np.array(data[["label"]])

print trainPerceptronAlgorithm(values, labels)


# -*- coding: utf-8 -*-
"""l1-l2-combined.ipynb

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/17Xr03SrQnf94XZN_eWeVtGFzvBiugO4O
"""

# Importing Python packages from Python Libraries
import matplotlib.pyplot as plt
import numpy as np

"""
    Set up the 2-D space for graph plotting.
    Can be extended to 3 dimensions, or "n" dimensions.
"""
w_1 = np.linspace(-1.0, 1.0, 100)
w_2 = np.linspace(-1.0, 1.0, 100)
W_1, W_2 = np.meshgrid(w_1,w_2)
# Constant representing the radius of the (w_1, w_2) circle.
t = 1
# Equation representing L2 regularization.
L2 = W_1**2 + W_2**2 - t
print("Plotting L2 regularization")
plt.contour(W_1,W_2,L2,[0])
plt.show()

# Equation representing L1 regularization.
L1 = abs(W_1) + abs(W_2) - t
print("Plotting L1 regularization")
plt.contour(W_1,W_2,L1,[0])
plt.show()

"""
    Equation for elastic net, which combines L1 regularization
        with L2 regularization as a linear combination of these
        regularization methods.
"""
alpha = [0.1*x for x in range(10)]
beta = [1 - y for y in alpha]
print("alpha is:",alpha,"=")
print("beta is:",beta,"=")
"""
    Determine the current combination of weights,
        or ratio of alpha and beta, for the elastic net to
        combine L1 and L2 norm.
"""
for index, (alpha_i,beta_i) in enumerate(zip(alpha,beta)):
    elastic_net = alpha_i*L1 + beta_i*L2
    print("Plotting elastic net combination of L1 & L2 regularization.")
    plt.contour(W_1,W_2,elastic_net,[0])
    plt.show()


import copy
import math
import numpy as np
from sympy import *
import plotly.graph_objects as go
from scipy.integrate import odeint
from prettytable import PrettyTable
x,y = symbols('x y')

def eulermethodf(f, a, b, x0, y0, n):
    expr = lambdify([x, y], f)
    h = (b - a) / n
    listaX = [x0]
    listaY = [y0]
    listaErr = []
    num = x0
    for i in range(n):
        num = num + h
        listaX.append(num)
    for i in range(n):
        listaY.append(listaY[len(listaY)-1] + h*expr(listaX[i],listaY[len(listaY)-1]))

    def g(y_0, xs):
        return expr(xs,y_0)

    w = odeint(g, y0,listaX)

    for i in range(len(listaY)):
        listaErr.append(abs(w[i][0] - listaY[i]))

    Table = PrettyTable()
    ks = []
    i = 0
    while(len(ks) < n + 1):
        ks.append(i)
        i+=1
    Table.add_column("i", ks)
    Table.add_column("X", listaX)
    Table.add_column("Y", listaY)
    Table.add_column("Erro", listaErr)
    print(Table)

def graficoeuler(f, a, b, x0, y0, n):
    expr = lambdify([x, y], f)
    h = (b - a) / n
    listaX = [x0]
    listaY = [y0]
    num = x0
    for i in range(n):
        num = num + h
        listaX.append(num)
    for i in range(n):
        listaY.append(listaY[len(listaY)-1] + h*expr(listaX[i],listaY[len(listaY)-1]))

    def g(y_0, xs):
        return expr(xs,y_0)

    z = np.arange(x0, listaX[len(listaX)-1]+0.001, 0.001)
    w = odeint(g, y0, z)

    c = []
    for i in range(len(w)):
        c.append(float(w[i]))

    fig = go.Figure()
    fig.add_trace(go.Scatter(x=z,y=c, name='Solução Exata'))
    fig.add_trace(go.Scatter(x=listaX, y=listaY, name="Estimativa", mode="markers"))

    fig.show()

def eulermethod(f, a, b, x0, y0, n):
    expr = lambdify([x, y], f)
    h = (b - a) / n
    listaX = [x0]
    listaY = [y0]
    num = x0
    for i in range(n):
        num = num + h
        listaX.append(num)
    for i in range(n):
        listaY.append(listaY[len(listaY)-1] + h*expr(listaX[i],listaY[len(listaY)-1]))

    Table = PrettyTable()
    ks = []
    i = 0
    while(len(ks) < n + 1):
        ks.append(i)
        i+=1
    Table.add_column("i", ks)
    Table.add_column("X", listaX)
    Table.add_column("Y", listaY)
    print(Table)

def rk2(f, a, b, x0, y0, n):
    expr = lambdify([x, y], f)
    h = (b - a) / n
    listaX = [x0]
    listaY = [y0]
    listaK1 = []
    listaK2 = []
    listaErr = []
    num = x0
    for i in range(n):
        num = num + h
        listaX.append(num)
    for i in range(n):
        listaK1.append(expr(listaX[i], listaY[i]))
        listaK2.append(expr(listaX[i] + h, listaY[i] + h*listaK1[i]))
        listaY.append(listaY[len(listaY)-1] + (h/2) * (listaK1[len(listaK1)-1] + listaK2[len(listaK2)-1]))
    listaK1.append("-")
    listaK2.append("-")

    def g(y_0, xs):
        return expr(xs,y_0)

    w = odeint(g, y0,listaX)

    for i in range(len(listaY)):
        listaErr.append(abs(w[i][0] - listaY[i]))

    Table = PrettyTable()
    ks = []
    i = 0
    while(len(ks) < n + 1):
        ks.append(i)
        i+=1
    Table.add_column("i", ks)
    Table.add_column("X", listaX)
    Table.add_column("K1", listaK1)
    Table.add_column("K2", listaK2)
    Table.add_column("Y", listaY)
    Table.add_column("Erro", listaErr)
    print(Table)

def graficork2(f, a, b, x0, y0, n):
    expr = lambdify([x, y], f)
    h = (b - a) / n
    listaX = [x0]
    listaY = [y0]
    listaK1 = []
    listaK2 = []
    num = x0
    for i in range(n):
        num = num + h
        listaX.append(num)
    for i in range(n):
        listaK1.append(expr(listaX[i], listaY[i]))
        listaK2.append(expr(listaX[i] + h, listaY[i] + h*listaK1[i]))
        listaY.append(listaY[len(listaY)-1] + (h/2) * (listaK1[len(listaK1)-1] + listaK2[len(listaK2)-1]))

    def g(y_0, xs):
        return expr(xs,y_0)

    z = np.arange(x0, listaX[len(listaX)-1]+0.001, 0.001)
    w = odeint(g, y0, z)

    c = []
    for i in range(len(w)):
        c.append(float(w[i]))

    fig = go.Figure()
    fig.add_trace(go.Scatter(x=z,y=c, name='Solução Exata'))
    fig.add_trace(go.Scatter(x=listaX, y=listaY, name="Estimativa", mode="markers"))

    fig.show()

def rk4(f, a, b, x0, y0, n):
    expr = lambdify([x, y], f)
    h = (b - a) / n
    listaX = [x0]
    listaY = [y0]
    listaK1 = []
    listaK2 = []
    listaK3 = []
    listaK4 = []
    listaErr = []
    num = x0
    for i in range(n):
        num = num + h
        listaX.append(num)
    for i in range(n):
        listaK1.append(expr(listaX[i], listaY[i]))
        listaK2.append(expr(listaX[i] + h/2, listaY[i] + (h/2)*listaK1[i]))
        listaK3.append(expr(listaX[i] + h/2, listaY[i] + (h/2)*listaK2[i]))
        listaK4.append(expr(listaX[i] + h, listaY[i] + h*listaK3[i]))
        listaY.append(listaY[len(listaY)-1] + (h/6) * (listaK1[len(listaK1)-1] + 2*listaK2[len(listaK2)-1] + 2*listaK3[len(listaK3)-1] + listaK4[len(listaK4)-1]))
    listaK1.append("-")
    listaK2.append("-")
    listaK3.append("-")
    listaK4.append("-")

    def g(y_0, xs):
        return expr(xs,y_0)

    w = odeint(g, y0,listaX)

    for i in range(len(listaY)):
        listaErr.append(abs(w[i][0] - listaY[i]))

    Table = PrettyTable()
    ks = []
    i = 0
    while(len(ks) < n + 1):
        ks.append(i)
        i+=1
    Table.add_column("i", ks)
    Table.add_column("X", listaX)
    Table.add_column("K1", listaK1)
    Table.add_column("K2", listaK2)
    Table.add_column("K3", listaK3)
    Table.add_column("K4", listaK4)
    Table.add_column("Y", listaY)
    Table.add_column("Erro", listaErr)
    print(Table)

def graficork4(f, a, b, x0, y0, n):
    expr = lambdify([x, y], f)
    h = (b - a) / n
    listaX = [x0]
    listaY = [y0]
    listaK1 = []
    listaK2 = []
    listaK3 = []
    listaK4 = []
    num = x0
    for i in range(n):
        num = num + h
        listaX.append(num)
    for i in range(n):
        listaK1.append(expr(listaX[i], listaY[i]))
        listaK2.append(expr(listaX[i] + h/2, listaY[i] + (h/2)*listaK1[i]))
        listaK3.append(expr(listaX[i] + h/2, listaY[i] + (h/2)*listaK2[i]))
        listaK4.append(expr(listaX[i] + h, listaY[i] + h*listaK3[i]))
        listaY.append(listaY[len(listaY)-1] + (h/6) * (listaK1[len(listaK1)-1] + 2*listaK2[len(listaK2)-1] + 2*listaK3[len(listaK3)-1] + listaK4[len(listaK4)-1]))
    listaK1.append("-")
    listaK2.append("-")
    listaK3.append("-")
    listaK4.append("-")

    def g(y_0, xs):
        return expr(xs, y_0)

    z = np.arange(x0, listaX[len(listaX)-1]+0.001, 0.001)
    w = odeint(g, y0, z)

    c = []
    for i in range(len(w)):
        c.append(float(w[i]))

    fig = go.Figure()
    fig.add_trace(go.Scatter(x=z,y=c, name='Solução Exata'))
    fig.add_trace(go.Scatter(x=listaX, y=listaY, name="Estimativa", mode="markers"))

    fig.show()

# def f(x,y): return y-x
# def f(x,y): return x - y + 2
# eulermethodf(f(x,y), 0, 1, 0, 2, 4)
# graficoeuler(f(x,y), 0, 1, 0, 2, 4)
# eulermethod(f(x,y), 0, 1, 0, 2, 4)
# rk2(f(x,y), 0, 1, 0, 2, 5)
# graficork2(f(x,y), 0, 1, 0, 2, 5)
# rk4(f(x,y), 0, 1, 0, 2, 5)
# graficork4(f(x,y), 0, 1, 0, 2, 5)


import numpy as np
from math import sqrt

def dist_origin_line(line):
    m, b = line
    return abs(b) / sqrt(1 + m**2)

def mse(line, points):
    'Mean Square Error'
    m, b = line
    mse = np.mean([(point[1] - m * point[0] - b)**2 for point in points])
    return mse

def slope_intercept(p1,p2):
    x1, y1 = p1
    x2, y2 = p2
    m = (y2 - y1) / (x2 - x1)
    b = y1 - m * x1
    return (m, b)

def convex_hull(points):
    """Computes the convex hull of a set of 2D points.

    Input: an iterable sequence of (x, y) pairs representing the points.
    Output: a list of vertices of the convex hull in counter-clockwise order,
      starting from the vertex with the lexicographically smallest coordinates.
    Implements Andrew's monotone chain algorithm. O(n log n) complexity.
    """

    # Sort the points lexicographically (tuples are compared lexicographically).
    # Remove duplicates to detect the case we have just one unique point.
    points = sorted(set(points)) ## CAN REMOVE

    # Boring case: no points or a single point, possibly repeated multiple times.
    if len(points) <= 1:
        return points

    # 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product.
    # Returns a positive value, if OAB makes a counter-clockwise turn,
    # negative for clockwise turn, and zero if the points are collinear.
    def cross(o, a, b):
        return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])

    # Build lower hull
    lower = []
    for p in points:
        while len(lower) >= 2 and cross(lower[-2], lower[-1], p) <= 0:
            lower.pop()
        lower.append(p)

    # Build upper hull
    upper = []
    for p in reversed(points):
        while len(upper) >= 2 and cross(upper[-2], upper[-1], p) <= 0:
            upper.pop()
        upper.append(p)

    # Concatenation of the lower and upper hulls gives the convex hull.
    # Last point of each list is omitted because it is repeated at the beginning of the other list.
    return lower[:-1] + upper[:-1]

def upper_hull(points):
    'points must be given in lexicographical order'

    assert(len(points) >= 2)
    points = sorted(set(points))

    def cross(o, a, b):
        return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])

    # Build upper hull
    upper = []
    for p in reversed(points):
        while len(upper) >= 2 and cross(upper[-2], upper[-1], p) <= 0:
            upper.pop()
        upper.append(p)

    return upper[::-1]

def lower_hull(points):
    'points must be given in lexicographical order'

    assert(len(points) >= 2)
    points = sorted(set(points))

    def cross(o, a, b):
        return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])

    # Build lower hull
    lower = []
    for p in points:
        while len(lower) >= 2 and cross(lower[-2], lower[-1], p) <= 0:
            lower.pop()
        lower.append(p)

    return lower

# Example: convex hull of a 10-by-10 grid.

if __name__ == "__main__":
    assert convex_hull([(i//10, i%10) for i in range(100)]) == [(0, 0), (9, 0), (9, 9), (0, 9)]

    #data = convex_hull([(i//10, i%10) for i in range(100)])
    data = [(0, 0), (1, 5), (2, 4), (3, 6), (4, 2), (5,3)]
    print(upper_hull(data))
    print(lower_hull(data))
    print(convex_hull(data))


# Obviously, neural networks for real problems are composed of many hidden layers
# and many more nodes in each layer. So, we can't continue making predictions using
# this very inefficient approach of computing the weighted sum at each node
# and the activation of each node manually.

# In order to code an automatic way of making predictions, let's generalize our network.
# A general network would take  𝑛  inputs, would have many hidden layers, each hidden
# layer having  𝑚  nodes, and would have an output layer. Although the network is
# showing one hidden layer, but we will code the network to have many hidden layers.
# Similarly, although the network shows an output layer with one node, we will code
# the network to have more than one node in the output layer.

# Initialize network

n=2 #inputs
n_hid=2 #hidden layers
m=[2,2] #nodes in each hidden layer
n_out=1 #nodes in output layer

import numpy as np
n_nodes_p=n # number of nodes in the previous layer

# intialize network
network={}

# Creating loops for each layer with their weights and biases associated with each node
# To include the output layer, add 1 to number of hidden layers

for layer in range(n_hid+1):
    #----------- layer determination -------------
    if layer == n_hid:
        layer_name='output' # to define last layer as output layer
        n_nodes=n_out
    else:
        layer_name ='layer_{}'.format(layer+1) # to name each layer as layer 1, layer 2....
        n_nodes=m[layer]
    #------------ weights and biases ---------------
    network[layer_name]={}
    for node in range(n_nodes):
        node_name='node_{}'.format(node+1) # to define nodes for each layer
        network[layer_name][node_name]={
            'weights': np.around(np.random.uniform(size=n_nodes_p),decimals=2),
            'bias': np.around(np.random.uniform(size=1),decimals=2) # define weights and bias for each node in each layer
        }
    n_nodes_p=n_nodes
with open ("Lab1_ANN_Networks.txt",'a') as f:
    print(network,file=f)

## For the above code, let us create a function that n (inputs),n_hid (hidden layers), m (nodes in each hidden layer)
## and n_out(nodes in output layer)

def intialize_network(n,n_hid,m,n_out):
    n_nodes_p=n
    network={}
    for layer in range(n_hid+1):
            if layer == n_hid:
                layer_name='output' # to define last layer as output layer
                n_nodes=n_out
            else:
                layer_name ='layer_{}'.format(layer+1) # to name each layer as layer 1, layer 2....
                n_nodes=m[layer]
            network[layer_name]={}
            for node in range(n_nodes):
                 node_name='node_{}'.format(node+1) # to define nodes for each layer
                 network[layer_name][node_name]={
                     'weights': np.around(np.random.uniform(size=n_nodes_p),decimals=2),
                     'bias': np.around(np.random.uniform(size=1),decimals=2) # define weights and bias for each node in each layer
                     }
            n_nodes_p=n_nodes
    return network

#--------- Exercise 1 = create a small network-----------------

ns=5 #inputs
n_hids=3 #hidden layers
ms=[3,2,3] #nodes in each hidden layer
n_outs=1 #nodes in output layer

small_network=intialize_network(ns,n_hids,ms,n_outs)
with open ("Lab1_ANN_Networks.txt",'a') as f:
    print('Small network is: \n',small_network,file=f)

Introduction to Deep Learning & Neural Networks with Keras/Module1/Lab1_Artificial_Neural_Networks_Networks.py


import numpy as np

from .tools import *

###############################################################################
# HOSVD - Tucker Decomposition
###############################################################################
# HOSVD as described in the lecture consists of multiple steps:
# 1. Calculate SVD of matricizations
# 2. Truncate basis matrices
# 3. Form core tensor
# 4. Reconstruct tensor from core and basis matrices
# My implementations has these different parts in different functions
# @nb.jit
def compute_base_matrices(A, singular_values=False):
    """SVDs of matricisations

    Returns a list of the basis matrices

    We need the singular values for error estimation, otherwise not
    """
    dim = len(A.shape)

    # 1. Calculate SVD of matricisations
    U_list = []
    for i in range(dim):
        m = i+1
        mat = m_mode_matricisation(A, m)
        U, Sigma, _ = np.linalg.svd(mat, full_matrices=False)
        if singular_values:
            U_list.append((U, Sigma))
        else:
            U_list.append(U)

    return U_list

# @nb.jit
def compute_core(A, U_list):
    """Form the core tensor

    Returns basically a Tucker decomposition of A
    """
    dim = len(A.shape)
    C = A
    for i in range(dim-1, -1, -1):
        C = m_mode_matrix_multiplication(C, U_list[i].T, i+1)

    return C, U_list

# @nb.jit
def reconstruct(C, U_list):
    """Reconstructs the tensor given the Tucker decomposition"""
    dim = len(C.shape)
    A_tilde = C
    for i in range(dim-1, -1, -1):
        A_tilde = m_mode_matrix_multiplication(A_tilde, U_list[i], i+1)
    return A_tilde

###############################################################################
# Combine the above
###############################################################################
def full_hosvd(A):
    """Example on how to use the above functions: Full hosvd"""
    U_list = compute_base_matrices(A)
    C, _ = compute_core(A, U_list)
    A_tilde = reconstruct(C, U_list)
    return A_tilde

def truncated_hosvd(A, ranks, U_list_full=None):
    """Other example: HOSVD with truncation of the base matrices

    Can be given the full basis matrices to save computations, we use this
    in the next section.
    """
    if not U_list_full:
        U_list_full = compute_base_matrices(A)
    U_list = [U[:, :ranks[i]] for i, U in enumerate(U_list_full)]
    C, _ = compute_core(A, U_list)
    A_tilde = reconstruct(C, U_list)
    return A_tilde


import numpy as np

def my_closest_power_of_2(M, max):
    M = np.where(max < M, max, M)
    while max > 1:
        M = np.where((max // 2 < M) * (M < max), max // 2, M)
        max //= 2
    return M

def closest_power_of_2(n):
    r = 1
    while r * 2 <= n:
        r *= 2
    return r

if __name__ == '__main__':
    for i in range(20):
        print(i, closest_power_of_2(i))
    n = np.arange(60)
    res_n = my_closest_power_of_2(n, 16)
    for i, v in enumerate(res_n):
        print(i, '-->', v)


"""
In MATLAB, there is a handy function called reshape which can reshape an m x n matrix into a new one with a different size r x c keeping its original data.
You are given an m x n matrix mat and two integers r and c representing the row number and column number of the wanted reshaped matrix.
The reshaped matrix should be filled with all the elements of the original matrix in the same row-traversing order as they were.
If the reshape operation with given parameters is possible and legal, output the new reshaped matrix; Otherwise, output the original matrix.
Example:
        Input - mat = [[1,2],[3,4]], r = 1, c = 4
        Output - [[1,2,3,4]]

        Input - mat = [[1,2],[3,4]], r = 2, c = 4
        Output - [[1,2],[3,4]]

Constraints:
        Time complexity - O(r*c)
        Space complexity - O(r*c)
        m == mat.length
        n == mat[i].length
        1 <= m, n <= 100
        -1000 <= mat[i][j] <= 1000
        1 <= r, c <= 300
"""

from numpy import zeros
class Solution:
    def matrixReshape(self, mat, r, c):
        m, n = len(mat), len(mat[0])
        if r * c != m * n:
            return mat

        ret_mat = zeros((r, c), dtype="int64")
        c_r, c_c = 0, 0

        for i in mat:
            for j in i:
                ret_mat[c_r][c_c] = j
                c_c += 1
                if c_c == c:
                    c_c = 0
                    c_r += 1

        return ret_mat


"""This file contains code used in "Think Stats",
by Allen B. Downey, available from greenteapress.com

Copyright 2014 Allen B. Downey
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html
"""

from __future__ import print_function, division

import first
import hypothesis
import scatter
import thinkstats2

import numpy as np

"""This file contains a solution to exercises in Think Stats:

As sample size increases, the power of a hypothesis test increases,
which means it is more likely to be positive if the effect is real.
Conversely, as sample size decreases, the test is less likely to
be positive even if the effect is real.

To investigate this behavior, run the tests in this chapter with
different subsets of the NSFG data.  You can use thinkstats2.SampleRows
to select a random subset of the rows in a DataFrame.

What happens to the p-values of these tests as sample size decreases?
What is the smallest sample size that yields a positive test?

My results:

test1: difference in mean pregnancy length
test2: difference in mean birth weight
test3: correlation of mother's age and birth weight
test4: chi-square test of pregnancy length

n       test1   test2   test2   test4
9148	0.16	0.00	0.00	0.00
4574	0.10	0.01	0.00	0.00
2287	0.25	0.06	0.00	0.00
1143	0.24	0.03	0.39	0.03
571	0.81	0.00	0.04	0.04
285	0.57	0.41	0.48	0.83
142	0.45	0.08	0.60	0.04

Conclusion: As expected, tests that are positive with large sample
sizes become negative as we take away data.  But the pattern is
erratic, with some positive tests even at small sample sizes.

In Section~\ref{testing}, we simulated the null hypothesis by
permutation; that is, we treated the observed values as if they
represented the entire population, and randomly assigned the
members of the population to the two groups.

An alternative is to use the sample to estimate the distribution for
the population, then draw a random sample from that distribution.
This process is called resampling.  There are several ways to
implement the resampling, but one of the simplest is to draw a sample,
with replacement, from the observed values, as in Section~\ref{power}.

Write a class named {\tt DiffMeansResample} that inherits from
{\tt DiffMeansPermute} and overrides {\tt RunModel} to implement
resampling, rather than permutation.

Use this model to test the differences in pregnancy length and
birth weight.  How much does the model affect the results?

Results:

means permute preglength
p-value = 0.1674
actual = 0.0780372667775
ts max = 0.226752436104

means permute birthweight
p-value = 0.0
actual = 0.124761184535
ts max = 0.112243501197

Conclusions: Using resampling instead of permutation has very
little effect on the results.

The two models are based on slightly difference assumptions, and in
this example there is no compelling reason to choose one or the other.
But in general p-values depend on the choice of the null hypothesis;
different models can yield very different results.

"""

class DiffMeansResample(hypothesis.DiffMeansPermute):
    """Tests a difference in means using resampling."""

    def RunModel(self):
        """Run the model of the null hypothesis.

        returns: simulated data
        """
        group1 = np.random.choice(self.pool, self.n, replace=True)
        group2 = np.random.choice(self.pool, self.m, replace=True)
        return group1, group2

def RunResampleTest(firsts, others):
    """Tests differences in means by resampling.

    firsts: DataFrame
    others: DataFrame
    """
    data = firsts.prglngth.values, others.prglngth.values
    ht = DiffMeansResample(data)
    p_value = ht.PValue(iters=10000)
    print('\nmeans permute preglength')
    print('p-value =', p_value)
    print('actual =', ht.actual)
    print('ts max =', ht.MaxTestStat())

    data = (firsts.totalwgt_lb.dropna().values,
            others.totalwgt_lb.dropna().values)
    ht = hypothesis.DiffMeansPermute(data)
    p_value = ht.PValue(iters=10000)
    print('\nmeans permute birthweight')
    print('p-value =', p_value)
    print('actual =', ht.actual)
    print('ts max =', ht.MaxTestStat())

def RunTests(live, iters=1000):
    """Runs the tests from Chapter 9 with a subset of the data.

    live: DataFrame
    iters: how many iterations to run
    """
    n = len(live)
    firsts = live[live.birthord == 1]
    others = live[live.birthord != 1]

    # compare pregnancy lengths
    data = firsts.prglngth.values, others.prglngth.values
    ht = hypothesis.DiffMeansPermute(data)
    p1 = ht.PValue(iters=iters)

    data = (firsts.totalwgt_lb.dropna().values,
            others.totalwgt_lb.dropna().values)
    ht = hypothesis.DiffMeansPermute(data)
    p2 = ht.PValue(iters=iters)

    # test correlation
    live2 = live.dropna(subset=['agepreg', 'totalwgt_lb'])
    data = live2.agepreg.values, live2.totalwgt_lb.values
    ht = hypothesis.CorrelationPermute(data)
    p3 = ht.PValue(iters=iters)

    # compare pregnancy lengths (chi-squared)
    data = firsts.prglngth.values, others.prglngth.values
    ht = hypothesis.PregLengthTest(data)
    p4 = ht.PValue(iters=iters)

    print('%d\t%0.2f\t%0.2f\t%0.2f\t%0.2f' % (n, p1, p2, p3, p4))

def main():
    thinkstats2.RandomSeed(18)

    live, firsts, others = first.MakeFrames()
    RunResampleTest(firsts, others)

    n = len(live)
    for _ in range(7):
        sample = thinkstats2.SampleRows(live, n)
        RunTests(sample)
        n //= 2

if __name__ == '__main__':
    main()

DSC 530 - Data Exploration and Analysis/ThinkStats2/code/chap09soln.py


import numpy as np
import pandas as pd
from colorama import Fore, Style
from gamestonk_terminal import feature_flags as gtff
from sklearn.metrics import (
    mean_absolute_error,
    r2_score,
    mean_squared_error,
)

def price_prediction_color(val: int, last_val: int) -> str:
    if float(val) > last_val:
        color = Fore.GREEN
    else:
        color = Fore.RED
    return f"{color}{val:.2f} ${Style.RESET_ALL}"

def print_pretty_prediction(df_pred: pd.DataFrame, last_price: float):
    if gtff.USE_COLOR:
        print(f"Actual price: {Fore.YELLOW}{last_price:.2f} ${Style.RESET_ALL}\n")
        print("Prediction:")
        print(df_pred.apply(price_prediction_color, last_val=last_price).to_string())
    else:
        print(f"Actual price: {last_price:.2f} $\n")
        print("Prediction:")
        print(df_pred.to_string())

def mean_absolute_percentage_error(y_true, y_pred):
    y_true, y_pred = np.array(y_true), np.array(y_pred)
    return np.mean(np.abs((y_true - y_pred) / y_true)) * 100

def print_prediction_kpis(real, pred):
    print("KPIs")
    print(f"MAPE: {mean_absolute_percentage_error(real, pred):.3f} %")
    print(f"R2: {r2_score(real, pred):.3f}")
    print(f"MAE: {mean_absolute_error(real, pred):.3f}")
    print(f"MSE: {mean_squared_error(real, pred):.3f}")
    print(f"RMSE: {mean_squared_error(real, pred, squared=False):.3f}")

def price_prediction_backtesting_color(val: list) -> str:
    err_pct = 100 * (val[0] - val[1]) / val[1]
    if val[0] > val[1]:
        s_err_pct = f"       {Fore.GREEN} +{err_pct:.2f} %"
    else:
        s_err_pct = f"       {Fore.RED} {err_pct:.2f} %"
    return f"{val[1]:.2f}    x    {Fore.YELLOW}{val[0]:.2f}{s_err_pct}{Style.RESET_ALL}"


import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from mpl_toolkits.axes_grid1 import make_axes_locatable
from numpy.random import default_rng

class MultivariateNormal():
    """
    A Multivariate Normal Distribution.
    """

    number_points = 200
    number_sigmas = 3

    def __init__(self, mean, cov):

        self.mean = np.atleast_1d(mean)
        self.cov = np.atleast_2d(cov)
        self.dim = self.mean.shape[0]

        self.normalization = (2 * np.pi)**(-self.dim / 2) / \
            np.sqrt(np.linalg.det(self.cov))
        self.precision_matrix = np.linalg.inv(self.cov)

    def __str__(self):
        return (f'{self.dim}-dimensional MVN with mean {self.mean} and covariance {self.cov}')

    def __repr__(self):
        return (f'MultivariateNormal({self.mean}, {self.cov})')

    def pdf(self, x):
        shifted_arg = x - self.mean
        argument = np.sum(
            (shifted_arg) @ self.precision_matrix * (shifted_arg), axis=-1)

        return self.normalization * np.exp(- argument / 2.)

    def marginalize(self, index):
        # not very general! we are only able to marginalize down
        # to one parameter right now

        # the generalization is simple conceptually (take the submatrix of self.cov)
        # but may make the code quite complicated

        marginal_mean = self.mean[index, np.newaxis]
        marginal_cov = self.cov[index, index, np.newaxis, np.newaxis]
        return self.__class__(marginal_mean, marginal_cov)

    def condition(self, index, condition_other_params):
        precision_matrix = np.linalg.inv(self.cov)
        conditioned_cov = np.linalg.inv(
            precision_matrix[index, index, np.newaxis, np.newaxis])

        cross_precision = np.delete(
            precision_matrix, index, axis=-1)[index, np.newaxis, :]

        mean_other_params = np.delete(self.mean, index, -1)
        mean_correction = conditioned_cov @ cross_precision @ (
            condition_other_params - mean_other_params)

        conditioned_mean = self.mean[index, np.newaxis] - mean_correction

        return self.__class__(conditioned_mean, conditioned_cov)

    @staticmethod
    def _analytical_CI(mean, std, percentage):
        number_sigmas = norm.ppf((percentage + 1)/2)
        delta = number_sigmas * std
        return (mean - delta, mean + delta)

    def analytical_CI(self, percentage):

        if self.dim != 1:
            print('This only applies to univariate normals!')

        mean = self.mean.flatten()[0]
        std = np.sqrt(self.cov.flatten()[0])
        return self._analytical_CI(mean, std, percentage)

    @property
    def coordinate_arrays(self):
        number_points = type(self).number_points
        number_sigmas = type(self).number_sigmas

        sigmas = np.sqrt(np.diagonal(self.cov))
        starts = self.mean - number_sigmas * sigmas
        ends = self.mean + number_sigmas * sigmas
        return(np.linspace(starts, ends, num=number_points).T)

    @property
    def cholesky_L(self):
        return (np.linalg.cholesky(self.cov))

    def cholesky_sample(self, number_samples=1, seed=None):
        rng = default_rng(seed)
        independent_deviates = rng.standard_normal(size=(self.dim, number_samples))
        correlated_deviates = (self.cholesky_L @ independent_deviates).T
        return correlated_deviates + self.mean[np.newaxis, :]

    def plot_2d_analytical(self, chosen_x, chosen_y, CL):

        if self.dim != 2:
            print('This only applies to bivariate normals!')
            return None

        marginal_x = self.marginalize(0)
        marginal_y = self.marginalize(1)

        conditioned_x = self.condition(0, chosen_y)
        conditioned_y = self.condition(1, chosen_x)

        x_array, y_array = self.coordinate_arrays
        x, y = np.meshgrid(x_array, y_array)
        positions = np.stack((x, y), axis=-1)
        z = self.pdf(positions)

        mx_interval = marginal_x.analytical_CI(CL)
        my_interval = marginal_y.analytical_CI(CL)
        cx_interval = conditioned_x.analytical_CI(CL)
        cy_interval = conditioned_y.analytical_CI(CL)

        fig = plt.figure(figsize=(10, 10))
        gs = gridspec.GridSpec(
            2, 2, width_ratios=[2, 1], height_ratios=[1, 2])

        ax1 = plt.subplot(gs[0])
        line_mx, = ax1.plot(x_array, marginal_x.pdf(
            x_array[:, np.newaxis]), label='Marginal')
        line_cx, = ax1.plot(x_array, conditioned_x.pdf(
            x_array[:, np.newaxis]), label='Conditioned')
        [ax1.axvline(x=mx_value, c=line_mx._color) for mx_value in mx_interval]
        [ax1.axvline(x=cx_value, c=line_cx._color) for cx_value in cx_interval]
        ax1.legend()

        ax4 = plt.subplot(gs[3])
        line_my, = ax4.plot(marginal_y.pdf(
            y_array[:, np.newaxis]), y_array, label='Marginal')
        line_cy, = ax4.plot(conditioned_y.pdf(
            y_array[:, np.newaxis]), y_array, label='Conditioned')
        [ax4.axhline(y=my_value, c=line_my._color) for my_value in my_interval]
        [ax4.axhline(y=cy_value, c=line_cy._color) for cy_value in cy_interval]
        ax4.legend()

        ax3 = plt.subplot(gs[2], sharex=ax1, sharey=ax4)
        colors = ax3.contourf(x, y, z)
        ax3.axvline(chosen_x, c='white')
        ax3.axhline(chosen_y, c='white')

        ax2 = plt.subplot(gs[1])
        ax2.set_visible(False)
        divider = make_axes_locatable(ax2)
        cax = divider.append_axes('left', size='20%', pad=0.05)
        cbar = fig.colorbar(colors, cax=cax)
        cbar.ax.set_ylabel('Probability density')


"""
    Authors: Amin Arriaga, Eduardo Lopez
    Project: Graduation Thesis: GIAdog

    [TODO: DESCRIPTION]
"""
import numpy as np
from typing import List
from __env__ import H_OFF, LEG_SPAN

def rotation_matrix_from_euler(theta: np.array) -> np.array:
    """
        Calculates a rotation matrix from the euler angles.

        Arguments:
        ----------
            theta: numpy.array, shape(3,)
                Euler angles. (roll, pitch, yaw)

        Return:
        -------
            numpy.array, shape(3,3)
                Rotation matrix.
    """

    roll, pitch, yaw = theta[0], theta[1], theta[2]

    R_x = np.array(
        [[1,        0,             0            ],
        [0,         np.cos(roll),  -np.sin(roll)],
        [0,         np.sin(roll),  np.cos(roll) ]]
    )

    R_y = np.array(
        [[np.cos(pitch),   0,      np.sin(pitch)],
        [0,                1,      0            ],
        [-np.sin(pitch),   0,      np.cos(pitch)]]
    )

    R_z = np.array(
        [[np.cos(yaw),   -np.sin(yaw),    0],
        [np.sin(yaw),    np.cos(yaw),     0],
        [0,              0,               1]]
    )

    return np.dot(R_z, np.dot( R_y, R_x ))

def transformation_matrices(base_rpy: np.array) -> List[np.array]:
    """
        Returns the transformation matrices from the hip to the leg base.

        Arguments:
        ---------
            base_rpy: numpy.array, shape(3,)
                The hip's (and robot base) euler angles. (roll, pitch, yaw)

        Returns:
        --------
            List[numpy.array], shape (4,4).
                A list containing the transformation matrices from the hip to
                the leg base, for each of the robots legs.
    """
    transformation_matrices = []

    # We transform the base orientation from quaternion to matrix
    # We also get the base euler angles
    base_roll, base_pitch, _ = base_rpy

    R_li_Hi = rotation_matrix_from_euler([base_roll, base_pitch, 0])

    for i in range(4):
        # We calculate the Hi frame  relative to the leg base position (hip)
        p_li_Hi = np.array([0, H_OFF * (-1)**i, -LEG_SPAN])
        # We do the same for the leg base orientation

        # Finally we concatenate the rotation matrix and position vector
        # To get the transformation matrix of the Hi horizontal frame expressed
        # in the leg base frame
        T_li_Hi =  np.concatenate((R_li_Hi,  p_li_Hi.reshape(3,1)), axis = 1)
        T_li_Hi = np.concatenate((T_li_Hi, np.array([[0,0,0,1]])), axis = 0)

        transformation_matrices.append(T_li_Hi)

    return transformation_matrices


# -*- coding: utf-8 -*-
"""
Created on Wed Oct 14 22:26:58 2020
Reference: https://stackoverflow.com/questions/39260616/generate-a-normal-distribution-of-dates-within-a-range
"""

import time
import numpy

_DATE_RANGE = ('2020-01-01', '2020-10-30')
_DATE_FORMAT = '%Y-%m-%d'
_EMPIRICAL_SCALE_RATIO = 0.15
_DISTRIBUTION_SIZE = 1000

def main():
    time_range = tuple(time.mktime(time.strptime(d, _DATE_FORMAT))
                       for d in _DATE_RANGE)
    distribution = numpy.random.normal(
        loc=(time_range[0] + time_range[1]) * 0.5,
        scale=(time_range[1] - time_range[0]) * _EMPIRICAL_SCALE_RATIO,
        size=_DISTRIBUTION_SIZE
    )
    date_range = tuple(time.strftime(_DATE_FORMAT, time.localtime(t))
                       for t in numpy.sort(distribution))
    print(date_range)

if __name__ == '__main__':
    main()


from __future__ import division
import numpy as np
from copy import copy

# This algorithm implements a simple algorithm to project a vector onto a simplex

def Projection_onto_Simplex(vector_to_project, a):

    if len(vector_to_project) == 1:
        vector_to_return = np.zeros(1)
        vector_to_return[0] = a
        return vector_to_return

    if a == 0:
        return np.zeros(len(vector_to_project))

    # a determines the simplex: all vectors (u1, u2, ..., un) in simplex meet the property sum(u1, u2,..., un) = a
    # Step 1: set vector v and find constant rou
    vect_size = len(vector_to_project)
    N = len(vector_to_project)
    v = np.asarray(copy(vector_to_project))
    rou = (sum(vector_to_project)-a)/N

    #Step 2
    current_len_v = N
    new_v = v[np.where(v > (rou))]
    count = 0   # Keeps track of the number of iterations done in

    while len(new_v) != current_len_v and not(np.array_equal(new_v,vector_to_project))\
            and count < (vect_size*vect_size):
        current_len_v = len(new_v)
        rou = (sum(new_v)-a)/current_len_v
        new_v = v[np.where(v > (rou))]
        count += 1

    #If new_v = vector_to_project, then the projection is not able to converge, hence requiring a change of tau
    # in the original algorithm. We then return None, and do not project this vector.
    #if np.array_equal(new_v,vector_to_project):
    #    return None

    # Step 3
    tau = rou
    K = len(new_v)

    projection_of_v = np.maximum(np.subtract(v,tau*np.ones(len(v))),np.zeros(len(v)))

    if count > 20:
        a = 1

    return projection_of_v

def Projection_onto_Simplex_old(vector_to_project, a):
    v = np.asarray(copy(vector_to_project))
    u_vector = np.sort(v)[::-1]
    K = 1

    for i in range(len(u_vector)):
        if i == 0:
            temp_value = (u_vector[0]-a)/(i+1)
        else:
            temp_value = (sum(u_vector[0:i])-a)/(i+1)

        if temp_value < u_vector[i]:
            K = i+1

    if K == 1:
        tau = u_vector[0]-a/K
    else:
        tau = (sum(u_vector[0:(K-1)]) - a) / K

    projection_of_v = np.maximum(np.subtract(v, tau * np.ones(len(v))), np.zeros(len(v)))

    return projection_of_v


import numpy as np

class Adam:

    def __init__(self,
                 X,
                 alpha=0.005,
                 beta_1=0.9,
                 beta_2=0.999,
                 epsilon=1e-8,
                 **kwargs) -> None:
        self.X = X

        self.alpha = alpha
        self.beta_1 = beta_1
        self.beta_2 = beta_2
        self.epsilon = epsilon

        self.t = 0
        self.m_t = 0
        self.v_t = 0

    def apply(self, dX):
        X = self.X

        self.t += 1
        beta_1, beta_2 = self.beta_1, self.beta_2

        # update moving average of gradient and squared gradient
        self.m_t = beta_1 * self.m_t + (1 - beta_1) * dX
        self.v_t = beta_2 * self.v_t + (1 - beta_2) * (dX * dX)

        # calculates the bias-corrected estimates
        m_cap = self.m_t / (1 - (beta_1 ** self.t))
        v_cap = self.v_t / (1 - (beta_2 ** self.t))

        # do the gradient update
        self.X = X - (self.alpha * m_cap) / (np.sqrt(v_cap) + self.epsilon)

class SGD:

    def __init__(self, X, alpha=0.001, **kwargs) -> None:
        self.X = X
        self.alpha = alpha

    def apply(self, dX):
        self.X = self.X - self.alpha * dX


from sympy import *
x, w, b = symbols('x w b')
eq = x**2 + b*x + w**2
#s = solve(eq == 0, x)
#u = expand(exp(s[1]), complex=True)
#u = re(u)  # im(u)

# not smart enough for this:
"""
t = Symbol('t', real=True)
w_tilde = Symbol('w_tilde', real=True)
dt = Symbol('dt', real=True)

B = exp(I*w_tilde*t)
Q = (B.subs(t, t+dt) - 2*B + B.subs(t, t-dt))
print Q
Q = expand(Q/B, complex=True)
print Q
Q = simplify(Q)
print Q
"""

# Taylor series expansion of the numerical frequency
dt, w, t, T = symbols('dt w t T')
w_tilde_e = 2/dt*asin(w*dt/2)
w_tilde_series = w_tilde_e.series(dt, 0, 4)
print 'w_tilde series expansion:', w_tilde_series
print 'Error in frequency, leading order term:', \
      (w-w_tilde_series).as_leading_term(dt)
# Get rid of O() term
w_tilde_series = w_tilde_series.removeO()
print 'w_tilde series without O() term:', w_tilde_series

# The error mesh function (I=1)
#u_e = cos(w*t) - cos(w_tilde_e*t)  # problems with /dt around dt=0
error = cos(w*t) - cos(w_tilde_series*t)
print 'The global error:', error
print 'Series expansion of the global error:', error.series(dt, 0, 6)
print 'Series expansion of the global error:', error
error = error.series(dt, 0, 6).as_leading_term(dt)
print 'Leading order of the global error:', error
error_L2 = sqrt(integrate(error**2, (t, 0, T)))
print error_L2
#print error_L2.series(dt, 0, 2)  # break down
"""
error_L2 = simplify(error_L2.series(dt, 0, 4).as_leading_term(dt))
print 'L2 error:', error_L2
"""


# using GRADIENT DESCENT to find extrema values

'''
Use gradient descent to find the minimum value for a function
'''

import math
from sympy import Derivative, Symbol, sympify
from sympy.core.sympify import SympifyError
from collections import namedtuple

def grad_descent( x0, f1x, v ):
	epsilon = 1e-6
	step_size = 1e-4
	x_old = x0
	x_new = x_old - step_size * f1x.subs( { v: x_old } ).evalf()
	print( x_new )

	while( abs( x_old - x_new ) > epsilon ):
	   x_old = x_new
	   x_new = x_old - step_size * f1x.subs( { v: x_old } ).evalf()
	   print( x_new )

	return round( x_new, 7 )

def find_min( f, v, i ):
	# Calculate the first derivative
	f1x = Derivative( f, v ).doit()
	print( f1x )
	return grad_descent( i, f1x, v )

if __name__ == '__main__':

	try:
		f = sympify( input( 'Enter a function in one variable: ') )
		v = Symbol( input( 'Enter the variable: ' ) )
		i = float( input( 'Enter the initial value: ' ) )
	except SympifyError:
		print( 'Error parsing the function' )
	else:
		x_min = find_min( f, v )
		value = round( f.subs( { v: x_min } ).evalf(), 7 )
		print( 'The global minimum for function {0} is {1} at {2}'.format( f, x_min, value ) )


# write tests for bfs
import pytest
import numpy as np
from mst import Graph
from sklearn.metrics import pairwise_distances

def check_mst(adj_mat: np.ndarray,
              mst: np.ndarray,
              expected_weight: int,
              allowed_error: float = 0.0001):
    """ Helper function to check the correctness of the adjacency matrix encoding an MST.
        Note that because the MST of a graph is not guaranteed to be unique, we cannot
        simply check for equality against a known MST of a graph.
        Arguments:
            adj_mat: Adjacency matrix of full graph
            mst: Adjacency matrix of proposed minimum spanning tree
            expected_weight: weight of the minimum spanning tree of the full graph
            allowed_error: Allowed difference between proposed MST weight and `expected_weight`
        TODO:
            Add additional assertions to ensure the correctness of your MST implementation
        For example, how many edges should a minimum spanning tree have? Are minimum spanning trees
        always connected? What else can you think of?
    """
    def approx_equal(a, b):
        # approximately equal, given a margin of error
        return abs(a - b) < allowed_error

    total = 0
    print(mst.shape[0])
    print(adj_mat)
    for i in range(mst.shape[0]):
        for j in range(i+1):
            total += mst[i, j]
    print(total)
    print(mst)
    # checks that the total weight of the generated mst is approx equal to expected_weight
    assert approx_equal(total, expected_weight), 'Proposed MST has incorrect expected weight'

    # EDGE CHECK; mst must have V-1 edges, where V is number vertices.
    nodes_mst = adj_mat.shape[0] # how many nodes?
    mst_edges = nodes_mst - 1
    # count non_zero for each non zero is an edge
    non_zero = np.count_nonzero(mst)
    # divide count by 2 bc symmetric
    edges = non_zero/2
    assert edges == mst_edges, 'Proposed MST has incorrect number of edges'

    # SYMMETRY CHECK; recall matrix is symm. if A = A ^ T
    # both mst and adj_mat are symm
    assert np.array_equal(mst, mst.T), 'Proposed MST is not symmetric'

def test_mst_small():
    """ Unit test for the construction of a minimum spanning tree on a small graph """
    file_path = './data/small.csv'
    g = Graph(file_path)
    g.construct_mst()
    check_mst(g.adj_mat, g.mst, 8)

def test_mst_single_cell_data():
    """ Unit test for the construction of a minimum spanning tree using
    single cell data, taken from the Slingshot R package
    (https://bioconductor.org/packages/release/bioc/html/slingshot.html)
    """
    file_path = './data/slingshot_example.txt'
    # load coordinates of single cells in low-dimensional subspace
    coords = np.loadtxt(file_path)
    # compute pairwise distances for all 140 cells to form an undirected weighted graph
    dist_mat = pairwise_distances(coords)
    g = Graph(dist_mat)
    g.construct_mst()
    check_mst(g.adj_mat, g.mst, 57.263561605571695)

def test_mst_student():
    """ Extra test given the following file matrix"""
    # my file is:
    #[0 2 3]
    #[2 0 4]
    #[3 4 0]
    #
    # mst:
    #[0 2 3]
    #[2 0 0]
    #[3 0 0 ]

    file = 'data/my_test.csv'
    g = Graph(file)
    g.construct_mst()

    check_mst(g.adj_mat, g.mst, 5)


import sklearn.decomposition as dp
import numpy as np
from utils import normalize

class pca():

    def __init__(self, dim):
        self.dim = dim
        pass

    def svd(self, X):
        return np.linalg.svd(X, full_matrices=False)

    def pca(self, X):
        mean = np.mean(X, axis=0)
        X -= mean
        # returned s already been sorted, from max to min
        u, s, vh = self.svd(X)
        s = np.diag(s)[:, :self.dim]
        print(u.shape)
        print(s.shape)
        return np.dot(u,s)

if __name__ == '__main__':
    X = np.array([[1.2, 2, 3],
                  [20, 5, 6],
                  [12, 4, 9]])
    X = np.random.rand(20,10)
    pca = pca(3)
    T = pca.pca(X)
    print(T)

    p = dp.PCA(n_components=3)
    X = p.fit_transform(X)
    print(X)


from __future__ import print_function
import numpy as np

def spline_kernel(x):
    """
    Spline kernel function. This is definied such that spline_kernel(x=0) = 1,
    spline_kernel(|x|>1) = 0 and int from -1 to 1 = 3/4.
    """
    if hasattr(x, "__len__"):
        # x is an array
        y = np.zeros(len(x))
        absx = abs(x)
        ind = absx < 0.5
        y[ind] = 1 - 6*absx[ind]**2 + 6*absx[ind]**3
        ind = np.logical_and(absx >= 0.5, absx < 1.)
        y[ind] = 2*(1-absx[ind])**3
    else:
        # x is a number
        absx = abs(x)
        if   absx < 0.5: y = 1 - 6*absx**2 + 6*absx**3
        elif absx < 1:   y = 2*(1-absx)**3
        else:            y=0
    return y

def scaled_spline_kernel(x, mean=0, sig=1):
    """
    Spline kernel function which has been rescaled to have a specified mean
    and standard deviaton, and normalized
    """
    y = spline_kernel((x-mean)/(sig*np.sqrt(12)))
    return y / (0.75 * sig * np.sqrt(12))

def spline_kernel_integral(x):
    """
    Returns the integral of the unscaled spline kernel function from -1 to x
    """
    if hasattr(x, "__len__"):
        # x in an array
        integral = np.zeros(len(x))
        absx = abs(x)
        ind = absx < 0.5
        integral[ind] = absx[ind] - 2*absx[ind]**3 + 1.5*absx[ind]**4
        ind = np.logical_and(absx >= 0.5, absx < 1.)
        integral[ind] = 0.375 - 0.5*(1-absx[ind])**4
        ind = absx >= 1.
        integral[ind] = 0.375
        ind = x < 0
        integral[ind] = -integral[ind]
    else:
        # x is a number
        absx = abs(x)
        if   absx < 0.5: integral = absx - 2*absx**3 + 1.5*absx**4
        elif absx < 1:   integral = 0.375 - 0.5*(1-absx)**4
        else:            integral = 0.375
        if x < 0: integral = -integral
    return integral

def cumulative_spline_kernel(x, mean=0, sig=1):
    """
    Returns the integral of the rescaled spline kernel function from -inf to x.
    The spline kernel is rescaled to have the specified mean and standard
    deviation, and is normalized.
    """
    integral = spline_kernel_integral((x-mean)/(sig*np.sqrt(12))) / 0.75
    y = 0.5 * (1. + 2*integral)
    return y

def _get_length(arr):
    if hasattr(arr, "__len__"): return len(arr)
    else: return np.NaN

def random(size=np.NaN, mean=0, sig=1):
    """
    Draw random numbers from a spline kernel probability distribution of a
    certain mean and standard deviaton. If size is not specified, a single
    number is returned. If size is specified, an array of random numbers of
    length size is returned. mean and sig can also be arrays.
    """
    sizes = np.array([size, _get_length(mean), _get_length(sig)])
    if np.isnan(sizes).all():
        size = np.NaN
    elif np.isnan(sizes).any():
        size = int(np.max(sizes[np.invert(np.isnan(sizes))]))
    else:
        size = int(np.max(sizes))

    # check if mean or sig are arrays, check if they are the same length as size
    if (sizes[np.invert(np.isnan(sizes))] < size).any():
        raise ValueError("size mismatch")

    # if an array of random numbers is being created, convert mean/sig to arrays
    if not np.isnan(size):
        if np.isnan(sizes[1]): mean = np.ones(size, dtype="f") * mean
        if np.isnan(sizes[2]): sig = np.ones(size, dtype="f") * sig

    if size is np.NaN:
        # generate single random number
        value=0; u=1
        while u > value:
            x = np.random.rand() * 2 - 1
            value = spline_kernel(x)
            u = np.random.rand()
    elif size >= 0:
        # generate array of random numbers
        x = np.random.rand(size) * 2 - 1
        value = spline_kernel(x)
        u = np.random.rand(size)
        index = np.where(u > value)[0] #index of numbers to reject
        while len(index) > 0:
            y = np.random.rand(len(index)) * 2 - 1
            x[index] = y
            value = spline_kernel(y)
            u = np.random.rand(len(index))
            index2 = np.where(u > value)[0]
            index = index[index2]

    return (x * sig * np.sqrt(12)) + mean


# $Id$
#
#  Copyright (C) 2001-2008  greg Landrum and Rational Discovery LLC
#  All Rights Reserved
#
""" various statistical operations on data

"""
import numpy
import math

def StandardizeMatrix(mat):
  """

  This is the standard *subtract off the average and divide by the deviation*
  standardization function.

   **Arguments**

     - mat: a numpy array

   **Notes**

     - in addition to being returned, _mat_ is modified in place, so **beware**

  """
  nObjs = len(mat)
  avgs = sum(mat, 0) / float(nObjs)
  mat -= avgs
  devs = math.sqrt(sum(mat * mat, 0) / (float(nObjs - 1)))
  try:
    newMat = mat / devs
  except OverflowError:
    newMat = numpy.zeros(mat.shape, 'd')
    for i in range(mat.shape[1]):
      if devs[i] != 0.0:
        newMat[:, i] = mat[:, i] / devs[i]
  return newMat

def FormCovarianceMatrix(mat):
  """ form and return the covariance matrix

  """
  nPts = mat.shape[0]
  sumVect = sum(mat)
  sumVect /= float(nPts)
  for row in mat:
    row -= sumVect
  return numpy.dot(numpy.transpose(mat), mat) / (nPts - 1)

def FormCorrelationMatrix(mat):
  """ form and return the covariance matrix

  """
  nVars = len(mat[0])
  N = len(mat)

  res = numpy.zeros((nVars, nVars), 'd')
  for i in range(nVars):
    x = mat[:, i]
    sumX = sum(x)
    sumX2 = sum(x * x)
    for j in range(i, nVars):
      y = mat[:, j]
      sumY = sum(y)
      sumY2 = sum(y * y)
      numerator = N * sum(x * y) - sumX * sumY
      denom = numpy.sqrt((N * sumX2 - sumX**2) * (N * sumY2 - sumY**2))
      if denom != 0.0:
        res[i, j] = numerator / denom
        res[j, i] = numerator / denom
      else:
        res[i, j] = 0
        res[j, i] = 0
  return res

def PrincipalComponents(mat, reverseOrder=1):
  """ do a principal components analysis

  """
  covMat = FormCorrelationMatrix(mat)

  eigenVals, eigenVects = numpy.linalg.eig(covMat)
  # The 'real' component, if it exists as its own attribute
  eigenVals = getattr(eigenVals, "real", eigenVals)
  eigenVects = getattr(eigenVects, "real", eigenVects)

  # and now sort:
  ptOrder = numpy.argsort(eigenVals).tolist()
  if reverseOrder:
    ptOrder.reverse()
  eigenVals = numpy.array([eigenVals[x] for x in ptOrder])
  eigenVects = numpy.array([eigenVects[x] for x in ptOrder])
  return eigenVals, eigenVects

def TransformPoints(tFormMat, pts):
  """ transforms a set of points using tFormMat

    **Arguments**

      - tFormMat: a numpy array

      - pts: a list of numpy arrays (or a 2D array)

    **Returns**

      a list of numpy arrays

  """
  pts = numpy.array(pts)
  nPts = len(pts)
  avgP = sum(pts) / nPts
  pts = pts - avgP
  res = [None] * nPts
  for i in range(nPts):
    res[i] = numpy.dot(tFormMat, pts[i])

  return res

def MeanAndDev(vect, sampleSD=1):
  """ returns the mean and standard deviation of a vector """
  vect = numpy.array(vect, 'd')
  n = vect.shape[0]
  if n <= 0:
    return 0., 0.
  mean = sum(vect) / n
  v = vect - mean
  if n > 1:
    if sampleSD:
      dev = numpy.sqrt(sum(v * v) / (n - 1))
    else:
      dev = numpy.sqrt(sum(v * v) / (n))

  else:
    dev = 0
  return mean, dev

def R2(orig, residSum):
  """ returns the R2 value for a set of predictions """

  # FIX: this just is not right
  #
  #  A correct formulation of this (from Excel) for 2 variables is:
  #   r2 = [n*(Sxy) - (Sx)(Sy)]^2 / ([n*(Sx2) - (Sx)^2]*[n*(Sy2) - (Sy)^2])
  #
  #

  vect = numpy.array(orig)
  n = vect.shape[0]
  if n <= 0:
    return 0., 0.
  oMean = sum(vect) / n
  v = vect - oMean
  oVar = sum(v * v)
  return 1. - residSum / oVar

# One Tail   0.10    0.05    0.025   0.01    0.005   0.001  0.0005
tConfs = {80: 1, 90: 2, 95: 3, 98: 4, 99: 5, 99.8: 6, 99.9: 7}
tTable = [
  [1, 3.078, 6.314, 12.71, 31.82, 63.66, 318.30, 637],
  [2, 1.886, 2.920, 4.303, 6.965, 9.925, 22.330, 31.6],
  [3, 1.638, 2.353, 3.182, 4.541, 5.841, 10.210, 12.92],
  [4, 1.533, 2.132, 2.776, 3.747, 4.604, 7.173, 8.610],
  [5, 1.476, 2.015, 2.571, 3.365, 4.032, 5.893, 6.869],
  [6, 1.440, 1.943, 2.447, 3.143, 3.707, 5.208, 5.959],
  [7, 1.415, 1.895, 2.365, 2.998, 3.499, 4.785, 5.408],
  [8, 1.397, 1.860, 2.306, 2.896, 3.355, 4.501, 5.041],
  [9, 1.383, 1.833, 2.262, 2.821, 3.250, 4.297, 4.781],
  [10, 1.372, 1.812, 2.228, 2.764, 3.169, 4.144, 4.587],
  [11, 1.363, 1.796, 2.201, 2.718, 3.106, 4.025, 4.437],
  [12, 1.356, 1.782, 2.179, 2.681, 3.055, 3.930, 4.318],
  [13, 1.350, 1.771, 2.160, 2.650, 3.012, 3.852, 4.221],
  [14, 1.345, 1.761, 2.145, 2.624, 2.977, 3.787, 4.140],
  [15, 1.341, 1.753, 2.131, 2.602, 2.947, 3.733, 4.073],
  [16, 1.337, 1.746, 2.120, 2.583, 2.921, 3.686, 4.015],
  [17, 1.333, 1.740, 2.110, 2.567, 2.898, 3.646, 3.965],
  [18, 1.330, 1.734, 2.101, 2.552, 2.878, 3.610, 3.922],
  [19, 1.328, 1.729, 2.093, 2.539, 2.861, 3.579, 3.883],
  [20, 1.325, 1.725, 2.086, 2.528, 2.845, 3.552, 3.850],
  [21, 1.323, 1.721, 2.080, 2.518, 2.831, 3.527, 3.819],
  [22, 1.321, 1.717, 2.074, 2.508, 2.819, 3.505, 3.792],
  [23, 1.319, 1.714, 2.069, 2.500, 2.807, 3.485, 3.768],
  [24, 1.318, 1.711, 2.064, 2.492, 2.797, 3.467, 3.745],
  [25, 1.316, 1.708, 2.060, 2.485, 2.787, 3.450, 3.725],
  [26, 1.315, 1.706, 2.056, 2.479, 2.779, 3.435, 3.707],
  [27, 1.314, 1.703, 2.052, 2.473, 2.771, 3.421, 3.690],
  [28, 1.313, 1.701, 2.048, 2.467, 2.763, 3.408, 3.674],
  [29, 1.311, 1.699, 2.045, 2.462, 2.756, 3.396, 3.659],
  [30, 1.310, 1.697, 2.042, 2.457, 2.750, 3.385, 3.646],
  [32, 1.309, 1.694, 2.037, 2.449, 2.738, 3.365, 3.622],
  [34, 1.307, 1.691, 2.032, 2.441, 2.728, 3.348, 3.601],
  [36, 1.306, 1.688, 2.028, 2.434, 2.719, 3.333, 3.582],
  [38, 1.304, 1.686, 2.024, 2.429, 2.712, 3.319, 3.566],
  [40, 1.303, 1.684, 2.021, 2.423, 2.704, 3.307, 3.551],
  [42, 1.302, 1.682, 2.018, 2.418, 2.698, 3.296, 3.538],
  [44, 1.301, 1.680, 2.015, 2.414, 2.692, 3.286, 3.526],
  [46, 1.300, 1.679, 2.013, 2.410, 2.687, 3.277, 3.515],
  [48, 1.299, 1.677, 2.011, 2.407, 2.682, 3.269, 3.505],
  [50, 1.299, 1.676, 2.009, 2.403, 2.678, 3.261, 3.496],
  [55, 1.297, 1.673, 2.004, 2.396, 2.668, 3.245, 3.476],
  [60, 1.296, 1.671, 2.000, 2.390, 2.660, 3.232, 3.460],
  [65, 1.295, 1.669, 1.997, 2.385, 2.654, 3.220, 3.447],
  [70, 1.294, 1.667, 1.994, 2.381, 2.648, 3.211, 3.435],
  [80, 1.292, 1.664, 1.990, 2.374, 2.639, 3.195, 3.416],
  [100, 1.290, 1.660, 1.984, 2.364, 2.626, 3.174, 3.390],
  [150, 1.287, 1.655, 1.976, 2.351, 2.609, 3.145, 3.357],
  [200, 1.286, 1.653, 1.972, 2.345, 2.601, 3.131, 3.340]
]

def GetConfidenceInterval(sd, n, level=95):
  col = tConfs[level]
  dofs = n - 1
  sem = sd / numpy.sqrt(n)
  idx = 0
  while idx < len(tTable) and tTable[idx][0] < dofs:
    idx += 1
  if idx < len(tTable):
    t = tTable[idx][col]
  else:
    t = tTable[-1][col]
  return t * sem


#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# shape function management

import numpy as np
from quadrature import Gauss

class Shape(object):
    """ Traditional EF linear shape functions (2 nodes / 1st degree)
    """
    def __init__(self):
        self.xis = np.array( [-1.0, 1.0] ) # reduced coordinates of the "nodes"

    def eval(self, xi):
        """ evaluate shape fct at reduced coord "xi"
        """
        N1 = 0.5*(1-xi)
        N2 = 0.5*(1+xi)
        return np.array( [N1, N2] )

    def evalD(self, xi):
        """ first derivative of shape fcts
        """
        dN1 = -0.5
        dN2 = 0.5
        return np.array( [dN1, dN2] )

    def interp(self, vs, xi):
        """ interpolate nodal values "vs" at xi
        """
        assert len(vs)==2
        N = self.eval(xi)
        return N.dot(vs)

class Lagrange(object):
    """ Traditional Lagrange shape functions (Np nodes / degree Np-1)
        (slow - not optimised at all)
    """
    def __init__(self, Np, classic=True):
        self.Np = Np
        # reduced coordinates of the "nodes"
        if classic:
            self.xis = np.linspace(-1.0, 1.0, Np) # classical FE positions
        else:
            gauss = Gauss(Np)
            self.xis = gauss.xg                   # DG nodal basis (leads to a diagonal mass matrix)

    def eval(self, xi):
        """ evaluate shape fct at reduced coord "xi" (brutal implementation)
        """
        if isinstance(xi, float):
            Ns = np.ones( (self.Np, 1) )
        else:
            Ns = np.ones( (self.Np, xi.shape[0]) )
        for i in range(self.Np):
            for j in range(self.Np):
                if i!=j:
                    Ns[i,:] *= (self.xis[j]-xi)/(self.xis[j]-self.xis[i])
        if isinstance(xi, float):
            Ns.shape = (self.Np, )

        return Ns

    def evalD(self, xi):
        """ first derivative of shape fcts (brutal implementation)
        """
        if isinstance(xi, float):
            Ns = np.zeros( (self.Np, 1) )
        else:
            Ns = np.zeros( (self.Np, xi.shape[0]) )
        for i in range(self.Np): # we derive shp #i wrt to xi
            #sm = 0.0
            for k in range(self.Np): # sum of k terms
                prod = np.ones(xi.shape)
                if i!=k:
                    for j in range(self.Np):
                        if i!=j:
                            if j!=k:
                                prod *= (self.xis[j]-xi)/(self.xis[j]-self.xis[i])
                            else:
                                prod *= (-np.ones(xi.shape))/(self.xis[j]-self.xis[i])
                    Ns[i,:] += prod
        if isinstance(xi, float):
            Ns.shape = (self.Np, )
        return Ns

    def interp(self, vs, xi):
        """ interpolate nodal values "vs" at xi
        """
        #assert len(vs)==2
        N = self.eval(xi)
        return N.dot(vs)

if __name__=="__main__":

    # test - display Lagrange fct and derivatives of a given order
    order = 2
    shp = Lagrange(order)

    x = np.linspace(-1.0, 1.0, 100)
    s = shp.eval(np.array( x ))
    sn = shp.eval(shp.xis)
    ds = shp.evalD(np.array( x ))
    dsn = shp.evalD(shp.xis)

    # display
    import matplotlib.pyplot as plt
    plt.figure(1)
    #plt.clf()
    plt.plot(x, np.transpose(s)) #, 'ro-', markersize=4)
    plt.gca().set_prop_cycle(None)
    plt.plot(shp.xis, np.transpose(sn), 'o', markersize=8)

    plt.xlabel('t')
    plt.ylabel('N_i(x)')
    plt.grid(True)
    plt.title('Shape functions of order %d' % (shp.Np-1))

    plt.figure(2)
    #plt.clf()
    plt.plot(x, np.transpose(ds)) #, 'ro-', markersize=4)
    plt.gca().set_prop_cycle(None)
    plt.plot(shp.xis, np.transpose(dsn), 'o', markersize=8)

    plt.xlabel('t')
    plt.ylabel('dN_i/dx(x)')
    plt.grid(True)
    plt.title('Derivatives of shape functions of order %d' % (shp.Np-1))

    plt.show()


##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
##                    Split dataset into training and test sets
## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#%% read data
import numpy as np
data = np.loadtxt('quadratic_raw_data.csv', delimiter=',')
x = data[:,0,None]; y = data[:,1,None]

#%% create pipeline for quadratic fit via linear model
# import relevant classes
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression

# add transformers and estimators sequentially as list of tuples
# the names ‘poly’, ‘scaler’, ‘model’ can be used to access the individual elements of pipeline later
pipe = Pipeline([('poly', PolynomialFeatures(degree=2, include_bias=False)),
                 ('scaler', StandardScaler()),
                 ('model', LinearRegression())])

#%% separate training data
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=1)

print('Number of samples in training set: ', x_train.shape[0])
print('Number of samples in test set: ', x_test.shape[0])

#%% fit pipeline and predict
pipe.fit(x_train, y_train)
y_predicted_train = pipe.predict(x_train)
y_predicted_test = pipe.predict(x_test)

#%% performance metrics
from sklearn.metrics import mean_squared_error as mse

print('Training metric (mse) = ', mse(y_train, y_predicted_train))
print('Test metric (mse) = ', mse(y_test, y_predicted_test))

#%% plot predictions
y_predicted = pipe.predict(x)

from matplotlib import pyplot as plt
plt.figure()
plt.plot(x_train,y_train, 'bo', label='raw training data')
plt.plot(x_test,y_test, 'ro', label='raw test data')
plt.plot(x,y_predicted, color='orange', label='quadratic fit')
plt.legend()
plt.xlabel('x'), plt.ylabel('y')


#!/usr/bin/python3

import numpy as np

# 多次元配列の基本を実装する

# 一次元配列
A = np.array([1, 2, 3, 4])
print(A)

# 次元を出力する
print(np.ndim(A))

# 構造を出力する
# タプルで出力する
print(A.shape)
# タプルのためインデックスで参照することができる
print(A.shape[0])

# 二次元配列
B = np.array([[1, 2], [3, 4], [5, 6]])
print(B)
print(np.ndim(B))
print(B.shape)

# 行列の積
# 要素数が一致していないと例外が発生する
# ドット積を算出する
C = np.array([[1, 2], [3, 4], [5, 6]])
D = np.array([7, 8])
# ドット積を算出
# 内部的には1*7+2*8, 3*7+4*8, 5*7+6*8を計算している
print(np.dot(C, D))

# ニューラルネットワークではドット積を利用して計算している
# 入力値または中間値と重みをドット積で計算している


import numpy as np

def exp_growth(amp1,amp2,dur1,dur2):
    t=np.arange(0,dur1)
    Y=np.exp(t/dur2)
    # Want Y[0]=amp1
    # Want Y[-1]=amp2
    Y=Y/(Y[-1]-Y[0])*(amp2-amp1)
    Y=Y-Y[0]+amp1;
    return Y

def exp_decay(amp1,amp2,dur1,dur2):
    Y=exp_growth(amp2,amp1,dur1,dur2)
    Y=np.flipud(Y) # used to be flip, but that was not supported by older versions of numpy
    return Y

def smooth_it(Y,t):
    Z=np.zeros(Y.size)
    for j in range(-t,t+1):
        Z=Z+np.roll(Y,j)
    return Z

def synthesize_single_waveform(*,N=800,durations=[200,10,30,200],amps=[0.5,10,-1,0]):

    durations=np.array(durations).ravel()
    if (np.sum(durations)>=N-2):
        durations[-1]=N-2-np.sum(durations[0:durations.size-1])

    amps=np.array(amps).ravel()

    timepoints=np.round(np.hstack((0,np.cumsum(durations)-1))).astype('int');

    t=np.r_[0:np.sum(durations)+1]

    Y=np.zeros(len(t))
    Y[timepoints[0]:timepoints[1]+1]=exp_growth(0,amps[0],timepoints[1]+1-timepoints[0],durations[0]/4)
    Y[timepoints[1]:timepoints[2]+1]=exp_growth(amps[0],amps[1],timepoints[2]+1-timepoints[1],durations[1])
    Y[timepoints[2]:timepoints[3]+1]=exp_decay(amps[1],amps[2],timepoints[3]+1-timepoints[2],durations[2]/4)
    Y[timepoints[3]:timepoints[4]+1]=exp_decay(amps[2],amps[3],timepoints[4]+1-timepoints[3],durations[3]/5)
    Y=smooth_it(Y,3)
    Y=Y-np.linspace(Y[0],Y[-1],len(t))
    Y=np.hstack((Y,np.zeros(N-len(t))))
    Nmid=int(np.floor(N/2))
    peakind=np.argmax(np.abs(Y))
    Y=np.roll(Y,Nmid-peakind)

    return Y

#Y=smooth_it(Y,3);
#Y=Y-linspace(Y(1),Y(end),length(Y));
#
#Y=[Y,zeros(1,N-length(Y))];
#
#Nmid=floor(N/2);
#[~,peakind]=max(abs(Y));
#Y=circshift(Y,[0,Nmid-peakind]);
#
#end
#
#function test_synth_waveform
#Y=synthesize_single_waveform(800);
#figure; plot(Y);
#end
#
#function Y=exp_growth(amp1,amp2,dur1,dur2)
#t=1:dur1;
#Y=exp(t/dur2);
#% Want Y(1)=amp1
#% Want Y(end)=amp2
#Y=Y/(Y(end)-Y(1))*(amp2-amp1);
#Y=Y-Y(1)+amp1;
#end
#
#function Y=exp_decay(amp1,amp2,dur1,dur2)
#Y=exp_growth(amp2,amp1,dur1,dur2);
#Y=Y(end:-1:1);
#end
#
#function Z=smooth_it(Y,t)
#Z=Y;
#Z(1+t:end-t)=0;
#for j=-t:t
#    Z(1+t:end-t)=Z(1+t:end-t)+Y(1+t+j:end-t+j)/(2*t+1);
#end;
#end

if __name__ == '__main__':
    Y=synthesize_single_waveform()
    import matplotlib.pyplot as plt
    plt.plot(Y)


import numpy as np
import matplotlib.pyplot as plt

class NeuralNetwork:
    def __init__(self, inp_size, out_size, hid_sizes=[6, 6]):
        self.inp_size = inp_size
        self.out_size = out_size
        self.hid_sizes = hid_sizes
        self.params = self._init_params()

    @staticmethod
    def create_dataset():
        np.random.seed(42)
        num_sample = 400
        num_cls = 2
        num_sample_each_cls = int(num_sample / num_cls)
        dim = 2
        X = np.zeros((num_sample, dim))
        Y = np.zeros((num_sample, 1), dtype=np.int8)
        a = 4
        for cls in range(num_cls):
            ix = range(num_sample_each_cls * cls, num_sample_each_cls * (cls + 1))  # index for different class
            theta = np.linspace(cls * 3.12, (cls + 1) * 3.12, num_sample_each_cls) + np.random.randn(
                num_sample_each_cls) * 0.2  # theta for different class
            radius = a * np.sin(4 * theta) + np.random.randn(num_sample_each_cls) * 0.2  # radius for different class
            X[ix] = np.c_[radius * np.sin(theta), radius * np.cos(theta)]
            Y[ix] = cls
        return X, Y

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

    @staticmethod
    def _compute_loss(y_true, y_pred):
        """Compute cross entropy loss."""
        num_samples = y_true.shape[0]
        return np.sum(y_true * np.log2(y_pred) + (1 - y_true) * np.log2(1 - y_pred), axis=None) / num_samples * (-1)

    def _init_params(self):
        params = {}
        # X of shape [N, inp_size]
        # W0 of shape [inp_size, h1_size]  -->  X * W0 + b
        # then use loop to create weight_i and bias_i
        pre_size = self.inp_size
        i = 0
        for h_size in self.hid_sizes:
            params[f'W{i}'] = np.random.randn(pre_size, h_size)
            params[f'b{i}'] = np.zeros((1, h_size))
            pre_size = h_size
            i += 1
        # last hidden layer to output layer weight and bias
        params[f'W{i}'] = np.random.randn(pre_size, self.out_size)
        params[f'b{i}'] = np.zeros((1, self.out_size))
        # Test
        print('parameters:')
        for key in params.keys():
            print(key, ': ', params[key].shape)
        #
        return params

    def _forward(self, X):
        """hidden layer use tanh activation function, while output layer use sigmoid activation function."""
        i = 0
        cache = {}
        inp = X.copy()
        # hidden layers
        for _ in range(len(self.hid_sizes)):
            cache[f'Z{i}'] = np.dot(inp, self.params[f'W{i}']) + self.params[f'b{i}']
            cache[f'A{i}'] = np.tanh(cache[f'Z{i}'])
            inp = cache[f'A{i}']
            i += 1
        # output layer
        cache[f'Z{i}'] = np.dot(inp, self.params[f'W{i}']) + self.params[f'b{i}']
        cache[f'A{i}'] = self.sigmoid(cache[f'Z{i}'])
        return cache[f'A{i}'], cache

    def _backward(self, cache, X, Y):
        i = len(self.hid_sizes)
        A_n = cache[f'A{i}']  # output from output layer (after sigmoid activation). [N, 1]
        dZ_n = A_n - Y  # [N, 1]
        num_samples = X.shape[1]
        # back propagation in hidden layers
        # pass in dZ_i, A_(i-1), W_i to calculate dW_i, db_i, dZ_(i-1). Let j = i - 1
        dZ_i = dZ_n
        grads = {}
        for _ in range(len(self.hid_sizes)):
            grads[f'dW{i}'] = np.dot(cache[f'A{i - 1}'].T, dZ_i) / num_samples
            grads[f'db{i}'] = np.sum(dZ_i, axis=0, keepdims=True) / num_samples
            dZ_i = np.dot(dZ_i, self.params[f'W{i}'].T) * (1 - cache[f'A{i - 1}'] ** 2)  # tanh(x)' = 1 - tanh(x)^2
            i -= 1
        grads[f'dW{i}'] = np.dot(X.T, dZ_i) / num_samples
        grads[f'db{i}'] = np.sum(dZ_i, axis=0, keepdims=True) / num_samples

        return grads

    def _update(self, grads, lr):
        i = 0
        for _ in range(len(self.hid_sizes) + 1):
            self.params[f'W{i}'] -= lr * grads[f'dW{i}']
            self.params[f'b{i}'] -= lr * grads[f'db{i}']
            i += 1

    def train(self, X, Y, lr, epochs):
        loss_list = []
        for epoch in range(epochs):
            # forward
            Y_pred, cache = self._forward(X)
            # compute loss
            loss = self._compute_loss(Y, Y_pred)
            loss_list.append(loss)
            # backward
            grads = self._backward(cache, X, Y)
            # update parameters
            self._update(grads, lr)
            if epoch % 100 == 0:
                print(f'Epoch {epoch} loss {loss:.4f}')
        return loss_list

    def predict(self, X):
        Y_pred, _ = self._forward(X)
        return (Y_pred > 0.5).astype(np.int8)

if __name__ == '__main__':
    # create dataset
    X, Y = NeuralNetwork.create_dataset()
    # train model
    nn = NeuralNetwork(2, 1, [10, 10])
    loss_list = nn.train(X, Y, 0.001, 10000)
    # plot loss
    plt.plot(loss_list, label='loss')
    plt.show()
    # plot sample points and decision boundary
    plt.scatter(X[:, 0], X[:, 1], c=Y[:, 0], cmap=plt.cm.Spectral)
    _x_min, _x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    _y_min, _y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(_x_min, _x_max, 0.02), np.arange(_y_min, _y_max, 0.02))
    Z = nn.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    cs = plt.contourf(xx, yy, Z, cmap=plt.cm.RdYlBu, alpha=0.1)
    plt.show()


import math
import sympy
from sympy.physics import hydrogen
from typing import Any, Callable

ORIENTATION = [
    {
        0: "",
    },
    {
        0: "_z",
        +1: "_x",
        -1: "_y",
    },
    {
        0: "_z2",
        +1: "_xz",
        -1: "_yz",
        +2: "_xy",
        -2: "_x2−y2",
    },
    {
        0: "_z3",
        +1: "_xz2",
        -1: "_yz2",
        +2: "_xyz",
        -2: "_z(x2−y2)",
        +3: "_x(x2−3y2)",
        -3: "_y(3x2−y2)",
    },
]

def orbital_name(n: int, l: int, m: int) -> str:
    """
    Generate orbital's name based on quantum numbers.

    Parameters
    ----------
    n, l, m : `int`
        Quantom numbers of the orbital

    Returns
    -------
    `str`
        The orbital's name. e.g. `orbital_name`(2,1,1) -> '2p_x'

    Raises
    ------
    ValueError
        When invalid quantum numbers are given
    NotImplementedError
        Currently only supported upto f orbital
    """
    if n < 1:
        raise ValueError("'n' must be positive integer")
    if not (n > l):
        raise ValueError("'n' must be greater than 'l'")
    if not (abs(m) <= l):
        raise ValueError("|'m'| must be less or equal to 'l'")
    if not (len(ORIENTATION) > l):
        raise NotImplementedError("Currently only supported upto f orbital")
    return f"{n}{'spdf'[l]}{ORIENTATION[l][m]}"

# TODO: test performance
# opt 1. transform first & pass to spherical
# opt 2. use transformed Psi(x, y, z)
# opt 3. numpy array operation

def to_orthogonal(r, theta, phi):
    sin_theta = math.sin(theta)
    x = r * math.cos(phi) * sin_theta
    y = r * math.sin(phi) * sin_theta
    z = r * math.cos(theta)
    return x, y, z

def to_spherical(x, y, z):
    r = math.sqrt(x * x + y * y + z * z)
    theta = math.acos(z / r) if r != 0 else 0
    phi = math.atan2(y, x)
    return r, theta, phi

# Q: what about imaginary parts of Psi?
# A: there won't be any imaginary part in 'real orbital' expression

# TODO: Psi(2p_x) = ( Psi(2,1,1) + Psi(2,1,-1) ) / sqrt(2)
#       Psi(2p_y) = ( Psi(2,1,1) - Psi(2,1,-1) ) / sqrt(2) / i
def get_wavefunc(
    n: int,
    l: int,
    m: int,
    *,
    Z: int = 1,
    abs_sqrd: bool = False,
    orthogonal: bool = False,
    **options,
) -> Callable[[Any, Any, Any], Any]:
    """
    Generate wave function of atomic orbital

    Parameters
    ----------
    n, l, m : `int`
        Quantom numbers of the orbital
    Z : `int`
        Atomic number. Default is set to 1 (Hydrogen)
    abs_sqrd : `bool`
        If `True`, absolute square of wave function (probability density) is used
    orthogonal : `bool`
        If `True`, returned function will take (x, y, z) instead of (r, theta, phi)
    **options : `dict`
        These kwargs will be passed to `sympy.lambdify()`

    Returns
    -------
    Callable[[`float`, `float`, `float`], `float`]
        Function that calculates Psi(r, theta, phi)

    Notes
    -----
    Generated wave functions only returns real part
    """
    r = sympy.Symbol("r", real=True, positive=True)
    theta = sympy.Symbol("theta", real=True)
    phi = sympy.Symbol("phi", real=True)

    # XXX: hydrogen.Psi_nlm() uses order [r, phi, theta] (why??)
    # TODO: define my own Psi_nlm
    expr = hydrogen.Psi_nlm(n, l, m, r, phi, theta, Z)
    if abs_sqrd:
        expr = expr * sympy.conjugate(expr)  # probability density
    else:
        expr = sympy.re(expr)  # only take the real part
    assert isinstance(expr, sympy.Expr)

    if not orthogonal:
        return sympy.lambdify([r, theta, phi], expr, **options)

    x = sympy.Symbol("x", real=True)
    y = sympy.Symbol("y", real=True)
    z = sympy.Symbol("z", real=True)

    expr = expr.subs(
        [
            (theta, sympy.acos(z / r)),  # swap 'theta' first to expand 'r'
            (r, sympy.sqrt(x * x + y * y + z * z)),
            (phi, sympy.atan2(y, x)),
        ]
    )

    return sympy.lambdify([x, y, z], expr, **options)

class Psi_nlm:
    def __init__(self, n: int, l: int, m: int, *, Z: int = 1, prec: int = 6):
        self.n = n
        self.l = l
        self.m = m

        self.Z = Z
        self.prec = prec

        self.r = sympy.Symbol("r", real=True, positive=True)
        self.theta = sympy.Symbol("theta", real=True)
        self.phi = sympy.Symbol("phi", real=True)

        # XXX: hydrogen.Psi_nlm() uses order [r, phi, theta] (why??)
        self.expr = hydrogen.Psi_nlm(n, l, m, self.r, self.phi, self.theta, Z)

    def __repr__(self) -> str:
        n, l, m = self.n, self.l, self.m
        return f"{self.__class__.__name__}({n}, {l}, {m}, Z={self.Z}, prec={self.prec})"

    def __str__(self) -> str:
        n, l, m = self.n, self.l, self.m
        return f"{self.__class__.__name__}({orbital_name(n, l, m)})"

    def spherical(self, r, theta, phi):
        subs = {self.r: r, self.theta: theta, self.phi: phi}
        return self.expr.evalf(self.prec, subs=subs)

    def orthogonal(self, x, y, z):
        r, theta, phi = to_spherical(x, y, z)
        subs = {self.r: r, self.theta: theta, self.phi: phi}
        return self.expr.evalf(self.prec, subs=subs)

class PDF_nlm(Psi_nlm):
    def __init__(self, n: int, l: int, m: int, Z: int = 1, prec: int = 6):
        super().__init__(n, l, m, Z=Z, prec=prec)
        self.expr = self.expr * sympy.conjugate(self.expr)


import numpy as np

def _my_interp(x, xp, fp, func):
    w = xp[1:] - xp[:-1]
    idx = np.digitize(x, xp) - 1
    xf = (x - xp[idx]) / w[idx]
    f = func(xf)
    out_ndim = fp.ndim
    f = np.expand_dims(f, tuple(range(1, out_ndim)))
    return fp[idx] * (1 - f) + fp[idx + 1] * f

def poly5_interp(x, xp, fp):
    return _my_interp(x, xp, fp, func=lambda xf: 6 * xf**5 - 15 * xf**4 + 10 * xf**3)

def cosine_interp(x, xp, fp):
    return _my_interp(x, xp, fp, func=lambda xf: 0.5 * (1 - np.cos(np.pi * xf)))

def perlin_1d(size, amp, freq, octaves=1, persist=0.5, seed=None, x=None):
    if seed is not None:
        np.random.seed(seed)
    if x is None:
        x = np.arange(size)
    y = np.zeros(size)
    for _ in range(octaves):
        n = int(size * freq)
        rp = (2 * np.random.rand(n) - 1) * amp
        xp = np.linspace(0, size, n)
        y += cosine_interp(x, xp, rp)
        amp *= persist
        freq *= 2
    if octaves > 1:
        y *= (1 - persist) / (1 - persist**octaves)
    return y

def perlin_2d(width, height, amp, freq, octaves=1, persist=0.5, seed=None, x=None, y=None):
    if seed is not None:
        np.random.seed(seed)
    if x is None:
        x = np.arange(width)
    if y is None:
        y = np.arange(height)
    img = np.zeros((height, width))
    for _ in range(octaves):
        n_x = int(width * freq)
        n_y = int(height * freq)
        gp_a = 2 * np.pi * np.random.rand(n_y, n_x)
        gp = np.dstack((np.cos(gp_a), np.sin(gp_a)))

        xp = np.linspace(0, width, n_x)
        w_x = xp[1:] - xp[:-1]
        idx_x = np.digitize(x, xp) - 1
        xf = (x - xp[idx_x]) / w_x[idx_x]
        xs = 6 * xf**5 - 15 * xf**4 + 10 * xf**3

        yp = np.linspace(0, height, n_y)
        w_y = yp[1:] - yp[:-1]
        idx_y = np.digitize(y, yp) - 1
        yf = (y - yp[idx_y]) / w_y[idx_y]
        ys = 6 * yf**5 - 15 * yf**4 + 10 * yf**3

        g_ul = gp[idx_y[:, None], idx_x[None, :]]
        dot_ul = g_ul[:, :, 0] * xf[None, :] + g_ul[:, :, 1] * yf[:, None]
        g_ur = gp[idx_y[:, None], idx_x[None, :] + 1]
        dot_ur = g_ur[:, :, 0] * (1 - xf[None, :]) + g_ur[:, :, 1] * yf[:, None]
        dot_u = (1 - xs[None, :]) * dot_ul + xs[None, :] * dot_ur

        g_bl = gp[idx_y[:, None] + 1, idx_x[None, :]]
        dot_bl = g_bl[:, :, 0] * xf[None, :] + g_bl[:, :, 1] * (1 - yf[:, None])
        g_br = gp[idx_y[:, None] + 1, idx_x[None, :] + 1]
        dot_br = g_br[:, :, 0] * (1 - xf[None, :]) + g_br[:, :, 1] * (1 - yf[:, None])
        dot_b = (1 - xs[None, :]) * dot_bl + xs[None, :] * dot_br

        dot = (1 - ys[:, None]) * dot_u + ys[:, None] * dot_b
        img += amp * dot

        amp *= persist
        freq *= 2
    if octaves > 1:
        img *= (1 - persist) / (1 - persist**octaves)
    return img


import numpy as np
from showml.losses import MeanSquaredError, BinaryCrossEntropy
from showml.losses.loss_functions import CrossEntropy

def r2_score(y: np.ndarray, z: np.ndarray) -> float:
    """Calculate the r^2 (coefficient of determination) score of the model.

    Args:
        y (np.ndarray): The true values.
        z (np.ndarray): The predicted values.

    Returns:
        float: The r^2 score.
    """
    rss = np.sum(np.square(y - z))
    tss = np.sum(np.square(y - np.mean(y)))
    r_2 = 1 - (rss / tss)
    return r_2

def accuracy(y: np.ndarray, z: np.ndarray) -> float:
    """Compute the classification accuracy of the model.

    Args:
        y (np.ndarray): The true labels.
        z (np.ndarray): The predicted labels.

    Returns:
        float: The classification accuracy of the model.
    """
    if y.ndim == 1:
        # y and z are not one hot encoded
        true_class = y
        predicted_class = [1 if i > 0.5 else 0 for i in z]
    else:
        # y and z are one hot encoded
        true_class = np.argmax(y, axis=1)
        predicted_class = np.argmax(z, axis=1)

    return np.sum(true_class == predicted_class) / len(true_class)

def mean_squared_error(y: np.ndarray, z: np.ndarray) -> float:
    """Computes the Mean Squared Error (MSE).

    Args:
        y (np.ndarray): The true labels.
        z (np.ndarray): The predicted labels.

    Returns:
        float: The MSE value.
    """
    return MeanSquaredError().objective(y, z)

def binary_cross_entropy(y: np.ndarray, z: np.ndarray) -> float:
    """Computes the Binary Cross Entropy value (BCE).

    Args:
        y (np.ndarray): The true labels.
        z (np.ndarray): The predicted labels.

    Returns:
        float: the BCE value.
    """
    return BinaryCrossEntropy().objective(y, z)

def cross_entropy(y: np.ndarray, z: np.ndarray) -> float:
    """Computes the Cross Entropy value (CE).

    Args:
        y (np.ndarray): The true labels.
        z (np.ndarray): The predicted labels.

    Returns:
        float: the CE value.
    """
    return CrossEntropy().objective(y, z)