import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as anm
import matplotlib.collections as clt
import pandas as pd
import string as st

def initial_setup(n_of_particles, packing = 0.3, height = 4, radius=1.4):
    """
    This function returns an array of initial positions for confined particles, and
    a region where these particles are enclosed with a packing fraction "packing"
    The particles are initially set in a square array, as far from each other as possible.
    """
    part_in_edge = int(np.round(np.sqrt(n_of_particles)))
    n_of_particles = part_in_edge**2

    area_particle = n_of_particles*radius**2*np.pi
    area_region = area_particle/packing

    length_region = np.sqrt(area_region)
    part_separation = length_region/part_in_edge

    x_loc = np.linspace(
        -length_region/2+part_separation/2,
        length_region/2-part_separation/2,part_in_edge)
    y_loc = np.linspace(
        -length_region/2+part_separation/2,
        length_region/2-part_separation/2,part_in_edge)

    [X,Y] = np.meshgrid(x_loc,y_loc)
    Z = np.zeros(np.shape(X))

    initial_positions = np.array([[x,y,z] for (x,y,z) in zip(X.flatten(),Y.flatten(),Z.flatten())])

    if part_separation<2*radius:
        raise ValueError("packing is too high")

    region = [np.round(length_region),np.round(length_region),height]
    return region, initial_positions

def ordered_dimers(n_of_particles = 150, packing=0.35, height = 4.1, r = 1.4, theta = 0, verbose = False):
    """
    This function returns an array of initial positions of a given packing fraction, arranged in a hexagonal lattice of preformed dimers. Dimers are in contact with each other and each colloid is in contact with an opposite wall. All dimmers are point in the same direction which is given by the angle theta
    """

    N_dimers = lambda Nt: np.round(Nt/2)

    N_columns = lambda Nt: np.round(np.sqrt(Nt)/2)*2
    N_rows = lambda Nt: np.round(Nt/N_columns(Nt))

    N_particles = 2*N_columns(N_dimers(n_of_particles))*N_rows(N_dimers(n_of_particles))

    if verbose:
        print("Creating %u particles; %u dimers \n"%(N_particles, N_particles/2))
        print("packing should be", packing)

    a = lambda phi: np.sqrt(6*np.pi/(phi*3**(3/2)))

    if verbose:
        print("The lattice constant is a = %2.3f"%a(packing))

    size = lambda phi,Nt: (N_columns(Nt)*a(phi),N_rows(Nt)*a(phi)*np.sqrt(3/4))

    def lattice(Nt):

        grid = np.meshgrid(
            np.arange(0,N_columns(Nt)),
            np.arange(0,N_rows(Nt)))

        primitive_vectors = np.array([[1,0],[1/2,np.sqrt(3/4)]])

        lattice = (grid[0]*primitive_vectors[0,0]+grid[1]*primitive_vectors[1,0],
                   grid[0]*primitive_vectors[0,1]+grid[1]*primitive_vectors[1,1])

        lattice = (np.mod(lattice[0],N_columns(Nt)),np.mod(lattice[1],N_rows(Nt)))

        return lattice[0]+0.25,lattice[1]+0.5*np.sqrt(3/4)

    h = height/r

    ps_in_dimer = np.array([
            [np.sqrt(h*(4-h))*np.cos(theta),
             np.sqrt(h*(4-h))*np.sin(theta),h-2],
            [-np.sqrt(h*(4-h))*np.cos(theta),
             -np.sqrt(h*(4-h))*np.sin(theta),2-h]])

    positions = lambda Nt,phi,rho: np.array(
            [[r*a(packing/2)*l1,r*a(packing/2)*l2,0] + p/rho
             for l1,l2 in zip(
                 lattice(N_dimers(Nt))[0].flatten(),
                 lattice(N_dimers(Nt))[1].flatten()) for p in ps_in_dimer ])

    def region(N_dimers,h) :
        regionsize = np.array(size(packing/2,N_dimers))
        regionsize = np.array([regionsize[0],regionsize[1],h])
        return regionsize

    region = region(N_dimers(N_particles),h)*r
    positions = positions(N_particles,packing,r)-np.append(region[0:2]/2,[0])

    if verbose:
        print("The region is ", region)
        print(len(positions)," positions created")
        region_a = region[0]*region[1]
        partic_a = np.pi*r**2*len(positions)
        print("packing is actually %2.2f/%2.2f = %2.2f"%(partic_a,region_a,partic_a/region_a))

    return region, positions

def hexagonal_order(n_of_particles = 150, packing=0.35, height = 4.1, r = 1.4, theta = 0, verbose = False):
    """
    This function returns an array of initial positions of a given packing fraction, arranged in a hexagonal lattice of preformed dimers. Dimers are in contact with each other and each colloid is in contact with an opposite wall. All dimmers are point in the same direction which is given by the angle theta
    """

    N_columns = lambda Nt: np.round(np.sqrt(Nt))
    N_rows = lambda Nt: np.round(Nt/N_columns(Nt))

    N_particles = N_columns(n_of_particles)*N_rows(n_of_particles)

    if verbose:
        print("Creating %u particles; %u dimers \n"%(N_particles, N_particles/2))
        print("packing should be", packing)

    a = lambda phi: r*np.sqrt(2*np.pi/(phi*3**(1/2)))

    if verbose:
        print("The lattice constant is a = %2.3f"%a(packing))

    size = lambda phi,Nt: (N_columns(Nt)*a(phi),N_rows(Nt)*a(phi)*np.sqrt(3/4))

    def lattice(Nt):

        grid = np.meshgrid(
            np.arange(0,N_columns(Nt)),
            np.arange(0,N_rows(Nt)))

        primitive_vectors = np.array([[1,0],[1/2,np.sqrt(3/4)]])

        lattice = (grid[0]*primitive_vectors[0,0]+grid[1]*primitive_vectors[1,0],
                   grid[0]*primitive_vectors[0,1]+grid[1]*primitive_vectors[1,1])

        lattice = (np.mod(lattice[0],N_columns(Nt)),np.mod(lattice[1],N_rows(Nt)))

        return lattice[0]+0.25,lattice[1]+0.5*np.sqrt(3/4)

    positions = lambda Nt,phi, r: np.array(
            [[a(phi)*l1,a(phi)*l2,0]
             for l1,l2 in zip(
                 lattice(Nt)[0].flatten(),
                 lattice(Nt)[1].flatten())])

    def region(N_particles,h,packing,r) :
        regionsize = np.array(size(packing,N_particles))
        region_area = regionsize[0]*regionsize[1]
        partic_area = N_particles*np.pi*r**2
        target_area = partic_area/packing
        if verbose:
            print("region was scaled by %s"%(target_area/region_area)**(1/2))
        regionsize = regionsize * (target_area/region_area)**(1/2)

        regionsize = np.array([regionsize[0],regionsize[1],h])
        return regionsize

    region = region(N_particles,height,packing,r)
    positions = positions(N_particles, packing, r)-np.append(region[0:2]/2,[0])

    if verbose:
        print("The region is ", region)
        print(len(positions)," positions created")
        region_a = region[0]*region[1]
        partic_a = np.pi*r**2*len(positions)
        print("packing is actually %2.2f/%2.2f = %2.2f"%(partic_a,region_a,partic_a/region_a))

    return region, positions

