<filename>Little_Square.py
# -*- coding: utf-8 -*-
"""
Created on Wed Oct 25 10:43:58 2017

@author: <NAME>
"""

# -*- coding: utf-8 -*-
"""
Created on Tue Oct 17 06:14:39 2017

@author: Salem

starting with a single square I want to see if I can add points (which I will call grey matter) to it that will make it's 4 nodes move in a desired way.

All the points will be connected, when the cost is minimized some of the spring constants will be allowed to go to zero, (2 N - 4) of them to be specific.

Elastic energy is minimized first, then the cost function brings this energy to zero for the desired motion.

normalizeVec, connect_all_verts, makeRigidityMat are defined in LatticeMaking

Methods:
         find_desired_square(deformationType = DispType.random, edgeType = EdgeTypes.all_connected,
                        num_of_added_verts = NUM_OF_ADDED_VERTS, squareDisp = None)

         initialize_square(num_of_added_points)

         TODO fill this up
"""

import numpy as np
import numpy.random as npr
import LatticeMaking
from numpy import linalg as la
from matplotlib import pyplot as plt
import scipy.optimize as op
import importlib
importlib.reload(LatticeMaking)

from LatticeMaking import *  #custom
from enum import Enum

NUM_OF_ADDED_VERTS = 5;
NUM_OF_DIMENSIONS = 2;

#maximum number of trials before adding more vertices to the gray matter
MAX_TRIALS = 30

# the coupling constant for the energy gap in the cost function
EIG_VAL_REPULSION = 1

# the potential barier of the walls
WAll_BARRIER = 1000;

# this is the part we want to control the motion of, these vertices will be fixed.
LITTLE_SQUARE = np.array([[0.0, 0.0], [0, 1.0] , [np.cos(0*np.pi/13), np.sin(0*np.pi/13)], [np.cos(0*np.pi/13), 1 - np.sin(0*np.pi/13)]])

#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#This enum represents the different types of deformations that you can have
#TODO this def might fit in lattice making
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class DispType(Enum):
    random = 1
    isotropic = 2
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#this enumumerates the possible ways to connect the added vertices to each other and the square
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class EdgeTypes(Enum):
    all_connected = 1
    all_to_square = 2
    #square_lattice = 3
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