def animate_trj(trj, sim = None, region = None, radius = None, framerate = None, ax=False, verb=False, start=0, end=False, step = 1, speedup = 1, preserve_limits = False, time_index = "frame", color_field = "z", color_label='$z [\mu{m}]$', clim = None, cmap = None):
    """
    This function animates the trajectory resulting from a confined dimer simulation. It displays the z direction as a colormap and the particles in the x and y direction. The simulation is required as argument to obtain parameters like the region size and the particles radius.
    Optional parameters are:
    * ax: an axis object to use for creating the plot.
    * start: start time of the simulation if not the whole time is required. The default is 0.
    * end: end time of the simulation. The default is the total simulation time.
    * step = 1. The framerate, so to speak.
    * verb = False. If verb = True, the routine prints indicators that is running.
    * speedup allows us to do faster videos. Default is 1, which means normal ratio.
    todo:
    Fix for bidisperse particles. Take draw_trj as example.
    """

    from mpl_toolkits.axes_grid1 import make_axes_locatable

    idx = pd.IndexSlice

    if not ax:
        ax = plt.gca()
    fig = ax.figure

    if sim is not None:
        region = [r.magnitude for r in sim.world.region]
        radius = [p.radius.magnitude for p in sim.particles]
        framerate = sim.framerate.magnitude
        #runtime = sim.total_time.magnitude
        timestep = sim.timestep.magnitude

    if cmap is None:
        cmap = plt.cm.RdBu

    try:
        radius[0]
    except (AttributeError, TypeError):
        radius = [radius]

    has_type = True
    if ("type" not in trj.columns):
        if (len(radius)==1):
            tp = 0
            has_type = False
        else:
            raise(ValueError("trj should include a type column to use an array of radius values"))

    particles = trj.index.get_level_values('id').unique()
    n_of_particles = len(trj.index.get_level_values('id').unique())

    lammps_time = 1e6;

    #dt_data = np.round(1/(timestep*framerate)) # Data timestep in lammps_time

    frames = trj.index.get_level_values(time_index).unique().values

    time = np.arange(len(frames))/framerate

    if not end:
        end = time[-1]

    frame_id_min=np.argmin(np.abs(time-start))
    frame_id_max=np.argmin(np.abs(time-end))

    trj = trj.loc[idx[frames[frame_id_min:frame_id_max:step],:]]
    frames = trj.index.get_level_values(time_index).unique().values

    #dt_video = np.mean(np.diff(frames))*timestep*1000/speedup # video timestep in miliseconds
    dt_video = 1000/framerate/speedup*step # video timestep in miliseconds

    if not preserve_limits:

        ax.set_xlim(region[0],region[1])
        ax.set_ylim(region[2],region[3])
        ax.set(aspect='equal')
        ax.set_xlabel("$x [\mu{m}]$")
        ax.set_ylabel("$y [\mu{m}]$")

    patches = []
    for i,p in enumerate(particles):
        if has_type:
            tp = int(trj.loc[idx[frames[0],p],'type'])-1

        c = plt.Circle((0, 0), radius[tp])
        patches.append(c)

    p = clt.PatchCollection(patches, cmap=cmap)
    p.set_array(np.zeros(0))

    if clim is None:
        clim = [region[4]+radius,region[5]-radius]

    p.set_clim(clim)

    divider = make_axes_locatable(ax)
    cax = divider.append_axes("right", size="5%", pad=0)
    plt.colorbar(p,label=color_label,cax=cax)

    def init():
        ax.add_collection(p)
        return p,

    def animate(frame):

        particles_in = trj.loc[idx[frames[frame],:],:].index.get_level_values('id').unique()
        particles_not_in = [prt for prt in particles if prt not in particles_in]

        if verb:
            print("frame[%u] is "%frame,frames[frame])
        for (part_id,particle) in enumerate(particles_in):

            patches[part_id].center = (
                trj.loc[idx[frames[frame],particle],'x'],
                trj.loc[idx[frames[frame],particle],'y'])

        for (part_id,particle) in enumerate(particles_not_in):

            patches[part_id+len(particles_in)].visible = False
            patches[part_id+len(particles_in)].center = [2*region[0],2*region[2]]

        p.set_paths(patches)
        p.set_array(trj.loc[idx[frames[frame],:],color_field].values)
        ax.add_collection(p)
        return p,

    if verb:
        print("started animating")
        print(frames)
        print(dt_video)

    anim = anm.FuncAnimation(fig, animate, init_func=init,
                                   frames=len(frames), interval=dt_video, blit=True);
    plt.close(anim._fig)

    return anim

def draw_trj(trj,sim = None, region = None, radius = None, iframe=-1, ax=False, time_index = "frame", color_field = "z", color_label='$z [\mu{m}]$', clim = None, cmap = None):
    from mpl_toolkits.axes_grid1 import make_axes_locatable
    """
    displays a trajectory statically.
    If iframe is given, it displays particles in the frame specified by the index iframe.
    If it isn't given, then it displays the last frame

    """
    if not ax:
        ax = plt.gca()

    if sim is not None:
        region = [r.magnitude for r in sim.world.region]
        radius = [p.radius.magnitude for p in sim.particles]

    try:
        radius[0]
    except (AttributeError, TypeError):
        radius = [radius]

    has_type = True
    if ("type" not in trj.columns):
        if (len(radius)==1):
            tp = 0
            has_type = False
        else:
            raise(ValueError("trj should include a type column to use an array of radius values"))

    if clim is None:
        clim = [region[4]+min(radius),region[5]-min(radius)]
    if cmap is None:
        cmap = plt.cm.RdBu

    idx = pd.IndexSlice
    particles = trj.index.get_level_values('id').unique()
    n_of_particles = len(trj.index.get_level_values('id').unique())
    frames = trj.index.get_level_values(time_index).unique()

    ax.set_xlim(region[0],region[1])
    ax.set_ylim(region[2],region[3])
    ax.set(aspect='equal')
    ax.set_xlabel("$x [\mu{m}]$")
    ax.set_ylabel("$y [\mu{m}]$")

    patches = []

    for i,p in enumerate(particles):
        if has_type:
            tp = int(trj.loc[idx[frames[iframe],p],'type'])-1

        c = plt.Circle(
            (trj.loc[idx[frames[iframe],p],'x'],trj.loc[idx[frames[iframe],p],'y']), radius[tp])
        patches.append(c)

    p = clt.PatchCollection(patches, cmap=cmap)
    p.set_array(trj.loc[idx[frames[iframe],:],color_field].values)
    p.set_clim(clim)

    ax.add_collection(p)

    divider = make_axes_locatable(ax)
    cax = divider.append_axes("right", size="5%", pad=0)
    plt.colorbar(p,label=color_label,cax=cax)

    return ax

def display_animation_direct(sim=None,trj=None,**kargs):

    if trj is None:
        trj = sim.load(read_trj=True)
        print("reading"+args)

    anim = animate_trj(trj,sim,**kargs)
    return anim.to_html5_video()

def draw_exp_phase_diagram(ax=False):
    from scipy.misc import imread

    if not ax:
        fig, ax = plt.subplots(1,1,figsize=(10,8))

    img = imread('ExperimentPhaseDiagram.png')
    img[:,:,3] = img[:,:,3]*0.5
    extent = [0, 0.5, 2.75, 5.7]

    extent_size = [extent[1]-extent[0],extent[3]-extent[2]]
    img_size = np.shape(img)[0:2]
    extent_ratio = extent_size[0]/extent_size[1]
    img_ratio = img_size[0]/img_size[1]

    ax.imshow(img, zorder=0, extent=extent)
    ax.set(aspect=extent_ratio*img_ratio)

    ax.set_xlabel("Area Packing Fraction $\phi$")
    ax.set_ylabel("height")
    return ax

## Dimer Finding Functions
def neighbors_within(distance,points, boundary_cond, region):
    """ Calculates, through cKDTree, a list of those pairs of particles that have a distance of less than "distance".
    """

    import scipy.spatial as spp
    if any([bc=='p' for bc in boundary_cond]):

        # padding a region prevents particles to be consider dimers
        # when they are close to the boundary of a non periodic dimension
        pad_region = [0 if bc=='p' else distance*2 for bc in boundary_cond]

        region[0::2] = region[0::2]-pad_region
        region[1::2] = region[1::2]+pad_region

        tree = spp.cKDTree(
            np.mod(
                (points-region[0::2]), # Centers
                region[1::2]-region[0::2]), # mod wraps particles outside the region
            boxsize = region[1::2]-region[0::2]) # boxsize finds neighbors across the borders of a torus.
    else:
        tree = spp.cKDTree(points) # everything is easier for closed boundaries

    pair_list = list(tree.query_pairs(distance))
    # pair lists are really more useful as sets, which are not ordered. When comparing sets {a,b}=={b,a}.
    return  [set([i for i in pair]) for pair in pair_list]

def nearest_neighbors(points,boundary_cond, region):
    """ Calculates, through cKDTree, the nearest neighbors of each particle in a frame.
    """

    import scipy.spatial as spp
    if any([bc=='p' for bc in boundary_cond]):

        # padding a region prevents particles to be consider dimers
        # when they are close to the boundary of a non periodic dimension
        pad_region = [0 if bc=='p' else distance*2 for bc in boundary_cond]
        region[0::2] = region[0::2]-pad_region
        region[1::2] = region[1::2]+pad_region

        tree = spp.cKDTree(
            np.mod(
                (points-region[0::2]), # Centers
                region[1::2]-region[0::2]), # mod wraps particles outside the region
            boxsize = region[1::2]-region[0::2]) # boxsize finds neighbors across the borders of a torus.
    else:
        tree = spp.cKDTree(points) # everything is easier for closed boundaries

    pair_list = list(tree.query())
    # pair lists are really more useful as sets, which are not ordered. When comparing sets {a,b}=={b,a}.
    return  [set([i for i in pair]) for pair in pair_list]

def dimers(trj, sim=None, boundary_cond=None, region = None, radius = None, distance=False):
    """ This returns a database of dimers in frames"""

    if sim is not None:
        boundary_cond = sim.world.boundaries
        region = np.array([r.magnitude for r in sim.world.region])
        radius = sim.radius.magnitude

    if not distance:
        distance = 2*radius

    idx = pd.IndexSlice
    frames = trj.index.get_level_values('frame').unique()

    pairs_in_frame = []

    for i_frame,frame in enumerate(frames):
        points = trj.loc[idx[frame,:]].filter(("x","y","z")).values
        p_id = trj.loc[idx[frame,:]].index.get_level_values('id').values

        pair_list = neighbors_within(distance,points, boundary_cond, region)
        # neighbors_within returns the location of the pair members.
        # we need to convert that into the id of the pair members.
        pair_list = [{p_id[loc] for loc in pair} for pair in pair_list]

        if i_frame>0:
            # here I'll store the id's of dimers that I find.
            # If the dimmer exists in the previous frame I must give it the same id. Otherwise I give it a new id.
            # It's then straightforward to include this id in the DataFrame
            pairs_index = np.empty(len(pair_list))

            for i_pair,pair in enumerate(pair_list):
                # Now, for each pair in the new frame

                pairs_0_id = pairs_in_frame[i_frame-1].index.values

                # "where" is the location of the pair in the previous array.
                # If the pair is not in the previous array, it returns an empty, which is fine (see later)
                # However, if the pair is in the previous array more than once it should fail.
                where = [pair_id for pair_id, pair_0 in enumerate(pairs_in_frame[i_frame-1].values) if pair_0 == pair]

                if where:
                    pairs_index[i_pair] = pairs_0_id[where]
                else:
                    # if where is empty, I assign a new id to the pair. I then update the newest pair id.
                    pairs_index[i_pair] = new_pair_id
                    new_pair_id=new_pair_id+1
            pair_df = pd.DataFrame({'members':pair_list},index = pairs_index)
            pair_df.index.name = 'id'
            pairs_in_frame.append(pair_df)

        else:
            pair_df = pd.DataFrame({'members':pair_list})
            pair_df.index.name = 'id'
            pairs_in_frame.append(pair_df)
            new_pair_id = len(pair_list)

    return pd.concat(pairs_in_frame,keys = frames).sort_index(level='frame')