#================================================================================================================================================
# Runs the minimization procedure to return the results for the spring constants and the positions
#================================================================================================================================================
def find_desired_square(deformationType = DispType.random, edgeType = EdgeTypes.all_connected,
                        num_of_added_verts = NUM_OF_ADDED_VERTS, squareDisp = None, square_ID = None):
    """
    minimizes over the spring constants and positions of the added returns the result of minimization after testing.

    deformationType: this is an option for generating the desired displacement field. This is overrided if squareDisp is given
    There are two deformations options now:
        DispType.random: random displacements.
        DispType.isotropic: contraction or expansion towards the origin.

    edgeType: type of connectivity of the network
        EdgeTypes.all_connected: everything is connected to everything.
        EdgeTypes.all_to_square: every added points is connected to all the vertices of the square.
        EdgeTypes.square_lattice: an additional square lattice in the interior. corresponding corners connected.

    """
    #initialize test results so that the while loop goes at least once
    test_result = True

    #how many times the minimization procedure ran
    trial_num = 0

    #initialize the lattice
    vertices, edge_array = initialize_square(edgeType, num_of_added_verts)

    num_of_verts = vertices.size//2
    num_of_edges = edge_array.size//2

    #generate displacement field for the square. outside loop because we don't want to keep changing this
    U = make_desired_disp(vertices, num_of_vertices=num_of_verts, DeformType=deformationType)

    if(squareDisp is not None):
        U[:LITTLE_SQUARE.size - 3] = squareDisp

    while (test_result):

        # connectivity dependent matrices that are used to calculate the rigidity matrix
        edgeMat1 = makeEdgeMatrix1(edge_array, numOfEdges=num_of_edges, numOfVerts=num_of_verts)
        edgeMat2 = makeEdgeMatrix2(edge_array, numOfEdges=num_of_edges, numOfVerts=num_of_verts)

        #initialize var: points and spring constants
        k0  = npr.rand(num_of_edges)

        var0 = np.hstack((vertices.flatten(), k0))
        #the square spring constants are fixed
        var0[2*num_of_verts:2*num_of_verts + 4] = np.ones(4)

        #minimize cost funcion
        res = op.minimize(cost_function, var0, method='BFGS',args=(U, edgeMat1, edgeMat2, num_of_edges, num_of_verts), options={'disp': False})

        #this works if we are working with the script of Many little squares
        if(square_ID is not None):
            print("Working on square number ", square_ID)
        trial_num += 1; print("Trial Number: ", trial_num, "\n")

        #if this returns true then keep trying, checks if U is close to the minimum on the LITTLE_SQUARE
        test_result = test_results(res.x, U, edgeMat1, edgeMat2, num_of_edges, num_of_verts)

        #initialize the lattice again, adds a new vertex for every max_trials trials
        vertices, edge_array = initialize_square(edgeType, num_of_added_verts + trial_num//MAX_TRIALS)

        #if the number of trials increases the maximum then add a gray matter vertex
        if (np.mod(trial_num, MAX_TRIALS) == 0):

            #update num of verts and edges
            num_of_verts = vertices.size//2
            num_of_edges = edge_array.size//2

            # add the initial displacement for the extra vertex, it's essentialy a place holder
            U = np.hstack((U, npr.rand(2) - 0.5))

    #get the new vertices from the results
    newVertices = res.x[:2*num_of_verts]

    #the square ones are fixed
    newVertices[:LITTLE_SQUARE.size]  = LITTLE_SQUARE.flatten()

    newVertices = newVertices.reshape((num_of_verts, 2))

    #the resulting values of the spring constant
    newK = (res.x[2*num_of_verts:]**2)
    newK = newK/np.max(newK)
    #the square spring constants are fixed
    newK[:4] = np.ones(4)

    return [newVertices, edge_array, newK]

#================================================================================================================================================
# The cost function penalizes energy of the desired displacement of the square vertices
#================================================================================================================================================
def cost_function(var, disp_field, eMat1, eMat2, num_of_edges,num_of_vertices):
    """
    var is the combined variables to be minimized over. It represents all the vertices and spring constants
    var[:2*num_of_vertices] are the points
    var[2*num_of_vertices:] are the spring constants
    """

    #the square positions are fixed
    var[:LITTLE_SQUARE.size] = LITTLE_SQUARE.flatten()

    #the square spring constants are fixed
    var[2*num_of_vertices:2*num_of_vertices + 4] = np.ones(4)

   # var[:2*num_of_vertices] are the points of the lattice
   # var[2*num_of_vertices:] are the spring constants

    rigidityMatrix = makeRigidityMat(var[:2*num_of_vertices], edgeMat1=eMat1, edgeMat2=eMat2)[:, 3:]

    #calculate the dynamical matrix
    DynMat = makeDynamicalMat(RigidityMat= rigidityMatrix,
                              springK=var[2*num_of_vertices:], numOfVerts=num_of_vertices, numOfEdges=num_of_edges)

    # minimize the energy subject to the constraint that the square displacements are fixed
    res0 = op.minimize(energy, disp_field, method='Newton-CG', args=(DynMat, disp_field[:LITTLE_SQUARE.size - 3]), jac=energy_Der,
                       hess=energy_Hess, options={'xtol': 1e-8, 'disp': False})

    #lowestEigVector = normalizeVec(la.eigh(DynMat)[1][:5,0])
    lowestEs = lowestEigenVals(DynMat)

    #Wall_Cost = WAll_BARRIER*np.heaviside(-var[LITTLE_SQUARE.size:2*num_of_vertices:2], 1) + np.sum(var[LITTLE_SQUARE.size:2*num_of_vertices:2] - 0.5)**2
    # minimize this energy with respect to the lowest energy eigenvalue
    return res0.fun/lowestEs[0] + EIG_VAL_REPULSION * (lowestEs[0]/lowestEs[1])**2 #+ np.sum(Wall_Cost)
#================================================================================================================================================

#================================================================================================================================================
# Initializing the lattice
#================================================================================================================================================
def initialize_square(edgeType = EdgeTypes.all_connected, num_of_added_verts = NUM_OF_ADDED_VERTS):
    """
    This method returns an array of position vectors (vertices) and an array of edge vectors (edge_array).

    The vertices include a square with of unit width and (num_of_added_points) extra points that are inserted at random positions in a square
    of width = 2. The square vertices must be the first 0,1,2,3.

    Every point is connected to every other point so it generates the maximum number of edges.

    Example: initialize_square(2)
    Out[45]:
(array([[ 0.        ,  0.        ],
        [ 0.        ,  1.        ],
        [ 1.        ,  1.        ],
        [ 1.        ,  0.        ],
        [ 0.49850383,  0.26623088]]), array([[0, 1],
        [0, 2],
        [0, 3],
        [0, 4],
        [1, 2],
        [1, 3],
        [1, 4],
        [2, 3],
        [2, 4],
        [3, 4]]))
    """

    # this part I call grey matter, these are the added to the square vertices
    gray_matter = npr.rand(num_of_added_verts, NUM_OF_DIMENSIONS)*2.0 - 0.5

    # add them together to get the entire list of vertices
    vertices = np.vstack((LITTLE_SQUARE, gray_matter))

    if(edgeType == EdgeTypes.all_connected):
    # make the edge array, connect all points for now
        edge_array = connect_all_of_square(get_num_of_verts(vertices)) #connects all points of the square and gray matter. edges in definite order

    elif(edgeType == EdgeTypes.all_to_square):
        #connect each gray matter vertex to the square vertices
        edge_array = connect_all_to_square(num_of_added_verts)

    return vertices, edge_array
#================================================================================================================================================

#================================================================================================================================================
# generate the displacement field wanted
#================================================================================================================================================
def make_desired_disp(vertices, DeformType = DispType.random, num_of_vertices = -1):
    """
   DispType.random:     Makes a random displacement field. The first 3 degrees of freedom are assumed to
   be zero in order to fix rotation and translation of the lattice.
   DispType.isotropic: Every point moves towards the origin with an amount propotional to the distance from the origin
    """
    if(DeformType == DispType.random):

        if(num_of_vertices < 1):
            get_num_of_verts(vertices)

        return normalizeVec(npr.rand(2*num_of_vertices - 3))

    elif(DeformType == DispType.isotropic):
        return normalizeVec(vertices.flatten()[3:])
#================================================================================================================================================

#================================================================================================================================================
# After setting the boundary indices to the desired values, calculates the energy using the edge matrix.
#================================================================================================================================================
def energy(u, DynMat, squareDisp):
    """
    Be careful about using this in different scripts, because this assumes boundary conditions when computing the energy.
    The vertices of the squares have fixed displacements, the rest will be allowed to relax to minimum energy
    TODO: A more general energy function that takes in the boundary conditions directly

    energy(u, DynMat, BInds = boundaryIndices): calculates the energy after setting the boundary indices to the correct values.
    """
    u[:LITTLE_SQUARE.size - 3] = squareDisp #this assumes that the square vertex indices are 0,1,2,3
    u = normalizeVec(u)
    return 0.5*np.dot(np.dot(u.transpose(), DynMat), u)
#================================================================================================================================================

#================================================================================================================================================
# After setting the boundary indices to the desired values, calculates the energy gradient from the dynamical matrix.
#================================================================================================================================================
def energy_Der(u, DynMat, squareDisp):
    """
    Be careful about using this in different scripts, because this assumes boundary conditions when computing the energy.
    TO DO: A more general energy function that takes in the boundary conditions directly
    """
    u[:LITTLE_SQUARE.size - 3] = squareDisp
    u = normalizeVec(u)
    return np.dot(DynMat, u)
#================================================================================================================================================

#================================================================================================================================================
# After setting the boundary indices to the desired values, calculates the energy Hessian from the dynamical matrix.
#================================================================================================================================================
def energy_Hess(u, DynMat,  squareDisp):
    return DynMat
#================================================================================================================================================

#================================================================================================================================================
# Returns the lowest eignevalue of the dynamical matrix, exluding the rigid motions of course.
#================================================================================================================================================
def lowestEigenVals(DynMat, num_of_eigs = 2):
    return (la.eigvalsh(0.5*DynMat)[:num_of_eigs])
#================================================================================================================================================

#================================================================================================================================================
# Returns the lowest eignevalue of the dynamical matrix, exluding the rigid motions of course.
#================================================================================================================================================
def lowestEigenVal(DynMat):
    return (la.eigvalsh(0.5*DynMat)[0])
#================================================================================================================================================

#================================================================================================================================================
# Test the results of the minimization procedure
#================================================================================================================================================
def test_results(new_var, disp_field, eMat1, eMat2, num_of_edges, num_of_vertices):
    """
    this returns True if the dot product between the desired diplacement and the lowest eigen vector after minimization satisfies dotproduct < 0.95.
    this will result in trying the minimization procedure again.

    var is the combined variables to be minimized over. It represents all the vertices and spring constants
    var[:2*num_of_vertices] are the points
    var[2*num_of_vertices:] are the spring constants
    """

    #the square positions are fixed
    new_var[:LITTLE_SQUARE.size] = LITTLE_SQUARE.flatten()

   # var[:num_of_vertices] are the points of the lattice
   # var[num_of_vertices:] are the spring constants

    rigidityMatrix = makeRigidityMat(new_var[:2*num_of_vertices], edgeMat1=eMat1, edgeMat2=eMat2)[:, 3:]

    #calculate the dynamical matrix
    DynMat = makeDynamicalMat(RigidityMat= rigidityMatrix,
                              springK=new_var[2*num_of_vertices:], numOfVerts=num_of_vertices, numOfEdges=num_of_edges)

    # minimize the energy subject to the constraint that the square displacements are fixed
    res0 = op.minimize(energy, disp_field, method='Newton-CG', args=(DynMat, disp_field[:LITTLE_SQUARE.size - 3]), jac=energy_Der,
                       hess=energy_Hess, options={'xtol': 1e-8, 'disp': False})

    lowestEigVector = normalizeVec(la.eigh(DynMat)[1][:LITTLE_SQUARE.size - 3,0])
    secondEigVector = normalizeVec(la.eigh(DynMat)[1][:LITTLE_SQUARE.size - 3,1])

    #return false if the vectors are not close enough
    dotProduct = np.dot(lowestEigVector, normalizeVec(res0.x[:LITTLE_SQUARE.size - 3]))
    lowestEigVector *= np.sign(dotProduct)
    dotProduct *= np.sign(dotProduct)

    gap = (lowestEigenVals(DynMat, 2)[1] - lowestEigenVals(DynMat, 2)[0])/lowestEigenVals(DynMat, 2)[0]

    if((dotProduct < 0.995) or gap < 4):
        print("dot produce: ", dotProduct, "\n")
        print("square disps in lowest energy: ", normalizeVec(lowestEigVector[:LITTLE_SQUARE.size - 3]), "\n")
        print("square disps in desired motion: ", normalizeVec(res0.x[:LITTLE_SQUARE.size - 3]), "\n")
        print("eigenvalues: ", lowestEigenVals(DynMat, 5), "\n")
        print("gap: ", gap, "\n")
        print("trying again ... \n\n")
        return True;

    print("Number of edges: ", rigidityMatrix.shape[0], "\n")
    print("energy: ", energy(normalizeVec(res0.x), DynMat, disp_field[:LITTLE_SQUARE.size - 3]), "\n")
    print("eigenvalues: ", lowestEigenVals(DynMat, 5), "\n")
    print("dot produce: ", dotProduct, "\n")
    print("gap: ", gap, "\n")
    print("square disps in lowest energy: ", lowestEigVector, "\n")
    print("square disps in desired motion: ", normalizeVec(res0.x[:LITTLE_SQUARE.size - 3]), "\n")
    print("square disps in next to lowest: ", normalizeVec(secondEigVector[:LITTLE_SQUARE.size - 3]), "\n")

    #plotPoints(new_var[:2*num_of_vertices], num_of_vertices)

    return False
#================================================================================================================================================

#================================================================================================================================================
# plots the points as a scatter plot
#================================================================================================================================================
def plotPoints(flattenedPoints, num_of_verts = -1):
    """
    Takes in a list of point positions which is then reshaped into a list 2-vectors.
    A different color and size is chosen for the original square vertices.
    """
    if (num_of_verts < 0):
       num_of_verts = flattenedPoints.size//2

    #reshape the points to look like a list of vectors
    Points = flattenedPoints.reshape(num_of_verts, 2)

    #chose the area of the square vertices to be bigger
    area = 200*np.ones(num_of_verts)
    area[4:] *= 0.4
    #also a different color for the square vertices
    color = np.copy(area)

    plt.scatter(Points[:,0], Points[:,1], s=area, c=color)
#================================================================================================================================================


<gh_stars>1-10
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import os
import math
import numpy as np

import scipy.fftpack
import scipy as spy
from basic_sequences import LOWPAPRs, basic_generate_c_sequence, basic_cinit_calc

UL_REFERENCE_SEQUENCES_LEN = np.array([6, 12, 18, 24, 30, 36, 48, 54, 60, 72, 84,
                                       90, 96, 108, 120, 132, 144, 150, 156, 162, 168,
                                       180, 192, 204, 216, 228, 240, 252, 264, 270, 276, 288,
                                       300, 312, 324, 336, 348, 360, 384, 396, 408, 432,
                                       450, 456, 480, 486, 504, 528, 540, 552, 576, 600, 624,
                                       648, 672, 696, 720, 750, 768, 792, 810, 816, 864,
                                       900, 912, 960, 972, 1008, 1056, 1080, 1104, 1152, 1200, 1248,
                                       1296, 1344, 1350, 1440, 1458, 1500, 1536, 1584, 1620, 1632])

SRS_BANDWIDTH_CONFIG_TABLE = np.array([[0, 	4,	    1,	4,	    1,	   4,	   1,	4,	1],
                                    [1, 	8,	    1,	4,	    2,	   4,	   1,	4,	1],
                                    [2, 	12,	    1,	4,	    3,	   4,	   1,	4,	1],
                                    [3, 	16,	    1,	4,	    4,	   4,	   1,	4,	1],
                                    [4, 	16,	    1,	8,	    2,	   4,	   2,	4,	1],
                                    [5, 	20,	    1,	4,	    5,	   4,	   1,	4,	1],
                                    [6, 	24,	    1,	4,	    6,	   4,	   1,	4,	1],
                                    [7, 	24,	    1,	12,	    2,	   4,	   3,	4,	1],
                                    [8, 	28,	    1,	4,	    7,	   4,	   1,	4,	1],
                                    [9, 	32,	    1,	16,	    2,	   8,	   2,	4,	2],
                                    [10,	36,	    1,	12,	    3,	   4,	   3,	4,	1],
                                    [11,	40,	    1,	20,	    2,	   4,	   5,	4,	1],
                                    [12,	48,	    1,	16,	    3,	   8,	   2,	4,	2],
                                    [13,	48,	    1,	24,	    2,	   12,	   2,	4,	3],
                                    [14,	52,	    1,	4,	    13,	   4,	   1,	4,	1],
                                    [15,	56,	    1,	28,	    2,	   4,	   7,	4,	1],
                                    [16,	60,	    1,	20,	    3,	   4,	   5,	4,	1],
                                    [17,	64,	    1,	32,	    2,	   16,	   2,	4,	4],
                                    [18,	72,	    1,	24,	    3,	   12,	   2,	4,	3],
                                    [19,	72,	    1,	36,	    2,	   12,	   3,	4,	3],
                                    [20,	76,	    1,	4,	    19,	   4,	   1,	4,	1],
                                    [21,	80,	    1,	40, 	2,	   20,	   2,	4,	5],
                                    [22,	88,	    1,	44, 	2,	   4,	   11,	4,	1],
                                    [23,	96,	    1,	32, 	3,	   16,	   2,	4,	4],
                                    [24,	96,	    1,	48, 	2,	   24,	   2,	4,	6],
                                    [25,	104,	1,	52, 	2,	   4,	   13,	4,	1],
                                    [26,	112,	1,	56, 	2,	   28,	   2,	4,	7],
                                    [27,	120,	1,	60, 	2,	   20,	   3,	4,	5],
                                    [28,	120,	1,	40, 	3,	   8,	   5,	4,	2],
                                    [29,	120,	1,	24, 	5,	   12,	   2,	4,	3],
                                    [30,	128,	1,	64, 	2,	   32,	   2,	4,	8],
                                    [31,	128,	1,	64, 	2,	   16,	   4,	4,	4],
                                    [32,	128,	1,	16, 	8,	   8,	   2,	4,	2],
                                    [33,	132,	1,	44, 	3,	   4,	   11,	4,	1],
                                    [34,	136,	1,	68, 	2,	   4,	   17,	4,	1],
                                    [35,	144,	1,	72, 	2,	   36,	   2,	4,	9],
                                    [36,	144,	1,	48, 	3,	   24,	   2,	12,	2],
                                    [37,	144,	1,	48, 	3,	   16,	   3,	4,	4],
                                    [38,	144,	1,	16, 	9,	   8,	   2,	4,	2],
                                    [39,	152,	1,	76, 	2,	   4,	   19,	4,	1],
                                    [40,	160,	1,	80, 	2,	   40,	   2,	4,	10],
                                    [41,	160,	1,	80, 	2,	   20,	   4,	4,	5],
                                    [42,	160,	1,	32, 	5,	   16,	   2,	4,	4],
                                    [43,	168,	1,	84, 	2,	   28,	   3,	4,	7],
                                    [44,	176,	1,	88, 	2,	   44,	   2,	4,	11],
                                    [45,	184,	1,	92, 	2,	   4,	   23,	4,	1],
                                    [46,	192,	1,	96, 	2,	   48,	   2,	4,	12],
                                    [47,	192,	1,	96, 	2,	   24,	   4,	4,	6],
                                    [48,	192,	1,	64, 	3,	   16,	   4,	4,	4],
                                    [49,	192,	1,	24, 	8,	   8,	   3,	4,	2],
                                    [50,	208,	1,	104,	2,	   52,	   2,	4,	13],
                                    [51,	216,	1,	108,	2,	   36,	   3,	4,	9],
                                    [52,	224,	1,	112,	2,	   56,	   2,	4,	14],
                                    [53,	240,	1,	120,	2,	   60,	   2,	4,	15],
                                    [54,	240,	1,	80, 	3,	   20,	   4,	4,	5],
                                    [55,	240,	1,	48, 	5,	   16,	   3,	8,	2],
                                    [56,	240,	1,	24, 	10,	   12,	   2,	4,	3],
                                    [57,	256,	1,	128,	2,	   64,	   2,	4,	16],
                                    [58,	256,	1,	128,	2,	   32,	   4,	4,	8],
                                    [59,	256,	1,	16, 	16,	   8,	   2,	4,	2],
                                    [60,	264,	1,	132, 	2,	   44,	   3,	4,	11],
                                    [61,	272,	1,	136, 	2,	   68,	   2,	4,	17],
                                    [62,	272,	1,	68, 	4,	   4,	   17,	4,	1],
                                    [63,	272,	1,	16, 	17,	   8,	   2,	4,	2]])

#
#
#  1. The 1st method for the SRS receiver
#
#
def srs_freq_start_pos(frame_number, slot_number, srs_config, ttis_per_subframe):
    k_tc_p = 0
    k_0_overbar_p = 0
    N_b = 0
    m_srs_b = 0
    n_srs = 0
    f_b = 0
    n_b = 0;
    k_tc_overbar = srs_config.comb_offset
    k_tc = srs_config.comb_size
    c_srs = srs_config.config_index
    b_srs = srs_config.bandwidth_index
    b_hop = srs_config.frequency_hopping
    # /* it adjusts the SRS allocation to align with the common resource block grid in multiples of four */
    n_rrc = srs_config.frequency_position
    n_shift = srs_config.frequency_shift
    # Now, only repetitions = 0 is supported
    num_repetitions = 0
    R = 2**num_repetitions
    t_offset = srs_config.t_offset
    t_srs = srs_config.t_srs
    # /* consecutive OFDM symbols */
    n_symb_srs = srs_config.num_symbols
    l = 0;

    k_tc_p = k_tc_overbar
    # Now, only BWP start = 0 is supported
    k_0_p = n_shift * 12 + k_tc_p + 0

    for b in range(b_srs+1):
        n_b = 0;
        N_b = SRS_BANDWIDTH_CONFIG_TABLE[c_srs][2*b + 2]
        m_srs_b = SRS_BANDWIDTH_CONFIG_TABLE[c_srs][2*b + 1]

        if (b_hop >= b_srs):
            n_b = int(4 * n_rrc / m_srs_b) % N_b
        else:
            n_b = (4 * n_rrc / m_srs_b) % N_b

            if (b > b_hop):
                if (srs_config.resource_type == 0):
                    n_srs = l / R;
                else:
                    n_srs = ((20 * frame_number + slot_number - t_offset) / t_srs) * (n_symb_srs / R) + (l / R)

                product_n_b = 1
                for b_prime in np.arange(b_hop+1, b_srs):
                    product_n_b *= SRS_BANDWIDTH_CONFIG_TABLE[c_srs][2 * b_prime + 2]

                if (N_b % 2 == 1):
                    f_b = int(N_b / 2) * int(n_srs / product_n_b)
                else:
                    product_n_b_b_srs = product_n_b
                    product_n_b_b_srs *= SRS_BANDWIDTH_CONFIG_TABLE[c_srs][2 * b_srs + 2]
                    f_b = (N_b / 2) * ((n_srs % product_n_b_b_srs) / product_n_b) + ((n_srs % product_n_b_b_srs) / 2 * product_n_b)
                n_b = int(f_b + (4 * n_rrc / m_srs_b)) % N_b
        k_0_p += m_srs_b * 12 * n_b

    return k_0_p

def srs_scramble_data(data_in, length, c_init, data_out):
    x = np.zeros(2, dtype=np.uint64)
    x[0] = np.uint64(5188146772878295041)
    x1 = x[0]

    x[1] = c_init
    x2 = x[1]
    x2 = (x2 | ((x2 ^ (x2 >> np.uint64(1)) ^ (x2 >> np.uint64(2)) ^ (x2 >> np.uint64(3))) << np.uint64(31))) & np.uint64(0x7FFFFFFFFFFFFFF)
    x2 = x2 ^ (((x2 >> np.uint64(28)) ^ (x2 >> np.uint64(29)) ^ (x2 >> np.uint64(30)) ^ (x2 >> np.uint64(31))) << np.uint64(59))

    for i in np.arange(1, 25):
        x1 = (x1 >> np.uint64(2)) ^ (x1 >> np.uint64(8))
        x1 = x1 ^ (x1 << np.uint64(56)) ^ (x1 << np.uint64(62))
        x2 = (x2 >> np.uint64(2)) ^ (x2 >> np.uint64(4)) ^ (x2 >> np.uint64(6)) ^ (x2 >> np.uint64(8))
        x2 = x2 ^ (x2 << np.uint64(56)) ^ (x2 << np.uint64(58)) ^ (x2 << np.uint64(60)) ^ (x2 << np.uint64(62))

    num = ((np.uint32(length) - np.uint32(1)) >> np.uint32(6)) + np.uint32(1)
    out_tmp = data_out.view(dtype = np.uint64)
    in_tmp = data_in.view(dtype = np.uint64)
    for i in range(num):
        x1 = (x1 >> np.uint64(2)) ^ (x1 >> np.uint64(8))
        x1 = x1 ^ (x1 << np.uint64(56)) ^ (x1 << np.uint64(62))
        x2 = (x2 >> np.uint64(2)) ^ (x2 >> np.uint64(4)) ^ (x2 >> np.uint64(6)) ^ (x2 >> np.uint64(8))
        x2 = x2 ^ (x2 << np.uint64(56)) ^ (x2 << np.uint64(58)) ^ (x2 << np.uint64(60)) ^ (x2 << np.uint64(62))
        out_tmp[i] = in_tmp[i] ^ x1 ^ x2
        # print('TMP %lu %lu' %(out_tmp[i], in_tmp[i]))
    out = out_tmp.view(dtype=np.uint8)
    for i in range(length):
        data_out[i] = out[i]

def srs_group_sequence_hopping(srs_config, symb_idx, slot_idx, m_sc_b_srs):
    u = 0
    v = 0
    # generate PN sequence
    index = 0
    f_gh = 0
    num_bits = 2240;

    group_or_sequence_hopping = srs_config.group_or_sequence_hopping
    hopping_id = srs_config.sequence_id

    bit_in = np.zeros(183456, dtype=np.uint8)
    random_seq = np.zeros(num_bits, dtype=np.uint8)
    srs_scramble_data(bit_in, num_bits, hopping_id, random_seq)

    if (group_or_sequence_hopping.lower() == 'group_hopping'):
        f_gh = random_seq[slot_idx * 14 + symb_idx] % 30
    elif (group_or_sequence_hopping.lower() == 'sequence_hopping'):
        if (m_sc_b_srs >= 6 * 12):
            index = (slot_idx * 14 + symb_idx)
            v = ((random_seq[index >> 3] & (1 << (index & 7))) >> (index & 7))
    u = int(f_gh + hopping_id) % 30

    return u, v

def srs_get_cyclic_shift(point_offset, point_len, cyclic_shift_value, total_len, vec_in, vec_out):
    for i in range(total_len):
        vec_out[i] = 0
    for ii in range(point_len):
        if point_offset + ii < cyclic_shift_value :
            t1 = (point_offset + ii + total_len - cyclic_shift_value)
        else:
            t1 = (point_offset + ii - cyclic_shift_value)
        if point_offset + ii < total_len :
            t2 = (point_offset + ii)
        else:
            t2 = (point_offset + ii - total_len)
        if t2 < 0:
            t2 = t2 + total_len
        vec_out[t1] = vec_in[t2]

def srs_ch_estimates_proc(srs_config, grid, srs_pilot, nb_re_srs, n_ap, cycliShift, k_tc, nb_antennas_rx, nfft):
    aarx = 0
    n_srs_cs_max = 0

    # TS 38.211 6.4.1.4.2 Sequence generation
    if k_tc == 4:
        n_srs_cs_max = 12
    elif k_tc == 2:
        n_srs_cs_max = 8
    else:
        return

    min_cs_diff = n_srs_cs_max
    n_srs_cs_i = cycliShift
    for p_index1 in range(n_ap):
        for p_index2 in range(n_ap):
            cs_diff = n_srs_cs_i[p_index1] - n_srs_cs_i[p_index2]
            if (p_index1 != p_index2) and (abs(cs_diff) < min_cs_diff):
                min_cs_diff = abs(cs_diff)

    len_per_cs = int(nb_re_srs/n_srs_cs_max)
    win_size = min(12, min_cs_diff) * len_per_cs
    rms = 32
    peak_pos = 0
    peak_size = int((rms * nb_re_srs * k_tc - 1) / nfft) + 2
    peak_offset = 0
    if (nb_re_srs * k_tc * 32 < nfft):
        peak_size = max(int(win_size/2), 3)

    # Frequency domain data
    srs_ch_estimates_freq = np.zeros((nb_antennas_rx, nb_re_srs), dtype=complex)
    # Time domain data
    srs_ch_estimates_time = np.zeros((nb_antennas_rx, nb_re_srs), dtype=complex)
    srs_vars_tmp = np.zeros(nb_re_srs, dtype=float)
    srs_ch_estimates_time_pow = np.zeros(nb_re_srs, dtype=float)
    srs_ch_estimates_time_pow3 = np.zeros(3 * nb_re_srs, dtype=float)
    for aarx in range(nb_antennas_rx):
        # Freq channel estimation, z = x * y.conj()
        for k in range(nb_re_srs):
            srs_ch_estimates_freq[aarx][k] = grid[aarx][k] * np.conj(srs_pilot[k])

        # Direct estimate the TA from the state of noise power has not been decreased
        """
        srs_TOsum_tmp = 0
        for k in range(nb_re_srs - 1):
            srs_TOsum_tmp += srs_ch_estimates_freq[aarx][k] * np.conj(srs_ch_estimates_freq[aarx][k + 1])
        srs_TOest_tmp = np.arctan2(np.imag(srs_TOsum_tmp), np.real(srs_TOsum_tmp)) / (2 * np.pi * k_tc)
        """

        # IDFT to get time channel estimation
        srs_ch_estimates_time[aarx] = nb_re_srs * spy.fft.ifft(srs_ch_estimates_freq[aarx])

        # Get the time channel estimation Power and RSSI
        for k in range(nb_re_srs):
            srs_vars_tmp[k] = np.real(srs_ch_estimates_time[aarx][k]) * np.real(
                srs_ch_estimates_time[aarx][k]) + np.imag(srs_ch_estimates_time[aarx][k]) * np.imag(srs_ch_estimates_time[aarx][k])
            srs_ch_estimates_time_pow[k] += srs_vars_tmp[k]
    srs_rssi = np.sum(srs_ch_estimates_time_pow) / nb_re_srs

    # 3 points sum for peak detection
    if (nb_re_srs * k_tc * 32) < nfft:
        for k in range(nb_re_srs):
            srs_ch_estimates_time_pow3[nb_re_srs + k] = srs_ch_estimates_time_pow[k]
    else:
        # z[i] = r[i] + (x[i] + y[i])
        for k in range(nb_re_srs - 2):
            srs_ch_estimates_time_pow3[nb_re_srs + k + 1] = srs_ch_estimates_time_pow[k + 2] + (
                srs_ch_estimates_time_pow[k] + srs_ch_estimates_time_pow[k + 1])
        srs_ch_estimates_time_pow3[nb_re_srs] = srs_ch_estimates_time_pow[nb_re_srs - 1] + srs_ch_estimates_time_pow[0] + srs_ch_estimates_time_pow[1]
        srs_ch_estimates_time_pow3[2 * nb_re_srs - 1] = srs_ch_estimates_time_pow[nb_re_srs - 2] + srs_ch_estimates_time_pow[nb_re_srs - 1] + srs_ch_estimates_time_pow[0]
    for k in range(nb_re_srs):
        srs_ch_estimates_time_pow3[k] = srs_ch_estimates_time_pow3[nb_re_srs + k]
        srs_ch_estimates_time_pow3[2*nb_re_srs+k] = srs_ch_estimates_time_pow3[nb_re_srs + k]

    rssi_sum = srs_rssi
    # Cyclicshift process : TA estimation and power calculation
    peak_sc_sum = 0
    srs_hpow = np.zeros(n_ap, dtype=float)
    srs_TOest = np.zeros(n_ap, dtype=float)
    srs_TOsum = np.zeros(n_ap, dtype=complex)
    srs_ch_estimates_freq_pow = np.zeros((n_ap, nb_re_srs), dtype=float)
    srs_ch_estimates = np.zeros((n_ap*nb_antennas_rx, nb_re_srs), dtype=complex)
    srs_vars_tmp = np.zeros(nb_re_srs, dtype=complex)
    srs_vars_tmp1 = np.zeros(nb_re_srs, dtype=float)
    for p_index in range(n_ap):
        offset_per_cs = int(len_per_cs * ((n_srs_cs_max - n_srs_cs_i[p_index]) % n_srs_cs_max))

        # Get peak position
        max_pos = 0
        max_value = srs_ch_estimates_time_pow3[nb_re_srs+ offset_per_cs - int(win_size / 2)]
        for k in range(win_size+1):
            if (srs_ch_estimates_time_pow3[nb_re_srs+ offset_per_cs - int(win_size / 2)+k] > max_value):
                max_pos = k
                max_value = srs_ch_estimates_time_pow3[nb_re_srs+ offset_per_cs - int(win_size / 2)+k]
        peak_pos = max_pos + offset_per_cs - int(win_size / 2)
        peak_offset = peak_pos - (int((peak_size - 1) / 4) + 1)
        srs_TOest[p_index] = peak_pos - offset_per_cs

        # Remove cyclicshift
        for aarx in range(nb_antennas_rx):
            srs_get_cyclic_shift(peak_offset, peak_size, offset_per_cs, nb_re_srs, srs_ch_estimates_time[aarx], srs_vars_tmp)

            # get freq channel estimation
            srs_ch_estimates[p_index * nb_antennas_rx + aarx] = spy.fft.fft(srs_vars_tmp)

            # get freq channel estimation power
            for k in range(nb_re_srs):
                e = srs_ch_estimates[p_index * nb_antennas_rx + aarx][k]
                srs_vars_tmp1[k] = np.real(e) * np.real(e) + np.imag(e) * np.imag(e)
            # Z = X + Y * h
            for k in range(nb_re_srs):
                h = 1.0/(nb_re_srs * nb_re_srs)
                srs_ch_estimates_freq_pow[p_index][k] = srs_ch_estimates_freq_pow[p_index][k] + srs_vars_tmp1[k] * h

            # if nb_re_srs * k_tc * 16 < nfft:
            for k in range(nb_re_srs - 1):
                srs_TOsum[p_index] += srs_ch_estimates[p_index * nb_antennas_rx + aarx][k] * np.conj(srs_ch_estimates[p_index * nb_antennas_rx + aarx][k + 1])

        srs_hpow[p_index] = 0.
        for k in range(nb_re_srs):
            srs_hpow[p_index] += srs_ch_estimates_freq_pow[p_index][k]
        rssi_sum -= srs_hpow[p_index]
        peak_sc_sum += peak_size
        # print(srs_TOsum[p_index], srs_hpow[p_index], rssi_sum, peak_sc_sum)

    # Get the noise power
    srs_noisepow = rssi_sum / (nb_re_srs - peak_sc_sum) / nb_antennas_rx

    re_per_rb = int(12 / k_tc)
    nb_rb = int(nb_re_srs / re_per_rb)
    srs_wideband_snr = np.zeros(n_ap, dtype=float)
    # Get the SINR of each RB
    for p_index in range(n_ap):
        # All use frequency domain estimation
        # if (nb_re_srs * k_tc * 16) < nfft:
        srs_TOest[p_index] = np.arctan2(np.imag(srs_TOsum[p_index]), np.real(srs_TOsum[p_index])) / (2 * np.pi * k_tc)
        # else:
        #    srs_TOest[p_index] /= (nb_re_srs * k_tc)
        # Change the TA value into the seconds manner
        srs_TOest[p_index] = (srs_TOest[p_index] * 4096) / 122.88e6

        srs_wideband_snr[p_index] = 0
        tmp_sum = 0.
        for rb_idx in range(nb_rb):
            tmp_sum = 0.
            for k in range(re_per_rb):
                tmp_sum += srs_ch_estimates_freq_pow[p_index][rb_idx * re_per_rb + k]
            srs_wideband_snr[p_index] += (tmp_sum / re_per_rb / srs_noisepow)
        srs_wideband_snr[p_index] /= nb_rb
    # Collect the results
    print(srs_TOest)
    print(srs_wideband_snr)
    # The estimation of wideband SNR and TA
    sig_est = 0.
    ta_est = 0.
    for srs_cs_idx in range(srs_config.num_ports):
        sig_est += (1/srs_config.num_ports) * srs_wideband_snr[srs_cs_idx]
        ta_est += (1/srs_config.num_ports) * srs_TOest[srs_cs_idx]
    sig_est = 10.0 * math.log10(sig_est)    # dB
    return ta_est, sig_est

def srs_demodulation(sfn_id, slot_id, sys_parameters, srs_pdu, rx_grid):
    c_srs = srs_pdu.config_index
    b_srs = srs_pdu.bandwidth_index
    k_tc = srs_pdu.comb_size
    m_srs_b = SRS_BANDWIDTH_CONFIG_TABLE[c_srs][2 * b_srs + 1]
    m_sc_b_srs = int(m_srs_b * 12 / k_tc)

    k_0_p = srs_freq_start_pos(sfn_id, slot_id, srs_pdu, 2)

    l_start = srs_pdu.start_symbol_index
    # There are totally 14 time symbols
    num_of_subcarriers = int(np.shape(rx_grid)[0] / 14)
    start_subcarrier = l_start * num_of_subcarriers + k_0_p

    m_sc_b_srs_index = 0
    while ((UL_REFERENCE_SEQUENCES_LEN[m_sc_b_srs_index] != m_sc_b_srs) and (m_sc_b_srs_index < 84)):
        m_sc_b_srs_index += 1

    u, v = srs_group_sequence_hopping(srs_pdu, l_start, slot_id, m_sc_b_srs)

    srs_pilot = np.zeros(m_sc_b_srs, dtype=complex)

    # low_p1 = LOWPAPRs(0, 0, [], 6).gen_base()
    # low_p2 = LOWPAPRs(0, 0, [], 12).gen_base()
    # low_p3 = LOWPAPRs(0, 0, [], 18).gen_base()
    low_p = LOWPAPRs(u, v, [], m_sc_b_srs).gen_base()
    for i in range(m_sc_b_srs):
        srs_pilot[i] = low_p[i]

    nb_antennas_rx = sys_parameters.rx_ants
    rx_grid_ext = np.zeros((nb_antennas_rx, m_sc_b_srs), dtype=complex)
    for aarx in range(nb_antennas_rx):
        subcarrier = start_subcarrier
        for k in range(m_sc_b_srs):
            rx_grid_ext[aarx][k] = rx_grid[subcarrier][aarx]
            subcarrier += k_tc

    nfft = sys_parameters.nfft
    nrof_cyclicShift = srs_pdu.num_ports
    cycliShift = np.zeros(nrof_cyclicShift, dtype=int)
    SRS_antenna_port = [1000, 1001, 1002, 1003]
    if k_tc == 4:
        n_srs_cs_max = 12
    elif k_tc == 2:
        n_srs_cs_max = 8

    for p_index in range(nrof_cyclicShift):
        cycliShift[p_index] = (srs_pdu.cyclic_shift + (n_srs_cs_max * int(SRS_antenna_port[p_index] - 1000) / srs_pdu.num_ports)) % n_srs_cs_max

    ta_est, sig_est = srs_ch_estimates_proc(srs_pdu, rx_grid_ext, srs_pilot, m_sc_b_srs,
                                            nrof_cyclicShift, cycliShift, k_tc, nb_antennas_rx, nfft)
    return ta_est, sig_est

def srs_rx_proc(sfn_id, slot_id, sys_parameters, srs_pdu, rx_grid):
    if np.size(srs_pdu) != 1:
        print('Only support 1 SRS PDU right now !')
        sys.exit(-1)
    ta_est, sig_est = srs_demodulation(sfn_id, slot_id, sys_parameters, srs_pdu, rx_grid)
    print('Estimate SRS (TA, Sigal_strength) : %.8f %.8f' % (ta_est, sig_est))
    return ta_est, sig_est

#
#
#  2. The 2st method for the SRS receiver
#
#

def srs_generate_u_v(group_or_seq_hopping, n_id, n_sf_u, l, mzc):
    c_init = n_id
    if group_or_seq_hopping == 'neither':
        fgh = 0
        v = 0
    elif group_or_seq_hopping == 'group_hopping':
        c_len = 8 * (14 * n_sf_u + l) + 8 + 1
        c_seq = basic_generate_c_sequence(c_init, c_len)
        c_seq = c_seq[-8:]
        fgh = basic_cinit_calc(c_seq, 8)
        v = 0
    elif group_or_seq_hopping == 'sequence_hopping':
        fgh = 0
        if mzc >= 72:
            c_len = 14 * n_sf_u + l
            c_seq = basic_generate_c_sequence(c_init, c_len)
            v = c_seq[-1]
        else:
            v = 0
    else:
        print('Parameters of SRS group_or_seq_hopping error !')
        return 0, 0
    u = (fgh + n_id) % 30
    return u, v

def srs_baseseq_gen(slot_idx, group_or_seq_hopping, hopping_id, l, n_srs_re):
    u, v = srs_generate_u_v(group_or_seq_hopping, hopping_id, slot_idx, l, n_srs_re)
    low_p = LOWPAPRs(u, v, [], n_srs_re).gen_base()
    return low_p

def srs_cyclic_shift_func(vec_input, cyclicshift):
    vec_len = len(vec_input)
    vec_output = np.zeros(vec_len, dtype=complex)
    if cyclicshift > 0:
        vec_output[0:vec_len - cyclicshift] = vec_input[cyclicshift: vec_len]
        vec_output[vec_len - cyclicshift: vec_len] = vec_input[0: cyclicshift]
    elif cyclicshift < 0:
        vec_output[0: -cyclicshift] = vec_input[vec_len + cyclicshift: vec_len]
        vec_output[-cyclicshift: vec_len] = vec_input[0: vec_len + cyclicshift]
    else:
        vec_output = vec_input
    return vec_output

class SrsProc(object):
    def __init__(self, sfn, slot, sys_parameters, srs_pdu, rx_grid):

        self.n_rx                   = sys_parameters.rx_ants
        self.slot_idx               = slot
        self.symb_idx               = srs_pdu.start_symbol_index
        self.srs_parameter          = srs_pdu
        self.rx_grid                = rx_grid
        [self.num_rb, self.k0]      = self.srs_freq_position_calc()
        self.n_srs_re               = int(self.num_rb * 12 / srs_pdu.comb_size)
        self.n_cs_max               = 8 if srs_pdu.comb_size == 4 else 12
        self.n_fft                  = sys_parameters.nfft
        self.baseseq                = np.zeros((self.n_srs_re, self.srs_parameter.num_symbols), dtype=complex)

        cs_win                  = int(self.n_srs_re / self.n_cs_max)
        self.cyclicshift        = (self.srs_parameter.cyclic_shift + (self.n_cs_max * np.arange(self.srs_parameter.num_ports) / self.srs_parameter.num_ports).astype(int)) % self.n_cs_max
        n_ports                     = len(self.cyclicshift)

        min_cs_diff = self.n_cs_max
        for p_index1 in range(n_ports):
            for p_index2 in range(n_ports):
                cs_diff = self.cyclicshift[p_index1] - self.cyclicshift[p_index2]
                if ((p_index1 != p_index2) and (abs(cs_diff) < min_cs_diff)):
                    min_cs_diff = np.abs(cs_diff)

        win_size = min(4, min_cs_diff) * cs_win;  #the win size for detecte peak
        Rms = 32;
        peak_size = int((Rms * self.num_rb * 12 - 1) / self.n_fft) + 2;  #the rms point of peak
        if (self.num_rb * 12 * 32 < self.n_fft):
            peak_size = int(max(win_size / 2, 3))

        self.cyclicshift_offset     = cs_win * ((self.n_cs_max - self.cyclicshift) % self.n_cs_max)
        self.cyclicshift_winsize    = win_size
        self.peak_winsize           = peak_size

        n_re = int(self.num_rb * 12 / self.srs_parameter.comb_size)
        self.channel_est_freq             = np.zeros((self.n_rx, n_re), dtype=complex)
        self.channel_est_time             = np.zeros((self.n_rx, n_re), dtype=complex)
        self.channel_pow_time             = np.zeros(n_re, dtype = float)
        self.channel_pow3_time            = np.zeros(n_re, dtype = float)
        self.channel_est_cyclicshift      = np.zeros((self.n_rx, n_ports, n_re), dtype=complex)
        self.channel_pow_freq             = np.zeros((n_ports, n_re), dtype = float)

        self.ta_est             = np.zeros(n_ports, dtype = float)
        self.rssi               = 0
        self.noise_pow_est      = 0
        self.signal_pow_est     = np.zeros(n_ports, dtype = float)
        self.sinr_est           = np.zeros(n_ports, dtype = float)
        self.sig_est            = np.zeros(n_ports, dtype = float)

    def srs_baseseq_gen(self):

        sequence_id = self.srs_parameter.sequence_id
        group_or_sequence_hopping = self.srs_parameter.group_or_sequence_hopping

        for symb_idx in range(self.srs_parameter.num_symbols):
            l = symb_idx + self.srs_parameter.start_symbol_index
            self.baseseq[:, symb_idx] = srs_baseseq_gen(self.slot_idx, group_or_sequence_hopping, sequence_id, l, self.n_srs_re)

        print('srs_baseseq_gen Done')

    def srs_channel_ls_est(self):

        n_rx        = self.n_rx

        point_offset =  int(self.symb_idx * 3276 + self.k0)

        for symb_idx in range(self.srs_parameter.num_symbols):
            for rx_idx in range(n_rx): # extract srs re and ls est
                srs_extract = self.rx_grid[point_offset : point_offset + self.num_rb * 12 : self.srs_parameter.comb_size, rx_idx]
                self.channel_est_freq[rx_idx, :]  = srs_extract / self.baseseq[:, symb_idx]

        print('srs_channel_ls_est Done')

    def srs_channel_idft(self):

        n_rx        = self.n_rx
        n_re        = self.num_rb * 12 / self.srs_parameter.comb_size

        for rx_idx in range(n_rx): # extract srs re and ls est
            channel_idft = spy.fft.ifft(self.channel_est_freq[rx_idx, :]) * n_re
            self.channel_est_time[rx_idx, :]  = channel_idft
            self.channel_pow_time += np.real(channel_idft * np.conj(channel_idft))

        if (n_re * 32 < self.n_fft):
            self.channel_pow3_time = self.channel_pow_time
        else:
            for ii in np.array([-1, 0, 1]):
                self.channel_pow3_time = self.channel_pow3_time + srs_cyclic_shift_func(self.channel_pow_time, ii)
        self.rssi  = sum(self.channel_pow_time) / n_re

        # plt.plot(self.channel_pow_time)

        print('srs_channel_idft Done')

    def srs_channel_cyclicshift_proc(self):

        n_rx        = self.n_rx
        n_srs_re = int(self.num_rb * 12 / self.srs_parameter.comb_size)

        pow_diff = self.rssi
        peak_sc_sum = 0
        for cs_idx in range(len(self.cyclicshift)):
            for rx_idx in range(n_rx):
                h_tmp = srs_cyclic_shift_func(self.channel_est_time[rx_idx, :], self.cyclicshift_offset[cs_idx])
                pow_tmp = srs_cyclic_shift_func(self.channel_pow3_time, self.cyclicshift_offset[cs_idx])

                pow_cyclicshift = np.zeros(n_srs_re, dtype = float)
                pow_cyclicshift[0: int(self.cyclicshift_winsize/2)] = np.real(pow_tmp[0: int(self.cyclicshift_winsize/2)])
                pow_cyclicshift[n_srs_re - int(self.cyclicshift_winsize/2) : n_srs_re] = np.real(pow_tmp[n_srs_re - int(self.cyclicshift_winsize/2) : n_srs_re])

                max_index = np.where(pow_cyclicshift == np.max(pow_cyclicshift))
                peak_pos = max_index[0][0]

                # get ta est
                if peak_pos <= self.cyclicshift_winsize:
                    self.ta_est[cs_idx] = peak_pos
                else:
                    self.ta_est[cs_idx] = peak_pos - n_srs_re

                peak_offset = int(peak_pos - ((self.peak_winsize - 1) / 4 + 1));
                channel_time_cyclicshift = np.zeros(n_srs_re, dtype=complex)
                for ii in range(self.peak_winsize):
                    point_idx = peak_offset + ii
                    if (point_idx < 0):
                        point_idx += n_srs_re
                    # Add the cyclic shift for the right boundary, <NAME>, 2021.7.28
                    elif point_idx >= n_srs_re:
                        point_idx -= n_srs_re
                    channel_time_cyclicshift[point_idx] = h_tmp[point_idx]

                channel_dft = spy.fft.fft(channel_time_cyclicshift)

                self.channel_pow_freq[cs_idx, :] += np.real(channel_dft * np.conj(channel_dft)) / n_srs_re

                self.channel_est_cyclicshift[rx_idx, cs_idx, :] = channel_dft

            self.signal_pow_est[cs_idx] = sum(self.channel_pow_freq[cs_idx])/n_srs_re
            pow_diff -= self.signal_pow_est[cs_idx]
            peak_sc_sum += self.peak_winsize;

        self.noise_pow_est = pow_diff / (n_srs_re - peak_sc_sum) / n_rx

        print('srs_channel_cyclicshift_proc Done')

    def srs_channel_sinr_est(self):
        n_srs_re = int(self.num_rb * 12 / self.srs_parameter.comb_size)

        sig_est = 0.
        for cs_idx in range(len(self.cyclicshift)):
            self.sinr_est[cs_idx] = 10* np.log10(self.signal_pow_est[cs_idx] / n_srs_re / self.noise_pow_est)
            self.sig_est[cs_idx] = 10* np.log10(self.signal_pow_est[cs_idx] / n_srs_re)
            print('Signal (dB) estimated is = %.6f' % (self.sig_est[cs_idx]))
            sig_est += (1/len(self.cyclicshift)) * self.signal_pow_est[cs_idx] / n_srs_re / self.noise_pow_est

        sig_est = 10.0 * math.log10(sig_est)    # dB
        print('srs_channel_sinr_est Done')
        return sig_est

    def srs_freq_position_calc(self):
        c_srs = self.srs_parameter.config_index
        b_srs = self.srs_parameter.bandwidth_index
        comb_size = self.srs_parameter.comb_size
        comb_offset = self.srs_parameter.comb_offset
        n_shift = self.srs_parameter.frequency_shift
        n_rrc = self.srs_parameter.frequency_position
        N_rb_sc = 12

        m_srs = SRS_BANDWIDTH_CONFIG_TABLE[c_srs][2 * b_srs + 1]
        N = SRS_BANDWIDTH_CONFIG_TABLE[c_srs][2 * b_srs + 2]

        k0_bar = n_shift * N_rb_sc + comb_offset
        k0 = k0_bar
        for b in range(b_srs):
            n_b = np.floor(4 * n_rrc / m_srs) % N # only freq hopping disable
            k0 +=  n_shift + m_srs * comb_size * n_b
        return [m_srs, k0]

def srs_rx_proc2(sfn_id, slot_id, sys_parameters, srs_pdu, rx_grid):
    # Initialization
    srs_proc = SrsProc(sfn_id, slot_id, sys_parameters, srs_pdu, rx_grid)
    # Perform the receiver operation
    srs_proc.srs_baseseq_gen()
    srs_proc.srs_channel_ls_est()
    srs_proc.srs_channel_idft()
    srs_proc.srs_channel_cyclicshift_proc()
    # Set the estimated SINR
    sig_est = srs_proc.srs_channel_sinr_est()
    return sig_est


<gh_stars>1-10
from scipy.signal import sawtooth

from biofeedback_cube.config import HEIGHT, WIDTH
from ..hydra import hydra

def larson(grid, t):
    strip_height = 10
    y1 = HEIGHT // 2 - strip_height // 2
    y2 = y1 + strip_height
    f = 10.0 * hydra.f
    x = (sawtooth(f*t, width=0.5) + 1) / 2
    xw = int(x * (WIDTH))
    grid[y1:y2, xw, :] = (1, 0, 1)


<reponame>zhengsizehrb/opensim-moco
# -------------------------------------------------------------------------- #
# OpenSim Muscollo: plot_casadi_sparsity.py                                  #
# -------------------------------------------------------------------------- #
# Copyright (c) 2018 Stanford University and the Authors                     #
#                                                                            #
# Author(s): <NAME>                                              #
#                                                                            #
# Licensed under the Apache License, Version 2.0 (the "License"); you may    #
# not use this file except in compliance with the License. You may obtain a  #
# copy of the License at http://www.apache.org/licenses/LICENSE-2.0          #
#                                                                            #
# Unless required by applicable law or agreed to in writing, software        #
# distributed under the License is distributed on an "AS IS" BASIS,          #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
# See the License for the specific language governing permissions and        #
# limitations under the License.                                             #
# -------------------------------------------------------------------------- #
import scipy.sparse
import pandas as pd
import sys
import numpy as np
import pylab as pl

df = pd.read_csv(sys.argv[1], skiprows=2, sep=' ',
                 names=['row_indices', 'column_indices'])

with open(sys.argv[1]) as f:
    # The first line is a comment.
    f.readline()
    # The second line contains the number of row, columns, and nonzeroes.
    line1 = f.readline()
    numbers = line1.split(' ')
    num_rows = int(numbers[0])
    num_cols = int(numbers[1])

spmat = scipy.sparse.coo_matrix((np.ones_like(df.index),
        (df['row_indices'] - 1, df['column_indices'] - 1)),
        shape=(num_rows, num_cols))
pl.spy(spmat, markersize=2, markeredgecolor='k', marker='.')
pl.show()


<filename>data/plots/src/plot_2D_electrochem.py<gh_stars>0
from utils import *
import numpy
import matplotlib.pyplot as plt
import os, os.path
from scipy.constants import k, e, electron_volt, epsilon_0

pixels = (512, 512)

quantities = ("V", "c_p", "c_n", "zflux_cp", "zflux_cn")
units = ("V", "mol/L", "mol/L", "mol/(m$^{2}$*s)", "mol/(m$^{2}$*s)")

Vg_all = [0.001, 0.025, 0.05, 0.1, 0.15, 0.2] + list(numpy.arange(0.25, 1.35, 0.1))

file_template = "{0}.npy"

concentrations = (0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1)

ratio_conc = 10**3

# get the index of column in the new matrix
def get_col_index(Vg, quantity):
    idx_V = Vg_all.index(Vg)
    idx_quant = quantities.index(quantity)
    len_quant = len(quantities)
    return 2 + len_quant * idx_V + idx_quant

out_path = "../result/concentration/2D/"
plot_path = "../plot/concentration"

# Vg_plot = (0.001, 0.15)
Vg_plot = (0.15,)

pairs = [("c_p", 1), ("c_n", -1)]
T = 300

plt.style.use("science")

for conc in [0.001]:
    file_name = os.path.join(out_path, file_template.format(conc))
    data = get_data(file_name)  # already in nm
    X = data[:, 0].reshape(*pixels); Y = data[:, 1].reshape(*pixels)
    x = data[:, 0]; y = data[:, 1]
    # cond = numpy.where(x)
    # print(X[1, 1], Y[1, 1])
    for quant, z in pairs:
        for Vg in Vg_plot:
            print(get_col_index(Vg, quant))
            c = data[:, get_col_index(Vg, quant)]
            c[numpy.isnan(c)] = 0
            print(numpy.max(c), numpy.min(c))
            v = numpy.nan_to_num(data[:, get_col_index(Vg, "V")])
            v0 = numpy.mean(v[y>19.0])
            mu = (k * T * numpy.log(c / (conc * ratio_conc)) + z * e * v) / electron_volt
            # mu = (z * e * v) / electron_volt
            mu = (k * T * numpy.log(c / (conc * ratio_conc))) / electron_volt
            mu0 = numpy.mean(mu[y>19.5])
            mu = mu - mu0
            D = mu.reshape(*pixels)
            D[numpy.isinf(D)] = 0
            print(numpy.max(D), numpy.min(D))
            plt.cla()
            fig = plt.figure(figsize=(2.8, 2.8))
            ax = fig.add_subplot(111)
            # if z > 0:
                # vmin = -0.10; vmax = 0.01
            # else:
                # vmin = 0.008; vmax = 0.011
            mesh = plot_data(ax, X, Y, D)
            mesh.set_cmap("jet")
            ax.set_title("{0} mol/L-{1} V-{2}".format(conc,
                                                            Vg,
                                                            quant
            ))
            ax.set_xlabel("$r$ (nm)")
            ax.set_ylabel("$z$ (nm)")
            add_graphene(ax, R_p=10)
            fig.colorbar(mesh, fraction=0.03)
            fig.tight_layout()
            outfile = os.path.join(plot_path,
                                     "mu-{0}-{1}-{2}.svg".format(conc,
                                                              Vg,
                                                              quant))
            print(outfile)
            fig.savefig(outfile)


import numpy as np
import pandas as pd # added pandas module for v 0.0.5 (needed when features=3, visualize=True)
from sklearn.cluster import KMeans
from scipy.spatial.distance import cdist
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
import seaborn as sns
import warnings #v0.0.6 added warnings

class Optimal():

    """
    To find optimal number of clusters using different optimal clustering algorithms
    *citation*
    *parameters*
    *methods*
    *example*
    """

    opti_df = None

    def __init__(
        self,
        kmeans_kwargs: dict = None
    ):
        """
        *description*
        *citation*
        *parameters*
        *methods*
        *example*
        """
        self.kmeans_kwargs = kmeans_kwargs

    def elbow(self,df,upper=15,display=False,visualize=False,function='inertia',method='angle',sq_er=1):
        """
        *description*
        *citation*
        *parameters*
        *methods*
        *example*
        """
        lower=1
        inertia = []
        K=range(lower,upper)
        for i in K:
            cls = KMeans(n_clusters=i,**self.kmeans_kwargs) if self.kmeans_kwargs is not None else KMeans(n_clusters=i)
            cls_assignment = cls.fit_predict(df)
            if function=='inertia':
                inertia.append(cls.inertia_)
            elif function=='distortion':
                inertia.append(sum(np.min(cdist(df, cls.cluster_centers_,
                          'euclidean'),axis=1)) / df.shape[0])
            else:
                raise ValueError('function should be "inertia" or "distortion"')
        inertia = np.array(inertia)/(np.array(inertia)).max()*14 #v0.0.6 changed to fixed number of 14 (elbow)
        slopes = [inertia[0]-inertia[1]]
        for i in range(len(inertia)-1):
            slopes.append(-(inertia[i+1]-inertia[i]))
        angles = []
        for i in range(len(slopes)-1):
            angles.append(np.degrees(np.arctan((slopes[i]-slopes[i+1])/(1+slopes[i]*slopes[i+1]))))
        if display==True:
            plt.plot(K, inertia, 'bx-')
            plt.xlabel('Values of K')
            plt.ylabel(function)
            plt.title('The Elbow Method using '+function)
            plt.show()
        extra=''
        if method == 'angle':
            optimal = np.array(angles).argmax()+1
            confidence = round(np.array(angles).max()/90*100,2)
            if confidence<=50:
                extra=' with Confidence:'+str(confidence)+'%.'+' Try using elbow_kf, gap_stat_se or other methods, or change the method parameter to "lin"'
        elif method == 'lin': #v0.0.6 changed method for lin
            flag=False
            for i in range(len(slopes)-1):
                if (sum([(slopes[i]-slopes[j])**2 for j in range(i+1,len(slopes))]))<=sq_er:
                    optimal = i
                    flag=True
                    break
            if flag==False:
                optimal=upper-1
                warnings.warn("Optimal cluster value did not satisfy sq_er condition. Try increasing value of parameter upper for a better result")
        else:
            raise ValueError('method should be "angle" or "lin"')
        if visualize==True:
            x = self._visualization(df,optimal)
            if x=='fail':
                warnings.warn('Could not visualize: Number of columns of the DataFrame should be between 1 and 3 for visualization')#v0.0.6, changed ValueError to warning
        print('Optimal number of clusters is: ',str(optimal),extra) # raised ValueErrors for fail cases v 0.0.5
        return optimal

    def elbow_kf(self,df,upper=15,display=False,visualize=False,function='inertia',se_weight=1.5):#v0.0.6 added elbow_kf method
        """
        *description*
        *citation*
        *parameters*
        *methods*
        *example*
        """
        lower=1
        inertia = []
        K=range(lower,upper)
        for i in K:
            cls = KMeans(n_clusters=i,**self.kmeans_kwargs) if self.kmeans_kwargs is not None else KMeans(n_clusters=i)
            cls_assignment = cls.fit_predict(df)
            if function=='inertia':
                inertia.append(cls.inertia_)
            elif function=='distortion':
                inertia.append(sum(np.min(cdist(df, cls.cluster_centers_,
                          'euclidean'),axis=1)) / df.shape[0])
            else:
                raise ValueError('function should be "inertia" or "distortion"')
        inertia = np.array(inertia)/(np.array(inertia)).max()*14
        slopes = [inertia[0]-inertia[1]]
        for i in range(len(inertia)-1):
            slopes.append(-(inertia[i+1]-inertia[i]))
        if display==True:
            plt.plot(K, inertia, 'bx-')
            plt.xlabel('Values of K')
            plt.ylabel(function)
            plt.title('The Elbow Method using '+function)
            plt.show()
        means = []
        sds = []
        for i in range(len(slopes)-1):
            means.append(np.mean(np.array(slopes[i:i+3 if i+3<len(slopes) else (i+2 if i+2<len(slopes) else i+1)])))
            sds.append(np.std(np.array(slopes[i:i+3 if i+3<len(slopes) else (i+2 if i+2<len(slopes) else i+1)])))
        flag=False
        while(not flag):
            diffs = [x[0]-se_weight*x[1]>0 for x in zip(means,sds)]
            optimal = (len(diffs) - list(reversed(diffs)).index(False)) if False in diffs else -1
            if optimal==-1:
                se_weight+=0.5
                if se_weight>15:#v0.0.6 TODO: find a better way to check this fail case
                    warnings.warn('Optimal cluster not found even with high se_weight, try increasing upper parameter')
                    return -1
            else:
                flag=True
        k_factor = (round(diffs[0:optimal].count(False)/len(diffs[0:optimal]),2))
        if visualize==True:
            x = self._visualization(df,optimal)
            if x=='fail':
                warnings.warn('Could not visualize: Number of columns of the DataFrame should be between 1 and 3 for visualization')#v0.0.6, changed ValueError to warning
        extra = '' if k_factor>=0.60 else 'Lesser k_factor may be due to overlapping clusters, try increasing the se_weight parameter to '+str(se_weight+0.5)
        print('Optimal number of clusters is: ',str(optimal),' with k_factor:',str(k_factor),'.',extra)
        return optimal

    def silhouette(self,df,lower=2,upper=15,display=False,visualize=False):#v0.0.6 added slihouette method
        from sklearn.metrics import silhouette_score
        scores = []
        K=range(lower,upper)
        for i in K:
            cls = KMeans(n_clusters=i,**self.kmeans_kwargs) if self.kmeans_kwargs is not None else KMeans(n_clusters=i)
            cls_assignment = cls.fit_predict(df)
            scores.append(silhouette_score(df,cls_assignment))
        if display==True:
            plt.plot(K, scores, 'bx-')
            plt.xlabel('Values of K')
            plt.ylabel('silhouette_score')
            plt.title('Silhouette Analysis')
            plt.show()
        optimal = K[scores.index(sorted(scores,reverse=True)[0])]
        if visualize==True:
            x = self._visualization(df,optimal)
            if x=='fail':
                warnings.warn('Could not visualize: Number of columns of the DataFrame should be between 1 and 3 for visualization')#v0.0.6, changed ValueError to warning
        print('Optimal number of clusters is: ',str(optimal))
        return optimal

    def gap_stat(self,df,B=5,lower=1,upper=15,display=False,visualize=False):#v0.0.6 added gap_stat method
        if B*len(df)*upper>3000:
            warnings.warn('Many cases to check, may take some time')
        gaps = []
        K=range(lower,upper)
        for i in K:
            W_star = []
            for k in range(B):
                sample = np.random.random_sample(size=df.shape)
                W_star.append(KMeans(i).fit(sample).inertia_)
            cls = KMeans(n_clusters=i,**self.kmeans_kwargs) if self.kmeans_kwargs is not None else KMeans(n_clusters=i)
            cls_assignment = cls.fit_predict(df)
            W = cls.inertia_
            gaps.append(np.mean(np.log(W_star)) - np.log(W))
        if display==True:
            plt.plot(K, gaps, 'bx-')
            plt.xlabel('Values of K')
            plt.ylabel('Gaps')
            plt.title('Gaps Statistic Analysis')
            plt.show()
        optimal = np.array(gaps).argmax()+1
        if optimal==len(gaps):
            warnings.warn('Try increasing upper parameter for a better result.')
        if visualize==True:
            x = self._visualization(df,optimal)
            if x=='fail':
                warnings.warn('Could not visualize: Number of columns of the DataFrame should be between 1 and 3 for visualization')#v0.0.6, changed ValueError to warning
        return optimal

    def gap_stat_se(self,df,B=5,lower=1,upper=15,display=False,visualize=False,se_weight=1):
        import math
        if B*len(df)*upper>3000:
            warnings.warn('Many cases to check, may take some time')
        gaps = []
        s = []
        K=range(lower,upper)
        for i in K:
            W_star = []
            for k in range(B):
                sample = np.random.random_sample(size=df.shape)
                W_star.append(KMeans(i).fit(sample).inertia_)
            cls = KMeans(n_clusters=i,**self.kmeans_kwargs) if self.kmeans_kwargs is not None else KMeans(n_clusters=i)
            cls_assignment = cls.fit_predict(df)
            W = cls.inertia_
            gaps.append(np.mean(np.log(W_star)) - np.log(W))
            sd = np.std(np.log(W_star))
            s.append(math.sqrt(1+(1/B))*sd)
        if display==True:
            plt.plot(K, gaps, 'bx-',label='gaps')
            diff = np.append((np.array(gaps)-se_weight*np.array(s))[1:],(np.array(gaps)-se_weight*np.array(s))[-1:])
            plt.plot(K, diff, 'rx-',label='diff')
            plt.xlabel('Values of K')
            plt.ylabel('Gaps')
            plt.legend()
            plt.title('Gaps Statistic Analysis')
            plt.show()
        flag = False
        for i in range(0,len(gaps)-1):
            if (gaps[i]>=gaps[i+1]-se_weight*s[i+1]):
                optimal = i+1
                flag = True
                break
        if flag==False:
            warnings.warn('Could not find an optimal point using this method, optimal cluster returned is from gap_stat method. Try increasing upper parameter')
            optimal = np.array(gaps).argmax()+1#v0.0.6 added fail check for gap_stat_se
        if visualize==True:
            x = self._visualization(df,optimal)
            if x=='fail':
                warnings.warn('Could not visualize: Number of columns of the DataFrame should be between 1 and 3 for visualization')
        return optimal

    def gap_stat_wolog(self,df,B=5,upper=15,display=False,visualize=False,method='lin',se_weight=1,sq_er=1):
        if B*len(df)*upper>3000:
            warnings.warn('Many cases to check, may take some time')
        lower = 1
        gaps = []
        K=range(lower,upper)
        for i in K:
            W_star = []
            for k in range(B):
                sample = np.random.random_sample(size=df.shape)
                W_star.append(KMeans(i).fit(sample).inertia_)
            cls = KMeans(n_clusters=i,**self.kmeans_kwargs) if self.kmeans_kwargs is not None else KMeans(n_clusters=i)
            cls_assignment = cls.fit_predict(df)
            W = cls.inertia_
            gaps.append(np.mean((W_star)) - (W))
        gaps = (np.array(gaps)-np.array(gaps).min())/(np.array(gaps).max()-np.array(gaps).min())*14#v0.0.6 changed to fixed number of 14 (elbow)
        slopes = [gaps[0]-gaps[1]]
        for i in range(len(gaps)-1):
            slopes.append((gaps[i+1]-gaps[i]))
        if method=='angle':
            angles = []
            for i in range(len(slopes)-1):
                angles.append(np.degrees(np.arctan((slopes[i]-slopes[i+1])/(1+slopes[i]*slopes[i+1]))))
            optimal = np.array(angles).argmax()+1
        elif method=='lin':#v0.0.6 changed algo for lin method for gap_stat_wolog
            flag=False
            for i in range(len(slopes)-1):
                if (sum([(slopes[i]-slopes[j])**2 for j in range(i+1,len(slopes))]))<=sq_er:
                    optimal = i
                    flag=True
                    break
            if flag==False:
                optimal=upper-1
                warnings.warn("Optimal cluster value did not satisfy sq_er condition. Try increasing value of parameter upper for a better result")
        elif method=='max':
            optimal = np.array(gaps).argmax()+1
        else:
            raise ValueError('method should be "lin","angle" or "max"')
        if display==True:
            plt.plot(K, gaps, 'bx-')
            plt.xlabel('Values of K')
            plt.ylabel('Gaps')
            plt.title('Gaps Statistic Analysis')
            plt.show()
        if visualize==True:
            x = self._visualization(df,optimal)
            if x=='fail':
                warnings.warn('Could not visualize: Number of columns of the DataFrame should be between 1 and 3 for visualization')
        return optimal

    def _visualization(self,df,optimal):#v0.0.6 made visualization method a class method
        """
        *description*
        *citation*
        *parameters*
        *methods*
        *example*
        """
        cls = KMeans(n_clusters=optimal,**self.kmeans_kwargs) if self.kmeans_kwargs is not None else KMeans(n_clusters=optimal)
        cls_assignment = cls.fit_predict(df)
        if len(df.columns) == 1:
            col_name = df.columns[0]
            sns.stripplot(data = df,x=['']*len(df),y=col_name,hue=cls_assignment)
            plt.title('Clustering with '+str(optimal)+' clusters')
            plt.show()
        elif len(df.columns)==2:
            col_name1 = df.columns[0]
            col_name2 = df.columns[1]
            sns.scatterplot(data=df,x=col_name1,y=col_name2,hue=cls_assignment,palette='Set1')
            plt.title('Clustering with '+str(optimal)+' clusters')
            plt.show()
        elif len(df.columns)==3:
            fig = plt.figure()
            ax = plt.axes(projection="3d")
            col_name1 = df.columns[0]
            col_name2 = df.columns[1]
            col_name3 = df.columns[2]
            ax.scatter3D(xs=df[col_name1],ys=df[col_name2],zs=df[col_name3],c=pd.Series(cls_assignment))
            plt.title('Clustering with '+str(optimal)+' clusters')
            plt.show()
        else:
            return 'fail'

        #TODO: add documentation
        #TODO: add checks for upper,lower, other params
        #TODO: for optimal cluster of 1


<gh_stars>0
import math
import numpy as np
from scipy import special
import atmosphere

def variance(r0=None,L0=None,atmosphere=None):
    if atmosphere is not None:
        r0 = atmosphere.r0
        L0 = atmosphere.L0
    L0r0ratio= (L0/r0)**(5./3)
    return (24*math.gamma(6./5)/5.)**(5./6)* \
        (math.gamma(11./6)*math.gamma(5./6)/(2.*math.pi**(8./3)))*L0r0ratio

def covariance(rho,r0=None,L0=None,atmosphere=None):
    if atmosphere is not None:
        r0 = atmosphere.r0
        L0 = atmosphere.L0
    rho = np.array(rho)
    L0r0ratio= (L0/r0)**(5./3)
    cst      = (24.*math.gamma(6./5)/5.)**(5./6)* \
               (math.gamma(11./6)/(2.**(5./6)*math.pi**(8./3)))* \
                L0r0ratio
    out = np.zeros_like(rho)
    for k in range(rho.size):
        if rho[k]==0:
            out[k] = (24.*math.gamma(6./5)/5)**(5./6)* \
                     (math.gamma(11./6)*math.gamma(5./6)/(2.*math.pi**(8./3)))*L0r0ratio
        else:
            u  = 2.*math.pi*rho[k]/L0;
            out[k] = cst*u**(5./6)*special.kv(5./6,u)
    return out

def structure_function(rho,r0=None,L0=None,atmosphere=None):
    return 2*(variance(r0=r0,L0=L0,atmosphere=atmosphere) - covariance(rho,r0=r0,L0=L0,atmosphere=atmosphere))


#!/usr/bin/python3
'''Advent of Code 2018 Day 23 solution'''

import re
from statistics import median
from typing import List, Tuple, TextIO

Nanobot = Tuple[int, int, int, int]
Input = List[Nanobot]
Point = Tuple[Nanobot, int]

def taxicabdistance(a: Nanobot, b: Nanobot) -> int:
    '''Calculate Taxi Cab (Manhattan) distance between two pairs of coordinates'''
    return abs(a[0] - b[0]) + abs(a[1] - b[1]) + abs(a[2] - b[2])

def runpart1(inputs: Input) -> int:
    '''Solve part 1'''
    inputs.sort(key=lambda x: x[3], reverse=True)
    strongest = inputs[0]

    inrange = [n for n in inputs if taxicabdistance(n, strongest) <= strongest[3]]
    return len(inrange)

def runpart2(inputs: Input) -> int:
    '''Solve part 2'''
    # This deserves some explanation. Frankly, I'm amazed it worked first time. We take the median
    # of the input coordinates as a starting point, and move towards the best point in terms of
    # Nanobots in range, using distance from the origin (Our location) as tie break. We cast the
    # net very wide (searching current location +/- 10,000 initially) and gradually narrow that
    # down until we find the best point. Large initial net seems to be the key to not getting
    # caught in a local maximum, but the numbers might neet tweaking for other input data sets.
    x = int(median([a[0] for a in inputs]))
    y = int(median([a[1] for a in inputs]))
    z = int(median([a[2] for a in inputs]))

    # Size of initial scan area
    diff = 100000

    while True:
        box: List[Point] = [] # List of points we've checked

        # Check each point in turn and record nanobots in range.
        for a in [x-diff, x, x+diff]:
            for b in [y-diff, y, y+diff]:
                for c in [z-diff, z, z+diff]:
                    d: Nanobot = (a, b, c, 0)
                    inrange: int = len([n for n in inputs if taxicabdistance(n, d) <= n[3]])
                    box.append((d, inrange))

        # Get the best count of bots in range found during our checks
        box.sort(key=lambda bot: bot[1], reverse=True)
        best: int = box[0][1]

        # Get valid successors (Any point that equals the best count)
        valid: List[Point] = [n for n in box if n[1] == best]

        # Now find the best point (Closest to origin that is also valid)
        valid.sort(key=lambda p: abs(p[0][0] + abs(p[0][1]) + abs(p[0][2])))
        # If the current point is our best point...
        if (valid[0][0][0], valid[0][0][1], valid[0][0][2]) == (x, y, z):
            if diff == 1:
                # ...return the distance from the origin if we're already at minimum net size...
                return abs(x) + abs(y) + abs(z)
            # ...otherwise, shrink the net.
            diff = int(diff / 10)

        # Update the current point
        x = valid[0][0][0]
        y = valid[0][0][1]
        z = valid[0][0][2]

def readinputdata(f: TextIO) -> Input:
    '''Read input data from the given file handle into inputs'''
    inputs: Input = []

    m = re.compile(r'pos=<([-\d]+),([-\d]+),([-\d]+)>, r=([-\d]+)')
    for line in f.readlines():
        result = m.match(line)
        if result is not None:
            inputs.append((int(result.group(1)), int(result.group(2)),
                           int(result.group(3)), int(result.group(4))))

    return inputs

def run() -> Tuple[int, int]:
    '''Main'''
    with open('inputs/day23.txt', 'r') as f:
        inputs = readinputdata(f)

    return(runpart1(inputs), runpart2(inputs))

if __name__ == '__main__':
    print(run())


<gh_stars>0
import os
import pickle
from time import time

import cv2
import numpy as np
from keras.preprocessing.image import (ImageDataGenerator, array_to_img,
                                       img_to_array, load_img)
from scipy.stats import itemfreq
from skimage.feature import local_binary_pattern
from sklearn.cross_validation import train_test_split
from sklearn.decomposition import (MiniBatchDictionaryLearning, SparseCoder,
                                   dict_learning, sparse_encode)
from sklearn.metrics import classification_report
from sklearn.preprocessing import normalize
from sklearn.svm import SVC, LinearSVC
from tqdm import tqdm

bleeding_images_path = ""

normal_images_path = ""

image_name = []
for filename in os.listdir(bleeding_images_path):
    if(filename != '.ipynb_checkpoints'):
        image_name.append(os.path.join(bleeding_images_path, filename))
for filename in os.listdir(normal_images_path = ""):
    if(filename != '.ipynb_checkpoints'):
        image_name.append(os.path.join(normal_images_path, filename))

Y = np.append(np.ones(456), np.zeros(456))
Y = Y.reshape(len(image_name), 1)

X_train, X_test, y_train, y_test = train_test_split(image_name, Y, test_size=0.30, random_state=42)

print('finding the descriptors.........')
t0 = time()

keypoints_count_train = []
descriptors = []
for img_path in tqdm(X_train):
    img = cv2.imread(img_path)
    gray= cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    sift = cv2.xfeatures2d.SIFT_create()
    kp, des = sift.detectAndCompute(gray,None)
    keypoints_count_train.append(len(kp))
    descriptors.append(des)

descriptors = np.concatenate(descriptors, axis=0)

dt = time() - t0
print('done in %.2fs.' % dt)

print('descriptors.shape : ', descriptors.shape)

print('finding the test_descriptors..........')

t0 = time()

keypoints_count_test = []
test_descriptors = []

for img_path in X_test:
    img = cv2.imread(img_path)
    gray= cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    sift = cv2.xfeatures2d.SIFT_create()
    kp, des = sift.detectAndCompute(gray,None)
    keypoints_count_test.append(len(kp))
    test_descriptors.append(des)

test_descriptors = np.concatenate(test_descriptors, axis=0)

dt = time() - t0
print('done in %.2fs.' % dt)

print('test_descriptors.shape : ', test_descriptors.shape)

print('Learning the dictionary...')
t0 = time()
dico = MiniBatchDictionaryLearning(n_components=100, batch_size=10, alpha=1, n_iter=250)
dictionary = dico.fit(descriptors).components_
dt = time() - t0
print('done in %.2fs.' % dt)

print('dictionary.shape : ', dictionary.shape)

print('finding the X_train_sparse_code..........')

t0 = time()
dico.set_params(transform_algorithm='omp')
X_train_sparse_code = dico.transform(descriptors)
dt = time() - t0
print('done in %.2fs.' % dt)

print('X_train_sparse_code.shape : ', X_train_sparse_code.shape)

print('finding the X_test_sparse_code..........')

t0 = time()
dico.set_params(transform_algorithm='omp')
X_test_sparse_code = dico.transform(test_descriptors)
dt = time() - t0
print('done in %.2fs.' % dt)

print('X_test_sparse_code.shape : ', X_test_sparse_code.shape)

with open('descriptors', 'wb') as f:
    pickle.dump(descriptors, f)

with open('test_descriptors', 'wb') as f:
    pickle.dump(test_descriptors, f)

with open('X_train_sparse_code', 'wb') as f:
    pickle.dump(X_train_sparse_code, f)

with open('X_test_sparse_code', 'wb') as f:
    pickle.dump(X_test_sparse_code, f)

with open('dictionary', 'wb') as f:
    pickle.dump(dictionary, f)

with open('Dataset', 'wb') as f:
    pickle.dump([X_train, X_test, y_train, y_test], f)

print("...................saved all variables.............")

final_vectors_list_train = []
t0 = time()

last_keypoints_count_train = 0

for keypoints_count in keypoints_count_train:
    vector = np.amax(X_train_sparse_code[last_keypoints_count_train : keypoints_count + last_keypoints_count_train], axis = 0)
    final_vectors_list_train.append(vector)

    last_keypoints_count_train += keypoints_count

final_vectors_list_train = np.array(final_vectors_list_train)

dt = time() - t0
print('done in %.2fs.' % dt)

final_vectors_list_test = []
t0 = time()

last_keypoints_count_test = 0

for keypoints_count in keypoints_count_test:
    vector = np.amax(X_test_sparse_code[last_keypoints_count_test : keypoints_count + last_keypoints_count_test], axis = 0)
    final_vectors_list_test.append(vector)

    last_keypoints_count_test += keypoints_count

final_vectors_list_test = np.array(final_vectors_list_test)

dt = time() - t0
print('done in %.2fs.' % dt)

final_vectors_list_train.shape

final_vectors_list_test.shape

final_vectors_list_test

clf = LinearSVC()
clf.fit(final_vectors_list_train, y_train)
predictions = clf.predict(final_vectors_list_test)

print(classification_report(y_test, predictions))

with open('X_train_sparse_code_2', 'rb') as f:
    # Python 3: open(..., 'rb')
    X_train_sparse_code = pickle.load(f)

with open('X_test_sparse_code_2', 'rb') as f:
    # Python 3: open(..., 'rb')
    X_test_sparse_code = pickle.load(f)

with open('Dataset_2', 'rb') as f:# Python 3: open(..., 'rb')
     X_train, X_test, y_train, y_test = pickle.load(f)

print('finding the descriptors.........')
t0 = time()

keypoints_count_train = []
descriptors = []
for img_path in tqdm(X_train):
    img = cv2.imread(img_path)
    gray= cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    sift = cv2.xfeatures2d.SIFT_create()
    kp, des = sift.detectAndCompute(gray,None)
    keypoints_count_train.append(len(kp))
    descriptors.append(des)

descriptors = np.concatenate(descriptors, axis=0)

dt = time() - t0
print('done in %.2fs.' % dt)

print('finding the test_descriptors..........')

t0 = time()

keypoints_count_test = []
test_descriptors = []

for img_path in tqdm(X_test):
    img = cv2.imread(img_path)
    gray= cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    sift = cv2.xfeatures2d.SIFT_create()
    kp, des = sift.detectAndCompute(gray,None)
    keypoints_count_test.append(len(kp))
    test_descriptors.append(des)

test_descriptors = np.concatenate(test_descriptors, axis=0)

dt = time() - t0
print('done in %.2fs.' % dt)

with open('keypoints_count_train_2', 'wb') as f:
    pickle.dump(keypoints_count_train, f)

with open('keypoints_count_test_2', 'wb') as f:
    pickle.dump(keypoints_count_test, f)

final_vectors_list_train = []
t0 = time()

last_keypoints_count_train = 0

for keypoints_count in tqdm(keypoints_count_train):
    vector = np.amax(X_train_sparse_code[last_keypoints_count_train : keypoints_count + last_keypoints_count_train], axis = 0)
    final_vectors_list_train.append(vector)

    last_keypoints_count_train += keypoints_count

final_vectors_list_train = np.array(final_vectors_list_train)

dt = time() - t0
print('done in %.2fs.' % dt)

print('final_vectors_list_train.shape : ', final_vectors_list_train.shape)

final_vectors_list_test = []
t0 = time()

last_keypoints_count_test = 0

for keypoints_count in tqdm(keypoints_count_test):
    vector = np.amax(X_test_sparse_code[last_keypoints_count_test : keypoints_count + last_keypoints_count_test], axis = 0)
    final_vectors_list_test.append(vector)

    last_keypoints_count_test += keypoints_count

final_vectors_list_test = np.array(final_vectors_list_test)

dt = time() - t0
print('done in %.2fs.' % dt)

print('final_vectors_list_test.shape : ', final_vectors_list_test.shape)

with open('final_vectors_list_train_2', 'wb') as f:
    pickle.dump(final_vectors_list_test, f)

with open('final_vectors_list_train_2', 'wb') as f:
    pickle.dump(final_vectors_list_test, f)

clf = LinearSVC(C=0.001, loss='hinge', penalty='l2')
clf.fit(final_vectors_list_train, y_train)
predictions = clf.predict(final_vectors_list_test)

print(classification_report(y_test, predictions))

clf_2 = SVC(C=10.0, kernel='rbf', gamma=0.00001)

clf_2.fit(final_vectors_list_train, y_train)
predictions_2 = clf_2.predict(final_vectors_list_test)

print(classification_report(y_test, predictions_2))

radius = 3
no_points = 8 * radius

lbp_features_train = []

for img_path in tqdm(X_train):
    im = cv2.imread(img_path)
    im_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
    lbp = local_binary_pattern(im_gray, no_points, radius, method='uniform')
    x = itemfreq(lbp.ravel())
    hist = x[:, 1] / sum(x[:, 1])
    lbp_features_train.append(hist)

lbp_features_train = np.array(lbp_features_train)
lbp_features_train.shape

X_train_lbp = np.concatenate((final_vectors_list_train, lbp_features_train), axis = 1)
X_train_lbp.shape

lbp_features_test = []

for img_path in tqdm(X_test):
    im = cv2.imread(img_path)
    im_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
    lbp = local_binary_pattern(im_gray, no_points, radius, method='uniform')
    x = itemfreq(lbp.ravel())
    hist = x[:, 1] / sum(x[:, 1])
    lbp_features_test.append(hist)

X_test_lbp = np.concatenate((final_vectors_list_test, lbp_features_test), axis = 1)
X_test_lbp.shape

clf_3 = SVC(C=1.0, kernel='rbf', gamma=0.00001)

clf_3.fit(X_train_lbp, y_train)
predictions_3 = clf_3.predict(X_test_lbp)

print(classification_report(y_test, predictions_3))

final_vectors_list_train.shape

with open('X_train_lbp', 'wb') as f:
    pickle.dump(X_train_lbp, f)

with open('X_test_lbp', 'wb') as f:
    pickle.dump(X_test_lbp, f)

with open('lbp_features_train', 'wb') as f:
    pickle.dump(lbp_features_train, f)

with open('lbp_features_test', 'wb') as f:
    pickle.dump(lbp_features_test, f)


from math import sin, cos
from scipy.integrate import quad
from scipy.optimize import minimize

# This is based on the tutorial found here: https://tinyurl.com/92dwh52
# This sample code will likely be useful: https://tinyurl.com/4br894dt

class PathGenerator:
    def __init__(self, start_x, start_y, start_theta, start_curvature,
                        goal_x, goal_y, goal_theta, goal_curvature,
                        alpha=15, beta=25, gamma=35, kmax=0.5):
        """ Takes start and end coordinates, heading, and curvature,
        returns a curve that connects them. Alpha, beta, and gamma
        scale our soft constraints for x, y, and theta. For example,
        if we want our curve endpoint to be constrained very tightly
        to our goal conditions, we make these large. If we are okay with
        a bit of error, we make these small. Kmax determinines the
        max sharpness of a turn. If we want a very smooth ride, we make this
        small, if we are okay with sharp turns, we make this large."""

        # Start conditions
        self.x0, self.y0 = start_x, start_y
        self.t0, self.k0 = start_theta, start_curvature
        # End conditions
        self.xf, self.yf = goal_x, goal_y
        self.tf, self.kf = goal_theta, goal_curvature
        # Constraints
        self.alpha, self.beta, self.gamma, self.kmax = alpha, beta, gamma, kmax

        # We break the curve into 3 equal sections
        # We are solving for p1, p2, p4 (p0 & p3 are constant)
        # self.p0 = self.k0           # Starting point of curve
        # self.p1 = "1/3 down curve"  # 1/3 down curve
        # self.p2 = "2/3 down curve"  # 2/3 down curve
        # self.p3 = self.kf           # Ending point of curve
        # self.p4 = "Length of curve" # Length of curve
        self.p0 = self.k0
        self.p1 = 1./3.
        self.p2 = 2./3.
        self.p3 = self.kf
        self.p4 = 1.

        # Constraints for the optimization problem
        self.bound_p1 = [-1*self.kmax, self.kmax]    # p1's constraint
        self.bound_p2 = [-1*self.kmax, self.kmax]    # p2's constraint
        self.bounds = [self.bound_p1, self.bound_p2]

        # TODO: optimize self.objective_function
        # path_raw = "Optimization of self.objective_function() for params p1, p2, p4"
        # Pseudocode layout?: optimize(self.objective_function, params=[self.p1, self.p2, self.p4], bounds=self.bounds)
        x_0 = [self.p1, self.p2, self.p4]  # initial guess
        print('initial guess')
        print(self.objective_function(x_0))
        output_params = minimize(self.objective_function, x_0)
        print(output_params)
        # path = "The path above, but mapping the p values back to spiral parameters"
        self.p1 = output_params['x'][0]
        self.p2 = output_params['x'][1]
        self.p4 = output_params['x'][2]

        self.a_list = [self.a0_map(), self.a1_map(self.p1, self.p2, self.p4), self.a2_map(self.p1, self.p2, self.p4), self.a3_map(self.p1, self.p2, self.p4)]

        # Sample the curve to get our points
        s_i = 0
        self.interval = 0.25
        self.t_list = []
        self.k_list = []

        # get theta(s) and k(s) for all 's' with step size 'self.interval'
        while s_i <= self.p4:
            k_list.append(self.final_spiral(s_i))
            t_list.append(self.final_theta(s_i))
            s_i += self.interval

        # use our theta(s) values to find x(s) and y(s) values with trapezoid rule
        # the index 's' values should align with t_list and k_list (might be off by 1)
        x_list = self.x_trapezoidal()
        x_list.insert(0, self.x0)
        x_list.insert(-1, self.xf)

        y_list = self.y_trapezoidal()
        y_list.insert(0, self.y0)
        y_list.insert(-1, self.yf)

        self.path = [x_list, y_list, t_list, k_list]

        # information of our s values, if helpful
        self.s_info = {'min': 0, 'max':s_i, 'interval':self.interval}

    def final_spiral(self, s):
        """ Our final k(s) equation """
        return self.a_list[3]*s**3 + self.a_list[2]*s**2 + self.a_list[1]*s + self.a_list[0]

    def final_theta(self, s):
        """ Our final theta(s) equation """
        return self.t0 + self.a_list[3]/4*s**4 + self.a_list[2]/3*s**3 + self.a_list[1]/2*s**2 + a_list[0]*s

    def x_trapezoidal(self):
        """ Uses the trapezoidal rule to estimate x.
        x = x0 + (cos(s)+cos(s+interval))*interval/2
        We use indeces to access our precomputed cos(s) values
        spaced out by our interval (hence the index approach)"""
        x_list = []
        # Calculates values up to the second to last index
        for s_i in range(len(self.t_list)-2):
            x_s = self.x0 + (1/2)*(cos(self.t_list[s_i])+cos(self.t_list[s_i+1]))*self.interval
            x_list.append(x_s)
        return x_list[]

    def y_trapezoidal(self):
        """ Uses the trapezoidal rule to estimate y.
        y = y0 + (sin(s)+sin(s+interval))*interval/2
        We use indeces to access our precomputed sin(s) values
        spaced out by our interval (hence the index approach)"""
        y_list = []
        # Calculates values up to the second to last index
        for s_i in range(len(self.t_list)-2):
            y_s = self.y0 + (1/2)*(sin(self.t_list[s_i])+sin(self.t_list[s_i+1]))*self.interval
            y_list.append(y_s)
        return y_list[]

    def objective_function(self, x):
        """ the parameters to optimize are p1, p2, and p4 """
        p1 = x[0]
        p2 = x[1]
        p4 = x[2]

        # objective function needs to return a scalar value for scipy.optimize to work - Nicole
        result = self.f_be(p1, p2, p4) + self.x_soft(p1, p2, p4) + self.y_soft(p1, p2, p4) + self.theta_soft(p1, p2, p4)
        return result[0]

    def k_s(self, s, p1, p2, p4):
        """ Our cubic spiral equation. Not sure if we need this """
        return self.a3_map(p1, p2, p4)*s**3 + self.a2_map(p1, p2, p4)*s**2 + self.a1_map(p1, p2, p4)*s + self.a0_map()

    def f_be_integrand(self, s, a0, a1, a2, a3):
        """ Integrand to use with f_be()
        This is our cubic spiral equation squared
        integrated for variable 's' from 0 to p4"""
        return (a3*s**3 + a2*s**2 + a1*s + a0)**2

    def f_be(self, p1, p2, p4):
        """ Unconstrained objective function, using the quad integral solver
        from SciPy on our objective_integrand (variable 's')
        from 0 to curve length p4, using coefficients a0, a1, a2, and a3 """
        spiral_vals = (self.a0_map(), self.a1_map(p1, p2, p4), self.a2_map(p1, p2, p4), self.a3_map(p1, p2, p4))
        return quad(self.f_be_integrand, 0, p4, args=spiral_vals)

    def x_soft(self, p1, p2, p4):
        """ Soft inequality constraints, allows a small
        margin of error between goal point and final point
        in the curve. Scaled by alpha. """
        s = p4
        return self.alpha*(self.x_s(s, p1, p2, p4) - self.xf)

    def y_soft(self, p1, p2, p4):
        """ Soft inequality constraints, allows a small
        margin of error between goal point and final point
        in the curve. Scaled by beta. """
        s = p4
        return self.beta*(self.y_s(s, p1, p2, p4) - self.yf)

    def theta_soft(self, p1, p2, p4):
        """ Soft inequality constraints, allows a small
        margin of error between goal point and final point
        in the curve. Scaled by gamma. """
        s = p4
        return self.gamma*(self.theta_s(s, p1, p2, p4) - self.tf)

    def x_s(self, s, p1, p2, p4):
        """ Estimates x value at location 's' along curve
        using Simpson's rule (divide domain into n=8 sections) """
        n0 = cos(self.theta_s(0, p1, p2, p4))
        n1 = 4*cos(self.theta_s(1*s/8, p1, p2, p4))
        n2 = 2*cos(self.theta_s(2*s/8, p1, p2, p4))
        n3 = 4*cos(self.theta_s(3*s/8, p1, p2, p4))
        n4 = 2*cos(self.theta_s(4*s/8, p1, p2, p4))
        n5 = 4*cos(self.theta_s(5*s/8, p1, p2, p4))
        n6 = 2*cos(self.theta_s(6*s/8, p1, p2, p4))
        n7 = 4*cos(self.theta_s(7*s/8, p1, p2, p4))
        n8 = cos(self.theta_s(s, p1, p2, p4))
        n_sum = n0+n1+n2+n3+n4+n5+n6+n7+n8
        return self.x0 + (s/24)*(n_sum)

    def y_s(self, s, p1, p2, p4):
        """ Estimates y value at location 's' along curve
        using Simpson's rule (divide domain into n=8 sections) """
        n0 = sin(self.theta_s(0, p1, p2, p4))
        n1 = 4*sin(self.theta_s(1*s/8, p1, p2, p4))
        n2 = 2*sin(self.theta_s(2*s/8, p1, p2, p4))
        n3 = 4*sin(self.theta_s(3*s/8, p1, p2, p4))
        n4 = 2*sin(self.theta_s(4*s/8, p1, p2, p4))
        n5 = 4*sin(self.theta_s(5*s/8, p1, p2, p4))
        n6 = 2*sin(self.theta_s(6*s/8, p1, p2, p4))
        n7 = 4*sin(self.theta_s(7*s/8, p1, p2, p4))
        n8 = sin(self.theta_s(s, p1, p2, p4))
        n_sum = n0+n1+n2+n3+n4+n5+n6+n7+n8
        return self.y0 + (s/24)*(n_sum)

    def theta_s(self, s, p1, p2, p4):
        """ Finds theta value at location 's' along curve """
        s4 = self.a3_map(p1, p2, p4) * s**4 / 4
        s3 = self.a2_map(p1, p2, p4) * s**3 / 3
        s2 = self.a1_map(p1, p2, p4) * s**2 / 2
        s1 = self.a0_map() * s
        return self.t0+s4+s3+s2+s1

    def a0_map(self):
        """ Map between optimization params and spiral coefficients. """
        return self.p0

    def a1_map(self, p1, p2, p4):
        """ Map between optimization params and spiral coefficients. """
        num = -1*(11*self.p0/2 - 9*p1 + 9*p2/2 - self.p3)
        denom = p4
        return num/denom

    def a2_map(self, p1, p2, p4):
        """ Map between optimization params and spiral coefficients. """
        num = 9*self.p0 - 45*p1/2 + 18*p2 - 9*self.p3/2
        denom = p4**2
        return num/denom

    def a3_map(self, p1, p2, p4):
        """ Map between optimization params and spiral coefficients. """
        num = -1*(9*self.p0/2 - 27*p1/2 + 27*p2/2 - 9*self.p3/2)
        denom = p4**3
        return num/denom