def dimers_findpositions(dim, trj, sim=None, boundary_cond=None, region = None, radius = None):
    """
    Finds the positions of both elements of each dimer and stores them in two new fields.
    It also calculates the centers and directions of the dimers.
    In periodic boundaries, this function needs to unwrap the direction vector of those
    dimers that have one particle in each side of the boundary.
    """

    if sim is not None:
        boundary_cond = sim.world.boundaries
        region = np.array([r.magnitude for r in sim.world.region])
        radius = sim.radius.magnitude

    p0 = np.zeros([len(dim),3])
    p1 = np.zeros([len(dim),3])

    members = np.array([list(m) for m in dim.members.values])
    frames = dim.index.get_level_values('frame')

    idx = pd.IndexSlice

    for i,m in enumerate(members):
        m0 = trj.loc[idx[frames[i]]].loc[idx[m[0]]]
        m1 = trj.loc[idx[frames[i]]].loc[idx[m[1]]]

        p = np.array([m0.filter(("x","y","z")),m1.filter(("x","y","z"))])
        p = p[p[:,2].argsort()]

        p0[i] = p[0]
        p1[i] = p[1]

    center, direction = unwrap_dimers(p0,p1,region,boundary_cond)

    dim['part0'] = list(p0)
    dim['part1'] = list(p1)
    dim['center']= list(center)
    dim['direction']=list(direction)

    return dim

def unwrap_dimers(p0,p1,region,boundary_cond = None, tol=False):
    """
    Unwraps the dimers that are found across the periodic boundaries periodic boundaries of the region
    The input p0 and p1 are arrays of Nx3, where N is the number of dimers. Each row of p0 is the position in 3D of the first particle (convention is that this is the lower particle)
    Each row of p1 is the position of the second particle.
    """

    size = np.array(region)[1::2] - \
            np.array(region)[0::2]

    if not tol:
        tol = size/2
    elif not hasattr(tol, "__len__"):
        tol = tol*np.ones(3)

    tol = np.array(
        [t if (b=='p') else np.Inf  for b,t in zip(boundary_cond,tol)])

    """
    We stack in 3D both position arrays. This allows us to calculate the direction as a diff.
    In this array then, each element represents a dimer. Each dimer has three arrays which represent the three dimensions, and each of these arrays contains 2 elements, one for each dimer-member.
    That is: the first dimension is the dimers. The second dimension is the three elements of the positions, and the third dimension are the two members of the dimer. The array is therefore Nx3x2
    """
    point_pairs = np.stack([p0,p1],2)
    """Calculate the diferences of each component of every dimer"""
    delta = np.diff(point_pairs,axis=2)

    """
    We unwrap only those dimers that have a dimension greater than a tolerance. The tolerance is, by default half the size of the region in the respective direction
    To unwrap a value, we add to it's second point, the size of the region in its direction multiplied by the sign of the direction.
    """
    needs_unwrap = abs(delta)>np.array(tol).reshape(1,3,1)
    unwrap_amount = np.sign(delta)*size.reshape(1,3,1)

    second_points = point_pairs[:,:,1:2].flatten()
    second_points[needs_unwrap.flatten()] -= unwrap_amount[needs_unwrap].flatten()

    point_pairs[:,:,1]=second_points.reshape(np.shape(point_pairs[:,:,1]))

    direction = np.diff(point_pairs,axis=2)[:,:,0]

    """We calculate like this the center, because the usual formula (p0+p1)/2 would yield something in the center of the region for vectors that were unwraped."""
    center = p0+direction/2

    return center, direction

def draw_dim(dim,sim,iframe=-1,ax=False):
    """
    displays a trajectory statically.
    If iframe is given, it displays particles in the frame specified by the index iframe.
    If it isn't given, then it displays the last frame
    """

    if not ax:
        fig, ax = plt.subplots(1,1,figsize=(7,7))

    idx = pd.IndexSlice

    frames = dim.index.get_level_values('frame').unique()
    dimers = dim.loc[idx[frames[iframe],:]].index.get_level_values('id').unique()
    n_of_dimers = len(dimers)

    region = [r.magnitude for r in sim.world.region]

    ax.set_xlim(region[0],region[1])
    ax.set_ylim(region[2],region[3])
    ax.set(aspect='equal')
    ax.set_xlabel("$x [\mu{m}]$")
    ax.set_ylabel("$y [\mu{m}]$")

    patches = []
    for i,d in enumerate(dimers):
        center = dim.loc[idx[frames[iframe],d],'center']
        direction = dim.loc[idx[frames[iframe],d],'direction']
        p0 = center-direction/2
        c = plt.Arrow(p0[0],p0[1],direction[0],direction[1])
        patches.append(c)

    p = clt.PatchCollection(patches)
    ax.add_collection(p)

    return ax

def animate_dim(dim ,sim, ax=False, verb=False, start=0, end=False, step = 1, speedup = 1):
    """
    This function animates the dimers resulting from a confined dimer simulation.
    It displays each dimer as a vector that runs from the lower to the higher particles.
    The simulation is required as argument to obtain parameters like the region size and the
    particles radius.
    Optional parameters are:
    * ax: an axis object to use for creating the plot.
    * start: start time of the simulation if not the whole time is required. The default is 0.
    * end: end time of the simulation. The default is the total simulation time.
    * step = 1. The framerate, so to speak.
    * verb = False. If verb = True, the routine prints indicators that is running.
    * speedup allows us to do faster videos. Default is 1, which means normal ratio.
    """
    from mpl_toolkits.axes_grid1 import make_axes_locatable

    idx = pd.IndexSlice

    if not ax:
        fig, ax = plt.subplots(1,1,figsize=(7,7))

    dimers = dim.index.get_level_values('id').unique()
    n_of_dimers = len(dim.index.get_level_values('id').unique())

    region = [r.magnitude for r in sim.world.region]
    radius = sim.particles.radius.magnitude

    framerate = sim.framerate.magnitude
    runtime = sim.total_time.magnitude
    timestep = sim.timestep.magnitude

    lammps_time = 1e6;

    #dt_data = np.round(1/(timestep*framerate)) # Data timestep in lammps_time

    frames = dim.index.get_level_values('frame').unique().values

    if not frames.size:
        raise ValueError("There are no dimers in the input array")

    if not end:
        end = frames[-1]*timestep

    frame_min=start/timestep
    frame_max=end/timestep

    frame_id_min = np.where(frames>=frame_min)[0][0]
    frame_id_max = np.where(frames<frame_max)[0][-1]

    dim = dim.loc[idx[frames[frame_id_min:frame_id_max:step],:]]

    dimers = dim.index.get_level_values('id').unique()
    n_of_dimers = len(dim.index.get_level_values('id').unique())

    frames = dim.index.get_level_values('frame').unique().values
    dt_video = np.mean(np.diff(frames))*sim.timestep.magnitude*1000/speedup # video timestep in miliseconds

    ax.set_xlim(region[0],region[1])
    ax.set_ylim(region[2],region[3])
    ax.set(aspect='equal')
    ax.set_xlabel("$x [\mu{m}]$")
    ax.set_ylabel("$y [\mu{m}]$")

    patches = []
    for i,p in enumerate(dimers):
        a = plt.Arrow(0, 0, 0, 0)
        patches.append(a)

    p = clt.PatchCollection(patches)

    def init():
        ax.add_collection(p)
        return p,

    def animate(frame):
        if verb:
            print("frame[%u] is "%frame,frames[frame])

        patches = []

        dimers = dim.loc[idx[frames[frame],:]].index.get_level_values('id').unique()
        n_of_dimers = len(dimers)

        for (dim_id,dimer) in enumerate(dimers):

            center = dim.loc[idx[frames[frame],dimer],'center']
            direction = dim.loc[idx[frames[frame],dimer],'direction']
            p0 = center-direction/2
            a = plt.Arrow(p0[0],p0[1],direction[0],direction[1])
            patches.append(a)

        p.set_paths(patches)

        ax.add_collection(p)
        return p,

    if verb:
        print("started animating")
        print(frames)

    anim = anm.FuncAnimation(fig, animate, init_func=init,
                                   frames=len(frames), interval=dt_video, blit=True);
    plt.close(anim._fig)

    return anim

def dimers_array(dim):
    dim["member_a"] = np.array([list(m) for m in dim.members])[:,0]
    dim["member_b"] = np.array([list(m) for m in dim.members])[:,1]

    dim["x"] = np.array([m for m in dim.center])[:,0]
    dim["y"] = np.array([m for m in dim.center])[:,1]
    dim["z"] = np.array([m for m in dim.center])[:,2]

    dim["dx"] = np.array([m for m in dim.direction])[:,0]
    dim["dy"] = np.array([m for m in dim.direction])[:,1]
    dim["dz"] = np.array([m for m in dim.direction])[:,2]
    return dim.filter(["member_a","member_b","x","y","z","dx","dy","dz"])

def save_dimer_array(dim,filename,directory):
    """Converts dimers to a saveable array"""
    import os

    dim_store = dimers_to_array(dim)

    dim_store.to_csv(os.path.join(directory,filename)+"_dimers.dat",sep = "\t")

    return dim_store

### Order Paramters

def nematic_order(dim,director=False):
    """
    Calculates the nematic order parameter $S = <1/2*(3cos(theta)^2-1)>$, where $theta$ is the angle between the director vector and the dimers. If the director is not given, the function returns the order parameter and the director with the largest order with a resolution of one degree.
    """
    if director:
        direction = np.array(list(dim.direction.values))
        theta = np.arctan2(direction[:,1],direction[:,0])
        theta_0 = np.arctan2(director[1],director[0])
        return np.mean((3*np.cos(theta-theta_0)**2-1)/2)
    else:
        theta_0 = np.linspace(0,2*np.pi,360)

        S = [nematic_order(dim,[np.cos(th),np.sin(th)]) for th in theta_0]

        max_order = np.argmax(S)
        director = [np.cos(theta_0[max_order]),np.sin(theta_0[max_order])]
        return S[max_order], director

def load_trj(name,slc = None, output = ["x","y","z"]):
    from . import simulation as sim

    if slc is None:
        slc = slice(0,-1,1)

    lz_trj = sim.trj_lazyread(name, output)
    trj = lz_trj[slc].filter(["type","x","y","z"])

    bounds = lz_trj.get_bounds()

    return trj, bounds

def unwrap_trj(trj,bounds):

    trj2 = trj.copy(deep=True)

    def unwrap(p):
        p.iloc[:] = np.unwrap(p,axis=0)
        return p

    for c in trj.columns:
        trj2[c] = (trj2[c] - bounds[c+"_min"])/(bounds[c+"_max"] - bounds[c+"_min"])

    trj2 = (trj2*2*np.pi).groupby("id").apply(unwrap)/(2*np.pi)

    for c in trj.columns:
        trj2[c] = trj2[c]*(bounds[c+"_max"] - bounds[c+"_min"]) + bounds[c+"_min"]

    return trj2

def calculate_msd(series,lag_frame,col_name=None,framerate=1,particle_id = None):

    if col_name is None:
        col_name = series.columns.values

    def msd_point(lag):
        return np.nanmean((series[col_name].values[lag:]-series[col_name].values[:-lag])**2,axis=0)

    columns = ["msd_"+name for name in col_name]

    result =  pd.DataFrame(
        data=[msd_point(lag) for lag in lag_frame],
        index=lag_frame/framerate,columns=columns)

    result.index.name = "dt"

    if particle_id is not None:
        result["id"] = particle_id
        result["dt"] = result.index.values
        result = result.set_index(["dt","id"])

    return result


from collections.abc import Iterable
from mailcap import findmatch
from obaic import obaic
from obaic.model import ObaicModel
from obaic.source import MLSource, ObaicFeature, ObaicOutput

import asyncio

async def main():

    ml_src_parameters: dict[str, str]
    ml_src_credentials: dict[str, str]

    # define an ML source where predictions will be done
    ml_src: MLSource = MLSource(ml_src_parameters=ml_src_parameters,
                                ml_src_credentials=ml_src_credentials)

    # 1. define a model query and fetch a list of models from the ML source
    model_query: str
    ml_model_iter: Iterable[ObaicModel] = ml_src.list_models_async(model_query=model_query)

    # 3. pick a model from the list mlModels using some criteria
    for ml_model in ml_model_iter:
        if ml_model : # check for some criteria
            select_ml_model = ml_model
            break

    # 4. define a prediction request and run a prediction using the model selected above
    data_src_parameters: dict[str,str]
    data_src_credentials: dict[str,str]
    data_query: str
    predict_task = asyncio.create_task(
                        ml_src.predict_with_data_ref_async(model=select_ml_model,
                                                           data_src_parameters=data_src_parameters,
                                                           data_src_credentials=data_src_credentials,
                                                           data_query=data_query)
                    )
    await predict_task


from flask import Blueprint, request, g
from werkzeug.exceptions import NotFound, MethodNotAllowed, BadRequest

from access_guards import only_worker
from utils import datetime_from_json

from models.db import db
from models.trainings import Training
from models.courses import Course
from models.users import User, Role
from models.courses_utils import Tag, Place

bp = Blueprint('trainings', __name__, url_prefix='/trainings')

@bp.route('/<int:id>')
def get_training(id):
    training = Training.query.filter_by(id=id).first()
    if training is None:
        raise NotFound('Training with that id does not exist!')
    return training.to_dict()

@bp.route('/')
def get_trainings():
    json_body = request.get_json()
    trainings_pages = Training.query
    page = 1
    per_page = 20

    if json_body is not None:
        tags = json_body.get('tags')
        if tags:
            trainings_pages = trainings_pages.join(Training.course).join(
                Course.tags).filter(Tag.id.in_(tags))

        name = json_body.get('name')
        if name:
            trainings_pages = trainings_pages.join(
                Training.course).filter(Course.name.ilike('%{}%'.format(name)))

        cities = json_body.get('cities')
        if cities:
            trainings_pages = trainings_pages.join(
                Training.place).filter(Place.city_id.in_(cities))

        page = json_body.get('page', 1)
        per_page = json_body.get('per_page', 20)

    trainings_pages = trainings_pages.order_by(Training.id).paginate(
        page, per_page,
        error_out=False, max_per_page=9999)

    return {
        'trainings': [
            training.to_dict()
            for training in trainings_pages.items
        ],
        'items': len(trainings_pages.items),
        'pages': trainings_pages.pages,
        'page': trainings_pages.page,
        'page_size': trainings_pages.per_page,
        'is_last': not trainings_pages.has_next
    }

def get_instructor(instructor_id):
    instructor = User.query.filter_by(id=instructor_id).join(
        User.role).filter(Role.name == "worker").first()
    if instructor is None:
        raise BadRequest(
            "User with that id does not exist or is not a worker!")
    return instructor

@bp.route('/', methods=['POST'])
@only_worker
def create_training():
    request_body = request.form
    attrs = [
        request_body.get('price'), request_body.get('places_amount'),
        request_body.get('begin_date'), request_body.get('end_date'),
        request_body.get('enrolment_begin_date'), request_body.get(
            'enrolment_end_date'),
        request_body.get('course_id'), request_body.get('place_id'),
        request_body.get('instructor_id')
    ]
    if None in attrs:
        raise BadRequest("Give me all necessary data to create training!")
    course = Course.query.filter_by(id=attrs[6]).first()
    place = Place.query.filter_by(id=attrs[7]).first()
    instructor = get_instructor(attrs[8])
    if None in [course, place]:
        raise BadRequest("course or place with given id does not exist!")
    training = Training(
        attrs[0], attrs[1], attrs[1],
        datetime_from_json(attrs[2]), datetime_from_json(attrs[3]),
        datetime_from_json(attrs[4]), datetime_from_json(attrs[5]),
        course, place, g.user, instructor
    )
    db.session.add(training)
    db.session.commit()
    return training.to_dict()

@bp.route('/<int:id>', methods=['PATCH'])
@only_worker
def edit_training(id):
    request_body = request.form
    training = Training.query.filter_by(id=id).first()
    if training is None:
        raise NotFound("Training with that id does not exist")

    if len(training.participants) > 0:
        raise MethodNotAllowed(
            'Course Cannot have any participants to edit it!')

    price = request_body.get('price')
    if price is not None:
        training.price = float(price)

    places_amount = request_body.get('places_amount')
    if places_amount is not None:
        training.places_amount = int(places_amount)
        training.free_places_amount = int(places_amount)

    begin_date = request_body.get('begin_date')
    if begin_date is not None:
        training.begin_date = datetime_from_json(begin_date)

    end_date = request_body.get('end_date')
    if end_date is not None:
        training.end_date = datetime_from_json(end_date)

    enrolment_begin_date = request_body.get('enrolment_begin_date')
    if enrolment_begin_date is not None:
        training.enrolment_begin_date = datetime_from_json(
            enrolment_begin_date)

    enrolment_end_date = request_body.get('enrolment_end_date')
    if enrolment_end_date is not None:
        training.enrolment_end_date = datetime_from_json(enrolment_end_date)

    place_id = request_body.get('place_id')
    if place_id is not None:
        place = Place.query.filter_by(id=place_id).first()
        if place is None:
            raise BadRequest("Place with that id does not exist!")
        training.place = place

    instructor_id = request_body.get('instructor_id')
    if instructor_id is not None:
        instructor = get_instructor(instructor_id)
        training.instructor = instructor

    db.session.commit()
    return training.to_dict()


from dependency_injector import containers, providers

from app.repository import CoinApiRateConversionRepository
from app.services import RateConversionBaseService

from app.core.config import settings

class Container(containers.DeclarativeContainer):
    wiring_config = containers.WiringConfiguration(
        modules=[
            "app.api.v1.endpoints.conversion",
        ]
    )

    rate_conversion_repository = providers.Factory(
        CoinApiRateConversionRepository, api_key=settings.COIN_API_TOKEN
    )

    rate_conversion_service = providers.Factory(
        RateConversionBaseService, rate_repository=rate_conversion_repository
    )


from keras.layers import Input
from keras.layers.core import Dense, Dropout, Permute
from keras.layers.embeddings import Embedding
from keras.layers.merge import add, concatenate, dot
from keras.layers.recurrent import LSTM
from keras.models import Model
from keras.callbacks import ModelCheckpoint
from keras import backend as K
from pickle import load

#we load the previously saved data for the input
input_train = load(open('input_train.pickle', 'rb'))
input_test = load(open('input_test.pickle', 'rb'))

#we split the data we just loaded into stories and questions
XsTrain, XqTrain = input_train
XsTest, XqTest = input_test

#we load the previously saved data for the output
output_train = load(open('output_train.pickle', 'rb'))
output_test = load(open('output_test.pickle', 'rb'))

#we load the previously saved data for the vocabulary size
vocab_size = load(open('vocab_size.pickle', 'rb'))

EMBEDDINGS_SIZE=64
LATENT_SIZE=32

#inputs
story_input = Input(shape=(len(XsTrain[0]),))
question_input = Input(shape=(len(XqTrain[0]),))
print('input done!')

#story encoder memory
story_encoder = Embedding(input_dim=vocab_size, output_dim=EMBEDDINGS_SIZE, input_length=len(XsTrain[0]))(story_input)
story_encoder = Dropout(0.3)(story_encoder)
print('story encoder done!')

#question encoder memory
question_encoder = Embedding(input_dim=vocab_size, output_dim=EMBEDDINGS_SIZE, input_length=len(XqTrain[0]))(question_input)
question_encoder = Dropout(0.3)(question_encoder)
print('question encoder done!')

#match between story and question
match = dot([story_encoder, question_encoder], axes=[2,2])
print('match done!')

#encode story into vector space of question
story_encoder_c = Embedding(input_dim=vocab_size, output_dim=len(XqTrain[0]), input_length=len(XsTrain[0]))(story_input)
story_encoder_c = Dropout(0.3)(story_encoder_c)
print('story encoder c done!')

#combine match and story vectors
response = add([match, story_encoder_c])
response = Permute((2, 1))(response)
print('response done!')

#combine response and question vectors
answer = concatenate([response, question_encoder], axis=-1)
answer = LSTM(LATENT_SIZE)(answer)
answer = Dropout(0.3)(answer)

answer = Dense(round(LATENT_SIZE * 0.5), activation='relu')(answer)
output = Dense(1, activation='sigmoid')(answer)

print('output done!')

model = Model(inputs=[story_input, question_input], outputs=output)
model.compile(optimizer='sgd', loss='mean_squared_error', metrics=["accuracy"])
print('model compiled!')

# summarize model
model.summary()

BATCH_SIZE=5
NUM_EPOCHS=300

#we create a function to tell the model when and how to save itself
checkpoint = ModelCheckpoint('model.h5', monitor='val_loss', verbose=1, save_best_only=True, mode='min')

history = model.fit([XsTrain, XqTrain], [output_train], batch_size=BATCH_SIZE, epochs=NUM_EPOCHS, validation_data=([XsTest, XqTest], [output_test]), callbacks=[checkpoint], verbose=2)
print('model fitted!')


import os
from tkinter import *
import tkinter.messagebox as tmsg
from tkinter.filedialog import askopenfilename ,asksaveasfile
root = Tk()
root.title("Untitled - Notepad")
root.wm_iconbitmap("Progrmming.ico")
root.geometry("644x688")
# Add TextAread
TextArea = Text(root, font="lucida 13")
TextArea.pack(fill=BOTH, expand=True)
file = None
# Adding Menubar
MenuBar = Menu(root)
# Adding Scrollbar
Scroll = Scrollbar(TextArea)
Scroll.pack(fill=Y, side = RIGHT)
Scroll.config(command=TextArea.yview)
TextArea.config(yscrollcommand=Scroll.set)

#                   FileMenu Functions
def Newfile():
    global file
    root.title("Untitled - Notepad")
    file = None
    TextArea.delete(1.0, END)
def OpenFile():
    global file
    file = askopenfilename(defaultextension=".txt",filetypes = [("All Files","*.*"),("Text Documents")])
    if file == "":
        file = None
    else:
        root.title(os.path.basename(file) + " - Notepad")
        TextArea.delete(1.0, END)
        f = open(file, "r")
        TextArea.insert(1.0, f.read())
        f.close()

def Save():
    global file
    if file == None:
        file = asksaveasfile(initialfile = 'Untitled.txt', defaultextension = ".txt",filetypes = [("All Files","*.*"),("Text Documents")])
        if file == "":
            file = None
        else:
            #Save as a New File
            f = open(file, "w")
            f.write(TextArea.get(1.0, END))
            f.close()
            root.title(os.path.basename(file) + " - Notepad")
    else:
         #Save the New File
            f = open(file, "w")
            f.write(TextArea.get(1.0, END))
            f.close()
def QuitApp():
    root.destroy()

#                   EditMenu Functions
def Copy():
    TextArea.event_generate(("<<Copy>>"))
def Cut():
    TextArea.event_generate(("<<Cut>>"))
def Paste():
    TextArea.event_generate(("<<Paste>>"))

#                       HelpMenu Functions
def AboutPad():
    message = tmsg.showinfo("About","This is a text editor Made By Adityaraj")

#                  FileMenu Start
# To Open New File and To Open already existing file and To Save a Current File
FileMenu = Menu(MenuBar, tearoff=0)
FileMenu.add_command(label="New", command=Newfile)
FileMenu.add_command(label="Open", command=OpenFile)
FileMenu.add_command(label="Save", command=Save)
FileMenu.add_separator()
FileMenu.add_command(label="Exit", command=QuitApp)
MenuBar.add_cascade(label="File", menu=FileMenu)

#                  Edit Menu Starts
# To give a feature of cut,copy and paste
EditMenu = Menu(MenuBar,tearoff=0)
EditMenu.add_command(label="Copy", command=Copy)
EditMenu.add_command(label="Cut", command=Cut)
EditMenu.add_command(label="Paste", command=Paste)
MenuBar.add_cascade(label="Edit", menu= EditMenu)

#                  Help Menu Starts
HelpMenu = Menu(MenuBar, tearoff=0)
HelpMenu.add_command(label="About Notepad", command="AboutPad")
MenuBar.add_cascade(label="Help", menu= HelpMenu)

root.config(menu=MenuBar)
root.mainloop()


#!/usr/bin/env python3

# Sparse array class (1D)
# Only store non-zero values, use dictionary with key=index, value=value
# Want to provide functionality to make it seem like a real array
#  Length method should return total length (with zeros)
#  Should support getting and setting
#  Should support deletion by arbitrary index
#  Should support append
#  Should return error if accessed beyond end
#  Try and support slicing (return indices between endpoints, filling in zeros as needed)

class SparseArray(object):
    def __init__(self, input_array):
        # Store length (this will change if values are appended or deleted)
        self.length = len(input_array)
        # Initialize dict and fill with values
        self.value_dict = {}
        for index, value in enumerate(input_array):
            self.store_value(index, value)

    # Create a separate method for assignment in case storage method changes
    def store_value(self, index, value):
        if value: # Only store non-zero items
            self.value_dict[index] = value

    def __len__(self):
        return self.length

    def append(self, value):
        if value: # If non-zero, add value at next position
            self.value_dict[self.length] = value
            self.length += 1 # Increase 'virtual' length

    def __contains__(self, item):
        return item in self.value_dict.values()

    def __getitem__(self, index):
        if isinstance(index,slice): # slice
            array_slice = []
            # Set step to 1 if not defined
            if index.step == 0:
                raise ValueError('slice step cannot be zero')
            step = index.step if index.step else 1
            # Populate slice from array
            # Continue as long as current index is valid
            if step > 0:
                start, stop = index.start, index.stop
                current = start
                while current < self.length and current < stop:
                    value = self.value_dict.get(current,0)
                    array_slice.append(value)
                    current += step
            elif step < 0:
                start, stop = index.stop, index.start
                current = stop
                while current >= 0 and current > start:
                    value = self.value_dict.get(current,0)
                    array_slice.append(value)
                    current += step
            return array_slice
        elif isinstance(index,int): # single index
            if index >= self.length:
                raise IndexError('list index out of range')
            else:
                # Return zero if not in dictionary
                return self.value_dict.get(index,0)

    def __setitem__(self, index, value):
        if index >= self.length:
            raise IndexError('list assignment index out of range')
        else:
            self.store_value(index, value)

    def __delitem__(self, index):
        if index >= self.length:
            raise IndexError('list assignment index out of range')
        else:
            if index in self.value_dict:
                del self.value_dict[index]


# 2000*2000*28 = 112메가, 메모리 초과 주의
# 최소신장트리, 방문한곳을 기록하면 메모리 초과
# 크루스칼 알고리즘 사용, 프림알고리즘 사용하면 메모리 초과

import sys
import heapq

N,C = map(int, sys.stdin.readline().split())
list_field = []
# 모든 노드의 윗 노드는 자기자신을 가리키고 있다. 글로벌 변수
parents = [i for i in range(N)]

for _ in range(N):
    x, y = map(int, sys.stdin.readline().split())
    list_field.append([x,y])

# 모든 노드 사이 비용을 저장할 최소힙
graph = []
# 모든 노드 사이의 비용을 최소힙으로 담아 저장한다.
for i in range(len(list_field)):
    for j in range(i+1, len(list_field)):
        x1, y1 = list_field[i]
        x2, y2 = list_field[j]
        cost= (x2-x1)**2 + (y2-y1)**2
        if cost >= C:
            heapq.heappush(graph, [cost,i,j])

# 유니온과 파인드를 안쓰면 메모리 초과가 남 파이썬이 int 메모리로 28바이트 사용
def find(node):
    # 자신이 최초 노드이면 자신을 반환
    if parents[node] == node:
        return node
    else:
        # 그렇지 않으면 계속해서 위로 올라가서 최초를 반환함
        parents[node] = find(parents[node])
        return parents[node]

def union(n1, n2):
    r1, r2 = find(n1), find(n2)
    # 두 노드의 시작이 서로 같다면, 이미 연결된 노드이므로 False 반환
    if r1==r2:
        return False
    # 그렇지 않다면
    else:
        # 한쪽 노드의 루트 노드를 다른 한쪽의 루트 노드로 정한다.(while 문 안쪽과 순서만 맞추면 된다.)
        parents[r2]=r1
        return True
total_cost = 0
edges= 0

while graph:

    cost, i, j = heapq.heappop(graph)
    # 노드 i 와 j 가 연결 가능하다면(이미 연결되어있지 않다면)
    if union(i,j) :
        # 연결하고, 노드 j의 부모노드를 i로한다.
        parents[j] == i
        # 비용 합산
        total_cost += cost
        # 그리고 연결된 그래프 수에 1추가
        edges += 1
        # 모두 연결 하였으면 탈출
        if edges == N-1:
            break
#모두 연결 하였으면 비용 출력
if edges == N-1:
    print(total_cost)
# 그렇지 못했으면 -1 출력
else:
    print(-1)


from aiohttp import web

from Bubot.Helpers.Helper import Helper

class ReportHandler(web.View):
    def __init__(self, request):
        web.View.__init__(self, request)
        self.obj_type = self.request.match_info.get('objType')
        self.obj_name = self.request.match_info.get('objName')
        self.report_name = self.request.match_info.get('reportName')
        self.report_section = self.request.match_info.get('reportSection')
        try:
            self.handler = Helper.get_obj_class(f'jay.{self.obj_type}.{self.obj_name}', 'reports', self.report_name)()
        except Exception as err:
            raise err

    async def get(self):
        try:
            handler = getattr(self.handler, self.report_section)
            return await handler(self)
        except Exception as err:
            return web.HTTPInternalServerError(text=str(err))