#Py3D.py
#Contains all code to render and run 3D games

#Imports required modules
import time,sys,math
from operator import itemgetter

#Import pygame
import pygame
from pygame.locals import *

#Variables

black=(0,0,0)
white=(255,255,255)
blue=(0,0,255)
red=(255,0,0)
green=(0,255,0)
yellow=(255,255,0)
cyan=(0,255,255)
magenta=(255,0,255)
light_blue=(100,170,255)
pink=(255,150,255)
purple=(180,65,255)
grey=(150,150,150)
dark_grey=(80,80,80)
light_grey=(180,180,180)
dark_blue=(0,10,100)
dark_green=(10,110,10)
lime_green=(200,250,40)
orange=(250,130,0)

#Classes

#Main game class
class Game(object):
    def __init__(self,fov=75,ticks_per_second=40,max_fps=120,use_wait=True,
                 update_callback=None,frame_callback=None,time_source=None,max_frame_skip=5,
                 min_brightness=100,min_colour=white, debug=False, caption="Py3D Game", gui=None,
                 tick_handler=None, render_distance=500, light_distance=0):

        #Starts pygame
        pygame.init()

        #Screen Related
        self.screen=pygame.display.set_mode((0,0),pygame.FULLSCREEN)
        self.width,self.height=self.screen.get_size()

        #Settings
        self.ticks_per_second = ticks_per_second
        self.max_fps = max_fps
        self.max_frame_skip = max_frame_skip
        self.use_wait = use_wait
        self.dilation = 1.0
        self.fov=fov
        pygame.display.set_caption(caption)
        self.debug = debug
        self.auto_move = True

        #Rendering Settings
        self.displayNodes = 0
        self.displayEdges = 0
        self.displayFaces = 1
        self.displayLights = 0
        self.displayNorms = 0
        self.displayRays = 0
        self.background = black
        self.node_colour = green
        self.edge_colour = blue
        self.nodeRadius = 5
        self.min_brightness=min_brightness
        self.min_colour=min_colour
        self.render_distance=render_distance
        self.light_distance=light_distance

        #Clock Related
        self._wait = time.sleep
        if time_source is not None:
            self._get_ticks = time_source
        elif sys.platform in ('win32','cygwin'):
            self._get_ticks = time.clock
        else:
            self._get_ticks = time.time

        self.time = self._get_ticks()
        self._elapsed = 0.0
        self._frames_skipped = 0
        self._last_update = self.time
        self._last_frame = self.time
        self._update_ready = True
        self._frame_ready = True
        self._tps = 0
        self.tps = 0
        self.fps = 0
        self.ups = 0
        self.frame_count = 0
        self.update_count = 0

        #Other
        self._done = False
        self._skip_console = False
        self.objects = {}
        self.tick_handler = tick_handler
        self.gui = gui
        self.instance = Instance(0,0,0,0,0,0)
        self.lights = []
        self.events= []

    #Properties
    @property
    def fov(self):
        return self._fov
    @fov.setter
    def fov(self, val):
        if val > 0:
            self._fov = val
        else:
            self._fov = 75
        self._scr_dist=math.tan(math.radians(180-val/2+90))*self.width/2

    @property
    def ticks_per_second(self):
        return self._ticks_per_second
    @ticks_per_second.setter
    def ticks_per_second(self, val):
        if val > 0:
            self._ticks_per_second = val
        else:
            self._ticks_per_second = 25
        self._tick_step = 1.0 / self._ticks_per_second

    @property
    def max_fps(self):
        return self._max_fps
    @max_fps.setter
    def max_fps(self, val):
        if val > 0:
            self._max_fps = val
            self._frame_step = 1.0 / val
        else:
            self._max_fps = 0
            self._frame_step = 0

    @property
    def use_wait(self):
        return self._use_wait
    @use_wait.setter
    def use_wait(self, enabled):
        self._use_wait = enabled
        self._tps = float(self.max_fps)

    @property
    def max_frame_skip(self):
        return self._max_frame_skip
    @max_frame_skip.setter
    def max_frame_skip(self, n):
        if n > 0:
            self._max_frame_skip = n
        else:
            self._max_frame_skip = 0

    #Main loop
    #Updates objects, controls clock, renders game and registers iconify and close of window
    def loop(self):
        while not self._done:
            TIME = self._get_ticks()
            self._ticks = (TIME - self.time) / self.dilation
            self._elapsed += self._ticks
            self.time = TIME

            if self._elapsed >= 1.0:
                self._elapsed %= 1.0
                self.tps = self._tps
                self._tps = 0
                self.fps = self.frame_count
                self.ups = self.update_count
                self.frame_count = self.update_count = 0

            self._tps += 1
            self._update_ready = self._frame_ready = False

            if TIME >= self._last_update+self._tick_step*self.dilation:
                self._update_ready = True

            if self.max_fps == 0:
                self._frame_ready = True
            elif self._use_wait and self._max_fps > 0:
                wait_sec = self._last_frame + self._frame_step - self._get_ticks()
                if wait_sec > 0:
                    self._wait(wait_sec)
                self._frame_ready = True
            elif not self._use_wait and self._max_fps > 0:
                if self._last_frame + self._frame_step < self._get_ticks():
                    self._frame_ready = True

            if self._update_ready:
                self.update_count += 1
                self._frames_skipped += 1
                self._last_update += self._tick_step
                drift = self._tick_step / 5.0
                if not (TIME-drift < self._last_update < TIME+drift):
                    self._last_update = TIME
                self.tick_handler.pre_update(self)
                for i in self.objects:
                    j=self.objects.get(i)
                    j.update()
                    if self.auto_move:
                        j.end_update()
                for i in self.lights:
                    i.update()
                self.gui.update(self)
                self.tick_handler.post_update(self)
                self.events=pygame.event.get()
                for event in self.events:
                    if self.debug:
                        print(event)
                    if event.type == pygame.QUIT:
                        self._done = True
                        break;
                    if event.type == pygame.KEYDOWN:
                        if event.key == K_ESCAPE and event.mod&KMOD_SHIFT:
                            self._done = True
                        elif event.key == K_ESCAPE:
                            pygame.display.iconify()
                        elif event.key == K_LALT:
                            pygame.display.iconify()
                            self.console()

            if self._frame_ready:
                self.frame_count += 1
                self._frames_skipped = 0
                if self._frame_step:
                    self._last_frame += self._frame_step
                    drift = self._frame_step * self._max_frame_skip
                    if not (TIME-drift < self._last_frame < TIME+drift):
                        self._last_frame = TIME
                #-----Start of render function-----#
                screen=self.screen
                screen.fill(self.background)
                d=self._scr_dist
                j=self.instance

                dist_list=[]
                for key in self.objects:
                    wireframe=self.objects.get(key)
                    dist=getDistance(wireframe.centre(),(j.x,j.y,j.z))
                    dist_list.append([key,dist])
                for tempobj in sorted(dist_list,key=itemgetter(1),reverse=True):
                    wireframe=self.objects.get(key)
                    for i in wireframe.sub_objects:
                        if self.displayEdges:
                            for edge in i.edges:
                                try:
                                    a_xx=edge.start.x-j.x
                                    a_yy=edge.start.y-j.y
                                    a_zz=edge.start.z-j.z
                                    if a_zz<0:
                                            continue;
                                    a_x=a_xx/a_zz*d+self.width/2
                                    a_y=a_yy/a_zz*d+self.height/2

                                    b_xx=edge.stop.x-j.x
                                    b_yy=edge.stop.y-j.y
                                    b_zz=edge.stop.z-j.z
                                    if b_zz<0:
                                            continue;
                                    b_x=b_xx/b_zz*d+self.width/2
                                    b_y=b_yy/b_zz*d+self.height/2
                                except ZeroDivisionError:
                                    continue;

                                pygame.draw.aaline(screen, self.edge_colour, (int(a_x),int(a_y)), (int(b_x),int(b_y)), 1)

                        if self.displayNodes:
                            for node in i.nodes:
                                try:
                                    xx=node.x-j.x
                                    yy=node.y-j.y
                                    zz=node.z-j.z
                                    if zz<0:
                                            continue;
                                    x=xx/zz*d+self.width/2
                                    y=yy/zz*d+self.height/2
                                except ZeroDivisionError:
                                    continue;

                                pygame.draw.circle(screen, self.node_colour, (int(x),int(y)), self.nodeRadius, 0)

                        if self.displayFaces:
                            avg_z=[]
                            k=0
                            for face in i.faces:
                                dist=0
                                for node in face.corners:
                                    dist+=math.hypot(math.hypot(max(node.x,j.x)-min(node.x,j.x),max(node.z,j.z)-min(node.z,j.z)),max(node.y,j.y)-min(node.y,j.y))/face.num_corners
                                avg_z.append([k,dist])
                                k+=1
                            for temp in sorted(avg_z,key=itemgetter(1),reverse=True):
                                if temp[1]>self.render_distance:
                                    continue;
                                face=i.faces[temp[0]]
                                for tri in face.triangles:
                                    meanX = sum([node.x for node in tri.corners]) / 3
                                    meanY = sum([node.y for node in tri.corners]) / 3
                                    meanZ = sum([node.z for node in tri.corners]) / 3
                                    centre=[meanX,meanY,meanZ]
                                    cornerlist=[]
                                    for node in tri.corners:
                                        try:
                                            xx=node.x-j.x
                                            yy=node.y-j.y
                                            zz=node.z-j.z
                                            if zz<0:
                                                continue;
                                            x=xx/zz*d+self.width/2
                                            y=yy/zz*d+self.height/2
                                            cornerlist.append((x,y))
                                        except ZeroDivisionError:
                                            zz=0
                                            continue;
                                    if not len(cornerlist)==len(tri.corners):
                                        continue;
                                    light=[0,0,0]
                                    for lightobj in self.lights:
                                        norm=[tri.norm[0]+centre[0],tri.norm[1]+centre[1],tri.norm[2]+centre[2]]
                                        ldist=getDistance(centre,(lightobj.x,lightobj.y,lightobj.z))
                                        nldist=getDistance(norm,(lightobj.x,lightobj.y,lightobj.z))
                                        angle=math.degrees(math.acos(clamp((tri.ndist**2+ldist**2-nldist**2)/(2*tri.ndist*ldist),-1,1)))
                                        if angle>=90:
                                            continue;
                                        #Specular shading
                                        lvector=[lightobj.x-centre[0],lightobj.y-centre[1],lightobj.z-centre[2]]
                                        dist=lvector[0]*tri.norm[0]+lvector[1]*tri.norm[1]+lvector[2]*tri.norm[2]
                                        mlight=[lightobj.x-2*dist*tri.norm[0],lightobj.y-2*dist*tri.norm[1],lightobj.z-2*dist*tri.norm[2]]
                                        reflected_light=[centre[0]+(centre[0]-mlight[0]),centre[1]+(centre[1]-mlight[1]),centre[2]+(centre[2]-mlight[2])]
                                        cam_dist=getDistance((j.x,j.y,j.z),centre)
                                        caml_dist=getDistance((j.x,j.y,j.z),reflected_light)
                                        refldist=getDistance(centre,reflected_light)
                                        rayangle=180-math.degrees(math.acos(clamp((cam_dist**2+refldist**2-caml_dist**2)/(2*cam_dist*refldist),-1,1)))
                                        dispersion_angle=180*(1-face.reflect)
                                        try:
                                            fraction_light=rayangle/180*(180-dispersion_angle)/dispersion_angle+1
                                        except:
                                            fraction_light=rayangle/180*(180-dispersion_angle)+1
                                        if self.displayRays:
                                            print
                                            try:
                                                a_xx=centre[0]-j.x
                                                a_yy=centre[1]-j.y
                                                a_zz=centre[2]-j.z
                                                a_x=a_xx/a_zz*d+self.width/2
                                                a_y=a_yy/a_zz*d+self.height/2

                                                b_xx=lightobj.x-j.x
                                                b_yy=lightobj.y-j.y
                                                b_zz=lightobj.z-j.z
                                                b_x=b_xx/b_zz*d+self.width/2
                                                b_y=b_yy/b_zz*d+self.height/2

                                                pygame.draw.aaline(screen, self.edge_colour, (int(a_x),int(a_y)), (int(b_x),int(b_y)), 1)

                                            except ZeroDivisionError:
                                                pass

                                            try:
                                                a_xx=centre[0]-j.x
                                                a_yy=centre[1]-j.y
                                                a_zz=centre[2]-j.z
                                                a_x=a_xx/a_zz*d+self.width/2
                                                a_y=a_yy/a_zz*d+self.height/2

                                                b_xx=reflected_light[0]-j.x
                                                b_yy=reflected_light[1]-j.y
                                                b_zz=reflected_light[2]-j.z
                                                b_x=b_xx/b_zz*d+self.width/2
                                                b_y=b_yy/b_zz*d+self.height/2

                                                pygame.draw.aaline(screen, self.edge_colour, (int(a_x),int(a_y)), (int(b_x),int(b_y)), 1)

                                            except ZeroDivisionError:
                                                pass

                                            try:
                                                a_xx=centre[0]-j.x
                                                a_yy=centre[1]-j.y
                                                a_zz=centre[2]-j.z
                                                a_x=a_xx/a_zz*d+self.width/2
                                                a_y=a_yy/a_zz*d+self.height/2

                                                b_xx=mlight[0]-j.x
                                                b_yy=mlight[1]-j.y
                                                b_zz=mlight[2]-j.z
                                                b_x=b_xx/b_zz*d+self.width/2
                                                b_y=b_yy/b_zz*d+self.height/2

                                                pygame.draw.aaline(screen, self.edge_colour, (int(a_x),int(a_y)), (int(b_x),int(b_y)), 1)

                                            except ZeroDivisionError:
                                                pass

                                        #Light density shading
                                        fraction_light=(fraction_light*face.reflect)+(math.sin(math.radians(90-angle))*(1-face.reflect))
                                        #Inverse squared
                                        if self.light_distance:
                                            fraction_light/=(ldist/self.light_distance)**2
                                        #Getting colour
                                        temp_col=[lightobj.colour[0]*lightobj.brightness*fraction_light/100,lightobj.colour[1]*lightobj.brightness*fraction_light/100,lightobj.colour[2]*lightobj.brightness*fraction_light/100]
                                        light[0]=min(255,temp_col[0]+light[0])
                                        light[1]=min(255,temp_col[1]+light[1])
                                        light[2]=min(255,temp_col[2]+light[2])
                                    colour0=min(face.col[0]*(light[0]/255*(255-(self.min_brightness*self.min_colour[0]/100))+self.min_brightness*self.min_colour[0]/100)/255,255)
                                    colour1=min(face.col[1]*(light[1]/255*(255-(self.min_brightness*self.min_colour[1]/100))+self.min_brightness*self.min_colour[1]/100)/255,255)
                                    colour2=min(face.col[2]*(light[2]/255*(255-(self.min_brightness*self.min_colour[2]/100))+self.min_brightness*self.min_colour[2]/100)/255,255)
                                    pygame.draw.polygon(screen, (colour0,colour1,colour2), cornerlist, 0)

                                    if self.displayNorms:
                                        try:
                                            a_xx=centre[0]-j.x
                                            a_yy=centre[1]-j.y
                                            a_zz=centre[2]-j.z
                                            if a_zz<0:
                                                    continue;
                                            a_x=a_xx/a_zz*d+self.width/2
                                            a_y=a_yy/a_zz*d+self.height/2

                                            b_xx=tri.norm[0]+centre[0]-j.x
                                            b_yy=tri.norm[1]+centre[1]-j.y
                                            b_zz=tri.norm[2]+centre[2]-j.z
                                            if b_zz<0:
                                                    continue;
                                            b_x=b_xx/b_zz*d+self.width/2
                                            b_y=b_yy/b_zz*d+self.height/2
                                        except ZeroDivisionError:
                                            continue;

                                        pygame.draw.aaline(screen, self.edge_colour, (int(a_x),int(a_y)), (int(b_x),int(b_y)), 1)

                if self.displayLights:
                    for light in self.lights:
                        try:
                            xx=light.x-j.x
                            yy=light.y-j.y
                            zz=light.z-j.z
                            if zz<0:
                                    continue;
                            x=xx/zz*d+self.width/2
                            y=yy/zz*d+self.height/2
                        except ZeroDivisionError:
                            continue;

                        pygame.draw.circle(screen, self.node_colour, (int(x),int(y)), self.nodeRadius, 0)

                self.gui.draw(self)
                pygame.display.flip()

    #Adds a light object
    def add_light(self,x=0,y=0,z=0,brightness=100,colour=white):
        self.lights.append(Light(x=x,y=y,z=z,brightness=brightness,colour=colour))

    #Outputs Objects
    def __str__(self):
        for key in self.objects:
            print("\n"+key)
            print(self.objects.get(key))
        return ""

    #Run whenever the colsole is called up
    def console(self):
        pygame.event.pump()
        while True:
            command = input(">>>")
            #Carrys out command
            if command == "output":
                print(self)
            elif command == "wireframe":
                if self.displayFaces:
                    self.displayNodes = 1
                    self.displayEdges = 1
                    self.displayFaces = 0
                else:
                    self.displayNodes = 0
                    self.displayEdges = 0
                    self.displayFaces = 1
            elif command == "quit" or command == "exit":
                self._done = True
            elif command == "debug":
                if self.debug:
                    self.debug = False
                else:
                    print(self)
                    self.debug = True
            elif command == "performance":
                print("FPS:", str(self.fps))
                print("TPS:", str(self.tps))
                print("UPS:", str(self.ups))
            elif command[0:3] == "set":
                variable = command [4:]
                value = input(">>>")
                if variable == "maxfps":
                    self.max_fps = int(value)
                elif variable == "tickspersecond":
                    self.ticks_per_second = int(value)
                elif variable == "maxframeskip":
                    self.max_frame_skip = int(value)
                elif variable == "use_wait":
                    self.use_wait = bool(value)
                elif variable == "fov":
                    self.fov = int(value)
                elif variable == "caption":
                    pygame.display.set_caption(value)
                elif variable == "debug":
                    self.debug = bool(value)
                elif variable == "automove":
                    self.auto_move = bool(value)
                elif variable == "nodes":
                    self.displayNodes =  int(value)
                elif variable == "edges":
                    self.displayEdges =  int(value)
                elif variable == "faces":
                    self.displayFaces =  int(value)
                elif variable == "lights":
                    self.displayLights =  int(value)
                elif variable == "norms":
                    self.displayNorms =  int(value)
                elif variable == "rays":
                    self.displayRays =  int(value)
                else:
                    print(variable.title(),"isn't a valid variable")
            else:
                print(command.title(),"is not a valid command!")
                continue
            break

        #Restarts game
        restart = False
        while not restart and not self._done:
            for event in pygame.event.get(pygame.VIDEOEXPOSE):
                if event.type == pygame.VIDEOEXPOSE:
                    restart = True

    #Adds objects to the game
    def add_objects(self,objects,keys):
        for num in range(len(objects)):
            self.objects[keys[num]]=objects[num]

    def save_screen(self,name):
        pygame.image.save(self.screen, name)

#Basic Instance class
class Instance(object):
    def __init__(self,x,y,z,rotate_z,rotate_y,rotate_x):
        self.x=x
        self.y=y
        self.z=z
        self.rotate_z=rotate_z
        self.rotate_y=rotate_y
        self.rotate_x=rotate_x

#Basic object class
class ObjectBase(object):
    def __init__(self,x=0,y=0,z=0,rotate_z=0,rotate_y=0,rotate_x=0,sub_objects=[]):
        self.x=x
        self.y=y
        self.z=z
        self.rotate_z=rotate_z
        self.rotate_y=rotate_y
        self.rotate_x=rotate_x
        self.sub_objects=sub_objects

    def __str__(self):
        for i,obj in enumerate(self.sub_objects):
            print(str(i)+":")
            print(obj)
        return ""

    def update(self):
        pass

    def end_update(self):
        for i in self.sub_objects:
            i.x+=self.x
            i.y+=self.y
            i.z+=self.z
            i.obj_rotate_z=self.rotate_z
            i.obj_rotate_y=self.rotate_y
            i.obj_rotate_x=self.rotate_x
            i.obj_rotate=self.centre()
            i.update()
        for i in self.sub_objects:
            i.update_rotate()
        self.rotate_z=self.rotate_y=self.rotate_x=self.x=self.y=self.z=0

    def centre(self):
        num_sub=len(self.sub_objects)
        meanX=0
        meanY=0
        meanZ=0
        for i in self.sub_objects:
            x,y,z=i.centre()
            meanX+=x/num_sub
            meanY+=y/num_sub
            meanZ+=z/num_sub
        return (meanX,meanY,meanZ)

    def rotate(self,direction,c,degrees):
        if direction=='z':
            for i in self.sub_objects:
                i.rotate('z',c,degrees)
        elif direction=='y':
            for i in self.sub_objects:
                i.rotate('y',c,degrees)
        elif direction=='x':
            for i in self.sub_objects:
                i.rotate('x',c,degrees)

    def add_subobject(self,subobject):
        subobject.parent=self
        self.sub_objects.append(subobject)

    def scale(self, centre, scale):
        for i in self.sub_objects:
            i.scale(centre,scale)

#Rendering Components(Node, Edge and Face)

#Node Class
class Node(object):
    def __init__(self, coordinates):
        self.x = coordinates[0]
        self.y = coordinates[1]
        self.z = coordinates[2]

#Edge Class
class Edge(object):
    def __init__(self, start, stop):
        self.start = start
        self.stop  = stop

#Face Class
class Face(object):
    def __init__(self,corners,colour,reflect=0):
        self.corners=corners
        self.col=colour
        self.num_corners=len(corners)
        self.triangles=[]
        self.reflect=reflect
        for i in range(len(corners)-2):
            self.triangles.append(Triangle((corners[0],corners[i+1],corners[i+2])))

class Triangle(object):
    def __init__(self,corners):
        self.corners=corners
        meanX = sum([node.x for node in self.corners]) / 3
        meanY = sum([node.y for node in self.corners]) / 3
        meanZ = sum([node.z for node in self.corners]) / 3
        center=(meanX,meanY,meanZ)
        vector0=[corners[0].x-center[0],corners[0].y-center[1],corners[0].z-center[2]]
        vector1=[corners[1].x-center[0],corners[1].y-center[1],corners[1].z-center[2]]
        self.norm=[0,0,0]
        self.norm[0]=((vector0[1]*vector1[2])-(vector0[2]*vector1[1]))*3
        self.norm[1]=((vector0[2]*vector1[0])-(vector0[0]*vector1[2]))*3
        self.norm[2]=((vector0[0]*vector1[1])-(vector0[1]*vector1[0]))*3
        mag=magnitude(self.norm)
        self.norm[0]/=mag
        self.norm[1]/=mag
        self.norm[2]/=mag
        self.ndist=getDistance(self.norm,[0,0,0])

#Basic sub-object class
class SubObjectBase(object):
    def __init__(self,parent=None,x=0,y=0,z=0,rotate_z=0,rotate_y=0,rotate_x=0):
        self.x=x
        self.y=y
        self.z=z
        self.rotate_z=rotate_z
        self.rotate_y=rotate_y
        self.rotate_x=rotate_x
        self.nodes=[]
        self.edges=[]
        self.faces=[]
        self.obj_rotate_z=0
        self.obj_rotate_y=0
        self.obj_rotate_x=0
        self.obj_rotate=[0,0,0]

    def update(self):
        if self.x:
            self.translate("x",self.x)
        if self.y:
            self.translate("y",self.y)
        if self.z:
            self.translate("z",self.z)
        self.x=0
        self.y=0
        self.z=0

    def update_rotate(self):
        if self.rotate_z:
            self.rotate('z',self.centre(),rotate_z)
        if self.rotate_y:
            self.rotate('y',self.centre(),rotate_y)
        if self.rotate_x:
            self.rotate('x',self.centre(),rotate_x)
        if self.obj_rotate_z:
            self.rotate('z',self.obj_rotate,self.obj_rotate_z)
        if self.obj_rotate_y:
            self.rotate('y',self.obj_rotate,self.obj_rotate_y)
        if self.obj_rotate_x:
            self.rotate('x',self.obj_rotate,self.obj_rotate_x)
        self.obj_rotate_z=0
        self.obj_rotate_y=0
        self.obj_rotate_x=0
        self.rotate_z=0
        self.rotate_y=0
        self.rotate_x=0

    def add_nodes(self, nodeList):
        try:
            for node in nodeList:
                self.nodes.append(Node(node))
        except:
            self.nodes.append(Node(nodeList))

    def add_edges(self, edgeList):
        try:
            for (start, stop) in edgeList:
                self.edges.append(Edge(self.nodes[start], self.nodes[stop]))
        except:
            self.edges.append(Edge(self.nodes[edgeList[0]], self.nodes[edgeList[1]]))

    def add_faces(self, faceList):
        try:
            for (corners,colour) in faceList:
                corner_list=[]
                for i in corners:
                    corner_list.append(self.nodes[i])
                for i in range(len(corner_list)-1):
                    self.edges.append(Edge(corner_list[i], corner_list[i+1]))
                self.edges.append(Edge(corner_list[len(corner_list)-1], corner_list[0]))
                for i in range(len(corner_list)-3):
                    self.edges.append(Edge(corner_list[0],corner_list[i+2]))
                self.faces.append(Face(corner_list,colour))
        except:
            corners=faceList[0]
            colour=faceList[1]
            corner_list=[]
            for i in corners:
                corner_list.append(self.nodes[i])
            for i in range(len(corner_list)-1):
                self.edges.append(Edge(corner_list[i], corner_list[i+1]))
            self.edges.append(Edge(corner_list[len(corner_list)-1], corner_list[0]))
            for i in range(len(corner_list)-3):
                self.edges.append(Edge(corner_list[0],corner_list[i+2]))
            self.faces.append(Face(corner_list,colour))

    def add_mesh(self,filename):
        verts = []
        norms = []
        vertsOut = []
        normsOut = []
        dupes={}
        for line in open(filename, "r"):
            vals = line.split()
            if len(vals)==0:
                continue;
            if vals[0][0]=="#":
                continue;
            if vals[0] == "v":
                v = vals[1:4]
                count=0
                for i in v:
                    v[count]=float(i)
                    count+=1
                if v in verts:
                    count=0
                    for i in verts:
                        if i==v:
                            break;
                        count+=1
                    dupes[str(v)]=count
                verts.append(v)
            if vals[0] == "vn":
                n = vals[1:4]
                count=0
                for i in n:
                    n[count]=float(i)
                    count+=1
                norms.append(n)
            if vals[0] == "f":
                v=[]
                n=[]
                for f in vals[1:]:
                    w = f.split("/")
                    if int(w[0]) in dupes:
                        v.append(dupes[int(w[0])])
                    else:
                        v.append(verts[int(w[0])-1])
                    try:
                        n.append(norms[int(w[2])-1])
                    except:
                        pass
                vertsOut.append(v)
                normsOut.append(n)
        count=0
        faces=[]
        for i in vertsOut:
            self.add_nodes(i)
            faces.append((count,count+len(i)))
            count+=len(i)
        faceList=[]
        for i in faces:
            l=[]
            for j in range(i[1]-i[0]):
                l.append(j+i[0])
            faceList.append((l,white))
        self.add_faces(faceList)

    def add_model(self,model):
        self.add_nodes(model[0])
        self.add_faces(model[1])

    def __str__(self):
        self.print_nodes()
        self.print_edges()
        self.print_faces()
        return ""

    def print_nodes(self):
        print(" --- Nodes --- ")
        for i, node in enumerate(self.nodes):
            print(" %d: (%.2f, %.2f, %.2f)" % (i, node.x, node.y, node.z))

    def print_edges(self):
        print("\n --- Edges --- ")
        for i, edge in enumerate(self.edges):
            print(" %d: (%.2f, %.2f, %.2f)" % (i, edge.start.x, edge.start.y, edge.start.z))

    def print_faces(self):
        print("\n --- Faces --- ")
        for i, face in enumerate(self.faces):
            print(" %d: (%.2f, %.2f, %.2f, %.2f)" % (i, face.num_corners, face.corners[0].x, face.corners[0].y, face.corners[0].z))

    def translate(self, axis, d):
        if axis in ['x', 'y', 'z']:
            for node in self.nodes:
                setattr(node, axis, getattr(node, axis) + d)

    def scale(self, centre, scale):
        centre_x=centre[0]
        centre_y=centre[1]
        centre_z=centre[2]
        for node in self.nodes:
            node.x = centre_x + scale * (node.x - centre_x)
            node.y = centre_y + scale * (node.y - centre_y)
            node.z = centre_z + scale * (node.z - centre_z)
        for face in self.faces:
            for tri in face.triangles:
                tri.norm[0] = centre_x + scale * tri.norm[0]
                tri.norm[1] = centre_y + scale * tri.norm[1]
                tri.norm[2] = centre_z + scale * tri.norm[2]

    def centre(self):
        num_nodes = len(self.nodes)
        meanX = sum([node.x for node in self.nodes]) / num_nodes
        meanY = sum([node.y for node in self.nodes]) / num_nodes
        meanZ = sum([node.z for node in self.nodes]) / num_nodes
        return (meanX,meanY,meanZ)

    def rotate(self, direction, c, degrees):
        if direction=='z':
            for node in self.nodes:
                x      = node.x - c[0]
                y      = node.y - c[1]
                d      = math.hypot(y, x)
                theta  = math.atan2(y, x) + math.radians(degrees)
                node.x = c[0] + d * math.cos(theta)
                node.y = c[1] + d * math.sin(theta)
            for face in self.faces:
                for tri in face.triangles:
                    x      = tri.norm[0]
                    y      = tri.norm[1]
                    d      = math.hypot(y, x)
                    theta  = math.atan2(y, x) + math.radians(degrees)
                    tri.norm[0] = d * math.cos(theta)
                    tri.norm[1] = d * math.sin(theta)
        if direction=='y':
            for node in self.nodes:
                y      = node.y - c[1]
                z      = node.z - c[2]
                d      = math.hypot(y, z)
                theta  = math.atan2(y, z) + math.radians(degrees)
                node.z = c[2] + d * math.cos(theta)
                node.y = c[1] + d * math.sin(theta)
            for face in self.faces:
                for tri in face.triangles:
                    y      = tri.norm[1]
                    z      = tri.norm[2]
                    d      = math.hypot(y, z)
                    theta  = math.atan2(y, z) + math.radians(degrees)
                    tri.norm[2] = d * math.cos(theta)
                    tri.norm[1] = d * math.sin(theta)
        if direction=='x':
            for node in self.nodes:
                x      = node.x - c[0]
                z      = node.z - c[2]
                d      = math.hypot(x, z)
                theta  = math.atan2(x, z) + math.radians(degrees)
                node.z = c[2] + d * math.cos(theta)
                node.x = c[0] + d * math.sin(theta)
            for face in self.faces:
                for tri in face.triangles:
                    x      = tri.norm[0]
                    z      = tri.norm[2]
                    d      = math.hypot(x, z)
                    theta  = math.atan2(x, z) + math.radians(degrees)
                    tri.norm[2] = d * math.cos(theta)
                    tri.norm[0] = d * math.sin(theta)

#Light class
class Light(object):
    def __init__(self,x=0,y=0,z=0,brightness=100,colour=white):
        self.x=x
        self.y=y
        self.z=z
        self.brightness=brightness
        self.colour=colour

    def update(self):
        pass

def getDistance(a,b):
    return math.sqrt((max(a[0],b[0])-min(a[0],b[0]))**2+(max(a[1],b[1])-min(a[1],b[1]))**2+(max(a[2],b[2])-min(a[2],b[2]))**2)

def clamp(x,a,b):
    if a<=b:
        return min(max(x,a),b)
    else:
        return min(max(x,b),a)

def convert_mesh(filename):
    text=""
    for line in open(filename, "r"):
        text+=line
    print("Done")
    count=0
    ln_start=0
    write=open(filename,"w")
    line_count=0
    for char in text:
        if char=="v" or "f":
            write.write(text[ln_start:count])
            ln_start=count
            line_count+=1
            if line_count==10000:
                line_count=0
                write.close()
                print("Converting...")
                write=open(filename,"a")
        count+=1
    write.close()

def magnitude(vector):
    return math.sqrt(max(vector[0],-vector[0])+max(vector[1],-vector[1])+max(vector[2],-vector[2]))

#Code for demo program
def main():
    print("Py3D Module")
    print("See Template.py for usage instructions and game1.py for an example program")
    while True:
        choice=input("Do you want to open the demo program Y/N? ")
        if choice!="N" and choice!="n" and choice!="y" and choice !="Y":
            print("That is not a valid choice")
            continue;
        elif choice=="Y" or choice=="y":
            input("The controls are q,w,e,a,s,d,u,j,m,i,k,l\nPress enter to continue")
            game=Game(fov=75,ticks_per_second=40,max_fps=120,use_wait=True,
                         update_callback=None,frame_callback=None,time_source=None,
                         min_brightness=5,min_colour=white, debug=False,
                          caption="Py3D Game")
            game.min_brightness=5
            game.min_colour=white
            class cube(ObjectBase):
                def update(self):
                    if pygame.key.get_pressed()[K_q]:
                        self.rotate_z=-4
                    if pygame.key.get_pressed()[K_w]:
                        self.rotate_y=4
                    if pygame.key.get_pressed()[K_e]:
                        self.rotate_z=4
                    if pygame.key.get_pressed()[K_a]:
                        self.rotate_x=4
                    if pygame.key.get_pressed()[K_s]:
                        self.rotate_y=-4
                    if pygame.key.get_pressed()[K_d]:
                        self.rotate_x=-4
                    if pygame.key.get_pressed()[K_i]:
                        self.y=-1
                    if pygame.key.get_pressed()[K_k]:
                        self.y=1
                    if pygame.key.get_pressed()[K_j]:
                        self.x=-1
                    if pygame.key.get_pressed()[K_l]:
                        self.x=1
                    if pygame.key.get_pressed()[K_u]:
                        self.z=-1
                    if pygame.key.get_pressed()[K_m]:
                        self.z=1
            cube=cube()
            cube_frame=cube.add_subobject(SubObjectBase())
            cube_frame.add_nodes(((15,15,15),(15,50,15),(50,15,15),(50,50,15),(50,50,50),(50,15,50),(15,50,50),(15,15,50)))
            cube_frame.add_faces((((0,1,3,2),white),((4,5,7,6),cyan),((0,2,5,7),red),((1,3,4,6),green),((0,1,6,7),blue),((2,3,4,5),yellow)))
            objects={}
            objects["cube"]=cube
            game.objects=objects
            game.add_light(x=0,y=0,z=0,brightness=100,colour=white)
            class TickHandler(object):
                def pre_update(self,game):
                    pass
                def post_update(self,game):
                    pass
            class GUI(object):
                def update(self,game):
                    pass
                def draw(self,game):
                    pass
            game.tick_handler=TickHandler()
            game.gui=GUI()
            game.loop()
            pygame.quit()
            break;
        elif choice=="N" or choice=="n":
            break;

if __name__=="__main__":
    main()


import pathlib
import tempfile
from glob import glob

import pytest
import numpy as np

import attune

__here__ = pathlib.Path(__file__).parent

@pytest.mark.xfail
def test_is_instance():
    curve = attune.Curve.read(__here__ / "test.curve")
    assert isinstance(curve, attune.Curve)

@pytest.mark.xfail
def test_round_trip():
    curve = attune.Curve.read(__here__ / "test.curve")
    with tempfile.TemporaryDirectory() as td:
        td = pathlib.Path(td)
        curve.save(save_directory=td)
        read_curve = attune.Curve.read(next(td.glob("*.curve")))
        assert np.allclose(curve.setpoints[:], read_curve.setpoints[:])
        assert set(curve.dependent_names) == set(read_curve.dependent_names)
        for d in curve.dependents:
            assert np.allclose(curve[d][:], read_curve[d][:])
            assert curve[d].differential and read_curve[d].differential


from tkinter import *
from tkinter import scrolledtext
import logging

import options.sequential, options.nonsequential
from options.utils import TextHandler
import profile_manager as profileManager

class GUI:
    def __init__(self, master):
        self.master = master
        master.title('Simple Clicker')
        self.setWindowIcon(master)

        self.status = Label(master, text='Inactive', fg='#ff0000')
        self.status.grid(row=0, column=0)

        self.uptime = Label(master, text='0', fg='#ff0000', width=20)
        self.uptime.grid(row=0, column=1)

        self.config = LabelFrame(master, text='Configuration')
        self.config.grid(row=1, column=0, columnspan=2, sticky=W, padx=5, pady=5)

        self.hotkeyFrame = Frame(self.config)
        self.hotkeyFrame.grid(row=0, column=0, sticky=W)

        self.hotkeyLabel = Label(self.hotkeyFrame, text='Hotkey')
        vcmd = (master.register(self.limitChar), '%i')
        self.hotkey = Entry(self.hotkeyFrame, validate='key', validatecommand=vcmd, width=2)
        self.hotkeyLabel.grid(row=0, column=0, sticky=E)
        self.hotkey.grid(row=0, column=1, sticky=W)

        self.profiles = LabelFrame(self.config, text='Profile')
        self.profiles.grid(row=2, column=0, columnspan=2, sticky=W, padx=5, pady=5)

        self.profileLabel = Label(self.profiles, text='Set Profile')
        self.profileLabel.grid(row=1, column=0)

        self.profile = StringVar(master)
        self.setProfile = OptionMenu(self.profiles, self.profile, *self.profileList(), command=self.handleSetProfile)
        self.setProfile.grid(row=1, column=1)

        self.saveProfile = Button(self.profiles, text='Save', command=self.handleSaveProfile)
        self.saveProfile.grid(row=1, column=2)

        self.addProfile = Button(self.profiles, text='➕', command=self.handleAddProfile)
        self.addProfile.grid(row=1, column=3)

        self.delProfile = Button(self.profiles, text='❌', command=self.handleConfirmDelProfile)
        self.delProfile.grid(row=1, column=4)

        self.refreshButton = Button(self.config, text='Refresh Configuration', command=self.readSetting)
        self.refreshButton.grid(row=3, column=0)

        self.saveButton = Button(self.config, text='Save Configuration', command=self.writeSetting)
        self.saveButton.grid(row=3, column=1)

        self.addOptionFrame = Frame(master)
        self.addOptionFrame.grid(row=2, column=0, sticky=W)

        self.scrollFrame = ScrollFrame(master)
        self.scrollFrame.grid(row=3, column=0, columnspan=2, sticky=W)

        self.options = LabelFrame(self.scrollFrame.viewPort, text='Options')
        self.options.grid(row=0, column=0)

        self.logFrame = LabelFrame(master, text='Log')
        self.logFrame.grid(row=4, column=0, columnspan=2, sticky=W, padx=5, pady=5)

        self.level = StringVar(master)
        self.setLevel = OptionMenu(self.logFrame, self.level, *list(logging._levelToName.values()), command=self.changeLevel)
        self.setLevel.grid(row=0, column=0)

        self.clearLogButton = Button(self.logFrame, text='Clear Log', command=self.clearLog)
        self.clearLogButton.grid(row=0, column=1)

        self.log = scrolledtext.ScrolledText(self.logFrame, width=50, height=10, state='disabled')
        self.log.grid(row=1, column=0, columnspan=2)

        self.textHandler = TextHandler(self.log)
        self.textHandler.setLevel(logging.DEBUG)
        self.fileHandler = logging.FileHandler('log.txt')
        self.fileHandler.setLevel(logging.DEBUG)
        self.consoleHandler = logging.StreamHandler()
        self.consoleHandler.setLevel(logging.ERROR)
        self.formatter = logging.Formatter('[%(asctime)s][%(levelname)s] %(message)s')
        self.textHandler.setFormatter(self.formatter)
        self.fileHandler.setFormatter(self.formatter)
        self.consoleHandler.setFormatter(self.formatter)
        self.logger = logging.getLogger()
        self.logger.setLevel(logging.DEBUG)
        self.logger.addHandler(self.textHandler)
        self.logger.addHandler(self.fileHandler)
        self.logger.addHandler(self.consoleHandler)

        self.optionManager = OptionManager(self.options, options.nonsequential.optList, self.logger)

        self.readSetting()
        self.changeLevel(self.level.get())

    def setWindowIcon(self, window):
        try:
            window.iconbitmap('icon.ico')
        except TclError:
            # Linux compatibility
            window.iconphoto(False, PhotoImage(file='icon.png'))

    def limitChar(self, i):
        if i == '1': #if the index is 1 it means the string will be 2 characters long
            return False
        else:
            return True

    def profileList(self):
        profiles = list(profileManager.read().keys())
        if profiles == []:
            profiles = [None]
        return profiles

    def handleSaveProfile(self):
        self.newProfileName = Entry(self.master)
        self.newProfileName.delete(0, END)
        self.newProfileName.insert(0, self.profile.get())
        self.handleCreateProfile()

    def handleAddProfile(self):
        self.childWindow = Toplevel(self.master)
        self.childWindow.title('Name Profile')
        self.setWindowIcon(self.childWindow)
        self.childWindow.geometry('250x50')
        self.newProfileName = Entry(self.childWindow)
        self.newProfileName.pack(fill=X, expand=YES)
        createButton = Button(self.childWindow, text="Create", command=self.handleCreateProfile)
        createButton.pack(fill=X, expand=YES)

    def handleCreateProfile(self):
        profile = self.optionManager.getProfile()
        profileManager.write(self.newProfileName.get(), profile)
        self.refreshProfiles()
        self.profile.set(self.newProfileName.get())
        try:
            self.childWindow.destroy()
        except AttributeError:
            pass
        self.handleSetProfile()

    def handleSetProfile(self, *args):
        self.optionManager.destroyOptions()
        self.optionManager.setProfile(profileManager.read()[self.profile.get()])

    def menuCommand(self, value):
        self.profile.set(value)
        self.handleSetProfile()

    def refreshProfiles(self):
        profiles = self.profileList()

        menu = self.setProfile['menu']
        menu.delete(0, END)
        for string in profiles:
            menu.add_command(label=string,
                             command=lambda value=string: self.menuCommand(value))

    def handleConfirmDelProfile(self):
        self.childWindow = Toplevel(self.master)
        self.childWindow.title('Confirm Delete Profile')
        self.setWindowIcon(self.childWindow)
        self.childWindow.geometry('300x50')
        label = Label(self.childWindow, text='Delete Profile "{}"?'.format(self.profile.get()))
        label.pack(fill=X, expand=YES)
        createButton = Button(self.childWindow, text="Delete", command=self.handleDelProfile)
        createButton.pack(fill=X, expand=YES)

    def handleDelProfile(self):
        self.childWindow.destroy()
        profileManager.remove(self.profile.get())
        self.refreshProfiles()
        self.profile.set('')

    def readSetting(self):
        f = open('config.ini', 'r')
        self.hotkey.delete(0, END)
        self.hotkey.insert(0, f.readline().strip())
        self.profile.set(f.readline().strip())
        if self.profile.get() in self.profileList():
            self.handleSetProfile()
        self.level.set(f.readline().strip())
        f.close
        self.refreshProfiles()

    def writeSetting(self):
        f = open('config.ini', 'w')
        f.write(self.hotkey.get()+'\n')
        f.write(self.profile.get()+'\n')
        f.write(self.level.get())
        f.close()

    def changeLevel(self, level):
        self.logger.setLevel({v: k for k, v in logging._levelToName.items()}[level])

    def clearLog(self):
        f = open('log.txt', 'w').close()
        self.log.configure(state='normal')
        self.log.delete('1.0', END)
        self.log.configure(state='disabled')

class ScrollFrame(Frame):
    # from https://gist.github.com/mp035/9f2027c3ef9172264532fcd6262f3b01
    def __init__(self, parent):
        super().__init__(parent)

        self.canvas = Canvas(self, borderwidth=0, background='#F0F0F0')
        self.viewPort = Frame(self.canvas, background='#F0F0F0')
        self.vsb = Scrollbar(self, orient='vertical', command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.vsb.set)

        self.vsb.pack(side='right', fill='y')
        self.canvas.pack(side='left', fill='both', expand=True)
        self.canvas.create_window((4,4), window=self.viewPort, anchor='nw',
                                  tags='self.viewPort')

        self.viewPort.bind('<Configure>', self.onFrameConfigure)

    def onFrameConfigure(self, event):
        '''Reset the scroll region to encompass the inner frame'''
        self.canvas.configure(scrollregion=self.canvas.bbox('all'))

class OptionWrapper:
    def __init__(self, master, sequential, option, widgets, logger):
        self.widgets = widgets

        self.frame = LabelFrame(master, text=option)

        self.deleteButton = Button(self.frame, text='❌', command=self.findIdAndDestroy)
        self.deleteButton.grid(row=0, column=0)

        self.name = option

        if sequential:
            optionObject = options.sequential.optDict.get(option)
        else:
            optionObject = options.nonsequential.optDict.get(option)
        self.widget = optionObject.Widget(self.frame, 1, logger)

        self.frame.pack(anchor=W, padx=5, pady=0)

    def findIdAndDestroy(self):
        for i in self.widgets:
            if id(i) == id(self):
                self.widgets.remove(i)
        self.frame.destroy()

class OptionManager:
    def __init__(self, parent, availableOptions, logger, sequential=False,  maxOptions=None):
        self.parent = parent
        self.max = maxOptions
        self.sequential = sequential

        self.addOptionFrame = Frame(parent)
        self.addOptionFrame.pack()

        self.addOptionLabel = Label(self.addOptionFrame, text='Add Option')
        self.addOptionLabel.grid(row=0, column=0, sticky=E)

        self.selectedOption = StringVar(parent)
        self.selectedOption.set('➕')
        self.addOptions = OptionMenu(self.addOptionFrame, self.selectedOption, *availableOptions, command=self.handleAddOption)
        self.addOptions.grid(row=0, column=1, sticky=W)

        self.wrappers = []

        self.logger = logger

    def handleAddOption(self, *args):
        if self.max == None:
            self.addOption(self.selectedOption.get())
        elif len(self.wrappers) < self.max:
            self.addOption(self.selectedOption.get())
        self.selectedOption.set('➕')

    def addOption(self, option):
        self.wrappers.append(OptionWrapper(self.parent, self.sequential, option, self.wrappers, self.logger))

    def startOptions(self):
        for o in self.wrappers:
            o.widget.start()

    def stopOptions(self):
        for o in self.wrappers:
            o.widget.stop()

    def updateOptions(self):
        for o in self.wrappers:
            o.widget.update()

    def runOptions(self):
        for o in self.wrappers:
            o.widget.run()

    def destroyOptions(self):
        while self.wrappers != []:
            for o in self.wrappers:
                o.findIdAndDestroy()

    def getProfile(self):
        profile = {}
        occurrences = {}

        for o in self.wrappers:
            if not o.name in occurrences:
                occurrences[o.name] = 0
            else:
                occurrences[o.name] += 1
            optionName = '{}-{}'.format(o.name, occurrences[o.name])
            profile[optionName] = o.widget.returnSettings()

        return profile

    def setProfile(self, profile):
        for option, attributes in profile.items():
            self.addOption(option.split('-')[0])
            self.wrappers[-1].widget.addSettings(attributes)


import sys
import importlib

sys.path.append("commands")

command_modules = [
    "batima",
    "bandeco",
    "chuck",
    "leave",
    "fortune",
]

commands = []
for cmd in command_modules:
	mod = importlib.import_module(cmd, "commands")
	commands.extend(mod.command_description)


# -*- coding: utf-8 -*-
"""
Created on Thu Aug  6 15:38:25 2020

@author: Carlos Coronel

Some auxiliary functions.

All the graph metrics employed were taken from the Brain Connectivity Toolbox for Python [2]:
https://github.com/aestrivex/bctpy [2]

[1] Lancaster, G., Iatsenko, D., Pidde, A., Ticcinelli, V., & Stefanovska, A.
(2018). Surrogate data for hypothesis testing of physical systems.
Physics Reports, 748, 1-60.

[2] Rubinov, M., & Sporns, O. (2010). Complex network measures of brain connectivity:
uses and interpretations. Neuroimage, 52(3), 1059-1069.

"""

import numpy as np
from scipy import stats
from statsmodels.sandbox.stats.multicomp import multipletests

def get_uptri(x):
    """
    Gets the vectorized upper triangle of a matrix x.

    Parameters
    ----------
    x : numpy array.
        connectivity matrix.
    Returns
    -------
    vector : numpy array.
             upper triangle of x in vector form.
    """
    nnodes = x.shape[0]
    npairs = (nnodes**2 - nnodes) // 2
    vector = np.zeros(npairs)

    idx = 0
    for row in range(0, nnodes - 1):
        for col in range(row + 1, nnodes):
            vector[idx] = x[row, col]
            idx = idx + 1

    return(vector)

def matrix_recon(x):
    """
    Reconstructs a connectivity matrix from its upper triangle.

    Parameters
    ----------
    x : numpy array.
        upper triangle of the original matrix.
    Returns
    -------
    matrix : numpy array.
             original connectivity matrix.
    """
    npairs = len(x)
    nnodes = int((1 + np.sqrt(1 + 8 * npairs)) // 2)

    matrix = np.zeros((nnodes, nnodes))
    idx = 0
    for row in range(0, nnodes - 1):
        for col in range(row + 1, nnodes):
            matrix[row, col] = x[idx]
            idx = idx + 1
    matrix = matrix + matrix.T

    return(matrix)

def probabilistic_thresholding(y, surr_number = 50, alpha = 0.05):
    """
    Threshold for neglecting spurious connectivity values in functional fata.
    It uses a phase randomization to destroy the pairwise correlations, while
    preserving the spectral properties of the original signals. Type I error
    (multiple comparisons) corrected by the Benjamini-Hochberg procedure.

    Parameters
    ----------
    y : txN numpy array.
        time series.
        t -> time.
        N -> nodes.
    surr_number : integer.
                  number of surrogates.
    alpha : float (between 0 and 1).
            critical p_value for statistical inference. By default is 0.05.
    Returns
    -------
    FC_adjusted : NxN numpy array.
                  thresholded functional connectivity matrix.
    p_matrix : NxN numpy array.
               p_values for each connectivity pair.
    """

    y = y - np.mean(y, axis = 0)
    yf = np.fft.fft(y, axis = 0)

    nnodes = y.shape[1]
    npairs = (nnodes**2 - nnodes) // 2
    matrix_surr = np.zeros((npairs, surr_number))

    FC_real = np.corrcoef(y.T)
    FC_real = FC_real[np.triu_indices(n = nnodes, k = 1)]

    for i in range(surr_number):
        np.random.seed(i + 1)
        random_vector = np.random.uniform(0, 2 * np.pi, ((yf.shape[0] // 2), yf.shape[1]))
        random_vector = np.row_stack((random_vector, random_vector[::-1,:]))
        yfR = yf * np.exp(1j * random_vector)
        surrogate = np.fft.ifft(yfR, axis = 0)
        surrogate = surrogate.real
        FC_surr = np.corrcoef(surrogate.T)
        matrix_surr[:,i] = FC_surr[np.triu_indices(n = nnodes, k = 1)]

    p_vector = np.zeros(npairs)
    for i in range(npairs):
        mu, sigma = stats.norm.fit(matrix_surr[i,:])
        p_vector[i] = 1 - stats.norm.cdf(FC_real[i], mu, sigma)

    reject, p_adjust, alphacSidak, alphacBonf = multipletests(p_vector, alpha = alpha, method='fdr_bh')

    FC_adjusted = FC_real * ((p_adjust < alpha) * 1)
    FC_adjusted = matrix_recon(FC_adjusted)
    p_matrix = matrix_recon(p_adjust)

    return([FC_adjusted, p_matrix])

#Kuramoto order parameter
def simple_order_parameter(data):
    """
    Calculates the global phase synchronization over time as the Kuramoto order
    parameter.

    Parameters
    ----------
    data : phixN numpy array.
           phi -> phases.
           N -> nodes.
    Returns
    -------
    PhaseSynch : 1xphi numpy array.
                 Kuramoto order parameter.
    """
    phases = data
    PhaseSynch = np.abs(np.mean(np.exp(1j * phases), axis = 1))
    return(PhaseSynch)

###################################
###FUNCTIONS FOR GRAPH ANALYSIS###
###################################
###################################
class BCTParamError(RuntimeError):
    pass

def cuberoot(x):
    """
    Correctly handle the cube root for negative weights, instead of uselessly
    crashing as in python or returning the wrong root as in matlab
    """
    return(np.sign(x) * np.abs(x)**(1 / 3))

def invert(W, copy = True):
    """
    Inverts elementwise the weights in an input connection matrix.
    In other words, change the from the matrix of internode strengths to the
    matrix of internode distances.
    If copy is not set, this function will *modify W in place.*
    Parameters
    ----------
    W : :obj:`numpy.ndarray`
        weighted connectivity matrix
    copy : bool
        if True, returns a copy of the matrix. Otherwise, modifies the matrix
        in place. Default value=True.
    Returns
    -------
    W : :obj:`numpy.ndarray`
        inverted connectivity matrix
    """
    if copy:
        W = W.copy()
    E = np.where(W)
    W[E] = 1. / W[E]
    return(W)

def dummyvar(cis, return_sparse=False):
    """
    This is an efficient implementation of matlab's "dummyvar" command
    using sparse matrices.
    input: partitions, NxM array-like containing M partitions of N nodes
        into <=N distinct communities
    output: dummyvar, an NxR matrix containing R column variables (indicator
        variables) with N entries, where R is the total number of communities
        summed across each of the M partitions.
        i.e.
        r = sum((max(len(unique(partitions[i]))) for i in range(m)))
    """
    # num_rows is not affected by partition indexes
    n = np.size(cis, axis=0)
    m = np.size(cis, axis=1)
    r = np.sum((np.max(len(np.unique(cis[:, i])))) for i in range(m))
    nnz = np.prod(cis.shape)

    ix = np.argsort(cis, axis=0)
    # s_cis=np.sort(cis,axis=0)
    # FIXME use the sorted indices to sort by row efficiently
    s_cis = cis[ix][:, range(m), range(m)]

    mask = np.hstack((((True,),) * m, (s_cis[:-1, :] != s_cis[1:, :]).T))
    indptr, = np.where(mask.flat)
    indptr = np.append(indptr, nnz)

    import scipy.sparse as sp
    dv = sp.csc_matrix((np.repeat((1,), nnz), ix.T.flat, indptr), shape=(n, r))
    return dv.toarray()

def agreement(ci, buffsz=1000):
    """
    Takes as input a set of vertex partitions CI of
    dimensions [vertex x partition]. Each column in CI contains the
    assignments of each vertex to a class/community/module. This function
    aggregates the partitions in CI into a square [vertex x vertex]
    agreement matrix D, whose elements indicate the number of times any two
    vertices were assigned to the same class.
    In the case that the number of nodes and partitions in CI is large
    (greater than ~1000 nodes or greater than ~1000 partitions), the script
    can be made faster by computing D in pieces. The optional input BUFFSZ
    determines the size of each piece. Trial and error has found that
    BUFFSZ ~ 150 works well.
    Parameters
    ----------
    ci : NxM :obj:`numpy.ndarray`
        set of M (possibly degenerate) partitions of N nodes
    buffsz : int | None
        sets buffer size. If not specified, defaults to 1000
    Returns
    -------
    D : NxN :obj:`numpy.ndarray`
        agreement matrix
    """
    ci = np.array(ci)
    n_nodes, n_partitions = ci.shape

    if n_partitions <= buffsz:  # Case 1: Use all partitions at once
        ind = dummyvar(ci)
        D = np.dot(ind, ind.T)
    else:  # Case 2: Add together results from subsets of partitions
        a = np.arange(0, n_partitions, buffsz)
        b = np.arange(buffsz, n_partitions, buffsz)
        if len(a) != len(b):
            b = np.append(b, n_partitions)
        D = np.zeros((n_nodes, n_nodes))
        for i, j in zip(a, b):
            y = ci[:, i:j]
            ind = dummyvar(y)
            D += np.dot(ind, ind.T)

    np.fill_diagonal(D, 0)
    return D

def consensus_und(D, tau, reps = 1000):
    """
    This algorithm seeks a consensus partition of the
    agreement matrix D. The algorithm used here is almost identical to the
    one introduced in Lancichinetti & Fortunato (2012): The agreement
    matrix D is thresholded at a level TAU to remove an weak elements. The
    resulting matrix is then partitions REPS number of times using the
    Louvain algorithm (in principle, any clustering algorithm that can
    handle weighted matrixes is a suitable alternative to the Louvain
    algorithm and can be substituted in its place). This clustering
    produces a set of partitions from which a new agreement is built. If
    the partitions have not converged to a single representative partition,
    the above process repeats itself, starting with the newly built
    agreement matrix.
    NOTE: In this implementation, the elements of the agreement matrix must
    be converted into probabilities.
    NOTE: This implementation is slightly different from the original
    algorithm proposed by Lanchichinetti & Fortunato. In its original
    version, if the thresholding produces singleton communities, those
    nodes are reconnected to the network. Here, we leave any singleton
    communities disconnected.
    Parameters
    ----------
    D : NxN :obj:`numpy.ndarray`
        agreement matrix with entries between 0 and 1 denoting the probability
        of finding node i in the same cluster as node j
    tau : float
        threshold which controls the resolution of the reclustering
    reps : int
        number of times the clustering algorithm is reapplied. default value
        is 1000.
    Returns
    -------
    ciu : Nx1 :obj:`numpy.ndarray`
        consensus partition
    """
    def unique_partitions(cis):
        # relabels the partitions to recognize different numbers on same
        # topology

        n, r = np.shape(cis)  # ci represents one vector for each rep
        ci_tmp = np.zeros(n)

        for i in range(r):
            for j, u in enumerate(sorted(
                    np.unique(cis[:, i], return_index=True)[1])):
                ci_tmp[np.where(cis[:, i] == cis[u, i])] = j
            cis[:, i] = ci_tmp
            # so far no partitions have been deleted from ci

        # now squash any of the partitions that are completely identical
        # do not delete them from ci which needs to stay same size, so make
        # copy
        ciu = []
        cis = cis.copy()
        c = np.arange(r)
        # count=0
        while (c != 0).sum() > 0:
            ciu.append(cis[:, 0])
            dup = np.where(np.sum(np.abs(cis.T - cis[:, 0]), axis=1) == 0)
            cis = np.delete(cis, dup, axis=1)
            c = np.delete(c, dup)
        return np.transpose(ciu)

    n = len(D)
    flag = True
    while flag:
        flag = False
        dt = D * (D >= tau)
        np.fill_diagonal(dt, 0)

        if np.size(np.where(dt == 0)) == 0:
            ciu = np.arange(1, n + 1)
        else:
            cis = np.zeros((n, reps))
            for i in np.arange(reps):
                cis[:, i], _ = modularity_louvain_und_sign(dt)
            ciu = unique_partitions(cis)
            nu = np.size(ciu, axis=1)
            if nu > 1:
                flag = True
                D = agreement(cis) / reps

    return np.squeeze(ciu + 1)

def transitivity_wu(W):
    """
    Transitivity is the ratio of 'triangles to triplets' in the network.
    (A classical version of the clustering coefficient).
    Parameters
    ----------
    W : NxN :obj:`numpy.ndarray`
        weighted undirected connection matrix
    Returns
    -------
    T : int
        transitivity scalar
    """
    K = np.sum(np.logical_not(W == 0), axis=1)
    ws = cuberoot(W)
    cyc3 = np.diag(np.dot(ws, np.dot(ws, ws)))
    return np.sum(cyc3, axis=0) / np.sum(K * (K - 1), axis=0)

def modularity_louvain_und_sign(W, gamma=1, qtype='sta', seed=None):
    """
    The optimal community structure is a subdivision of the network into
    nonoverlapping groups of nodes in a way that maximizes the number of
    within-group edges, and minimizes the number of between-group edges.
    The modularity is a statistic that quantifies the degree to which the
    network may be subdivided into such clearly delineated groups.
    The Louvain algorithm is a fast and accurate community detection
    algorithm (at the time of writing).
    Use this function as opposed to modularity_louvain_und() only if the
    network contains a mix of positive and negative weights.  If the network
    contains all positive weights, the output will be equivalent to that of
    modularity_louvain_und().
    Parameters
    ----------
    W : NxN :obj:`numpy.ndarray`
        undirected weighted/binary connection matrix with positive and
        negative weights
    qtype : str
        modularity type. Can be 'sta' (default), 'pos', 'smp', 'gja', 'neg'.
        See Rubinov and Sporns (2011) for a description.
    gamma : float
        resolution parameter. default value=1. Values 0 <= gamma < 1 detect
        larger modules while gamma > 1 detects smaller modules.
    seed : int | None
        random seed. default value=None. if None, seeds from /dev/urandom.
    Returns
    -------
    ci : Nx1 :obj:`numpy.ndarray`
        refined community affiliation vector
    Q : float
        optimized modularity metric
    Notes
    -----
    Ci and Q may vary from run to run, due to heuristics in the
    algorithm. Consequently, it may be worth to compare multiple runs.
    """
    np.random.seed(seed)

    n = len(W)  # number of nodes

    W0 = W * (W > 0)  # positive weights matrix
    W1 = -W * (W < 0)  # negative weights matrix
    s0 = np.sum(W0)  # weight of positive links
    s1 = np.sum(W1)  # weight of negative links

    if qtype == 'smp':
        d0 = 1 / s0
        d1 = 1 / s1  # dQ=dQ0/s0-sQ1/s1
    elif qtype == 'gja':
        d0 = 1 / (s0 + s1)
        d1 = d0  # dQ=(dQ0-dQ1)/(s0+s1)
    elif qtype == 'sta':
        d0 = 1 / s0
        d1 = 1 / (s0 + s1)  # dQ=dQ0/s0-dQ1/(s0+s1)
    elif qtype == 'pos':
        d0 = 1 / s0
        d1 = 0  # dQ=dQ0/s0
    elif qtype == 'neg':
        d0 = 0
        d1 = 1 / s1  # dQ=-dQ1/s1
    else:
        raise KeyError('modularity type unknown')

    if not s0:  # adjust for absent positive weights
        s0 = 1
        d0 = 0
    if not s1:  # adjust for absent negative weights
        s1 = 1
        d1 = 0

    h = 1  # hierarchy index
    nh = n  # number of nodes in hierarchy
    ci = [None, np.arange(n) + 1]  # hierarchical module assignments
    q = [-1, 0]  # hierarchical modularity values
    while q[h] - q[h - 1] > 1e-10:
        if h > 300:
            raise BCTParamError('Modularity Infinite Loop Style A.  Please '
                                'contact the developer with this error.')
        kn0 = np.sum(W0, axis=0)  # positive node degree
        kn1 = np.sum(W1, axis=0)  # negative node degree
        km0 = kn0.copy()  # positive module degree
        km1 = kn1.copy()  # negative module degree
        knm0 = W0.copy()  # positive node-to-module degree
        knm1 = W1.copy()  # negative node-to-module degree

        m = np.arange(nh) + 1  # initial module assignments
        flag = True  # flag for within hierarchy search
        it = 0
        while flag:
            it += 1
            if it > 1000:
                raise BCTParamError('Infinite Loop was detected and stopped. '
                                    'This was probably caused by passing in a '
                                    'directed matrix.')
            flag = False
            # loop over nodes in random order
            for u in np.random.permutation(nh):
                ma = m[u] - 1
                # positive dQ
                dQ0 = ((knm0[u, :] + W0[u, u] - knm0[u, ma]) -
                       gamma * kn0[u] * (km0 + kn0[u] - km0[ma]) / s0)
                # negative dQ
                dQ1 = ((knm1[u, :] + W1[u, u] - knm1[u, ma]) -
                       gamma * kn1[u] * (km1 + kn1[u] - km1[ma]) / s1)

                dQ = d0 * dQ0 - d1 * dQ1  # rescaled changes in modularity
                dQ[ma] = 0  # no changes for same module

                max_dQ = np.max(dQ)  # maximal increase in modularity
                if max_dQ > 1e-10:  # if maximal increase is positive
                    flag = True
                    mb = np.argmax(dQ)

                    # change positive node-to-module degrees
                    knm0[:, mb] += W0[:, u]
                    knm0[:, ma] -= W0[:, u]
                    # change negative node-to-module degrees
                    knm1[:, mb] += W1[:, u]
                    knm1[:, ma] -= W1[:, u]
                    km0[mb] += kn0[u]  # change positive module degrees
                    km0[ma] -= kn0[u]
                    km1[mb] += kn1[u]  # change negative module degrees
                    km1[ma] -= kn1[u]

                    m[u] = mb + 1  # reassign module

        h += 1
        ci.append(np.zeros((n,)))
        _, m = np.unique(m, return_inverse=True)
        m += 1

        for u in range(nh):  # loop through initial module assignments
            ci[h][np.where(ci[h - 1] == u + 1)] = m[u]  # assign new modules

        nh = np.max(m)  # number of new nodes
        wn0 = np.zeros((nh, nh))  # new positive weights matrix
        wn1 = np.zeros((nh, nh))

        for u in range(nh):
            for v in range(u, nh):
                wn0[u, v] = np.sum(W0[np.ix_(m == u + 1, m == v + 1)])
                wn1[u, v] = np.sum(W1[np.ix_(m == u + 1, m == v + 1)])
                wn0[v, u] = wn0[u, v]
                wn1[v, u] = wn1[u, v]

        W0 = wn0
        W1 = wn1

        q.append(0)
        # compute modularity
        q0 = np.trace(W0) - np.sum(np.dot(W0, W0)) / s0
        q1 = np.trace(W1) - np.sum(np.dot(W1, W1)) / s1
        q[h] = d0 * q0 - d1 * q1

    _, ci_ret = np.unique(ci[-1], return_inverse=True)
    ci_ret += 1

    return ci_ret, q[-1]

def efficiency_wei(Gw, local = False):
    """
    The global efficiency is the average of inverse shortest path length,
    and is inversely related to the characteristic path length.
    The local efficiency is the global efficiency computed on the
    neighborhood of the node, and is related to the clustering coefficient.
    Parameters
    ----------
    Gw : NxN :obj:`numpy.ndarray`
        undirected weighted connection matrix
        (all weights in W must be between 0 and 1)
    local : bool
        If True, computes local efficiency instead of global efficiency.
        Default value = False.
    Returns
    -------
    Eglob : float
        global efficiency, only if local=False
    Eloc : Nx1 :obj:`numpy.ndarray`
        local efficiency, only if local=True
    Notes
    -----
       The  efficiency is computed using an auxiliary connection-length
    matrix L, defined as L_ij = 1/W_ij for all nonzero L_ij; This has an
    intuitive interpretation, as higher connection weights intuitively
    correspond to shorter lengths.
       The weighted local efficiency broadly parallels the weighted
    clustering coefficient of Onnela et al. (2005) and distinguishes the
    influence of different paths based on connection weights of the
    corresponding neighbors to the node in question. In other words, a path
    between two neighbors with strong connections to the node in question
    contributes more to the local efficiency than a path between two weakly
    connected neighbors. Note that this weighted variant of the local
    efficiency is hence not a strict generalization of the binary variant.
    Algorithm:  Dijkstra's algorithm
    """
    def distance_inv_wei(G):
        n = len(G)
        D = np.zeros((n, n))  # distance matrix
        D[np.logical_not(np.eye(n))] = np.inf

        for u in range(n):
            # distance permanence (true is temporary)
            S = np.ones((n,), dtype=bool)
            G1 = G.copy()
            V = [u]
            while True:
                S[V] = 0  # distance u->V is now permanent
                G1[:, V] = 0  # no in-edges as already shortest
                for v in V:
                    W, = np.where(G1[v, :])  # neighbors of smallest nodes
                    td = np.array(
                        [D[u, W].flatten(), (D[u, v] + G1[v, W]).flatten()])
                    D[u, W] = np.min(td, axis=0)

                if D[u, S].size == 0:  # all nodes reached
                    break
                minD = np.min(D[u, S])
                if np.isinf(minD):  # some nodes cannot be reached
                    break
                V, = np.where(D[u, :] == minD)

        np.fill_diagonal(D, 1)
        D = 1 / D
        np.fill_diagonal(D, 0)
        return D

    n = len(Gw)
    Gl = invert(Gw, copy=True)  # connection length matrix
    A = np.array((Gw != 0), dtype=int)
    if local:
        E = np.zeros((n,))  # local efficiency
        for u in range(n):
            # find pairs of neighbors
            V, = np.where(np.logical_or(Gw[u, :], Gw[:, u].T))
            # symmetrized vector of weights
            sw = cuberoot(Gw[u, V]) + cuberoot(Gw[V, u].T)
            # inverse distance matrix
            e = distance_inv_wei(Gl[np.ix_(V, V)])
            # symmetrized inverse distance matrix
            se = cuberoot(e) + cuberoot(e.T)

            numer = np.sum(np.outer(sw.T, sw) * se) / 2
            if numer != 0:
                # symmetrized adjacency vector
                sa = A[u, V] + A[V, u].T
                denom = np.sum(sa)**2 - np.sum(sa * sa)
                # print numer,denom
                E[u] = numer / denom  # local efficiency

    else:
        e = distance_inv_wei(Gl)
        E = np.sum(e) / (n * n - n)
    return(E)

def charpath(D, include_diagonal=False, include_infinite=True):
    """
    The characteristic path length is the average shortest path length in
    the network. The global efficiency is the average inverse shortest path
    length in the network.
    Parameters
    ----------
    D : NxN :obj:`numpy.ndarray`
        distance matrix
    include_diagonal : bool
        If True, include the weights on the diagonal. Default value is False.
    include_infinite : bool
        If True, include infinite distances in calculation
    Returns
    -------
    lambda : float
        characteristic path length
    efficiency : float
        global efficiency
    ecc : Nx1 :obj:`numpy.ndarray`
        eccentricity at each vertex
    radius : float
        radius of graph
    diameter : float
        diameter of graph
    Notes
    -----
    The input distance matrix may be obtained with any of the distance
    functions, e.g. distance_bin, distance_wei.
    Characteristic path length is calculated as the global mean of
    the distance matrix D, excludings any 'Infs' but including distances on
    the main diagonal.
    """
    D = D.copy()

    if not include_diagonal:
        np.fill_diagonal(D, np.nan)

    if not include_infinite:
        D[np.isinf(D)] = np.nan

    Dv = D[np.logical_not(np.isnan(D))].ravel()

    # mean of finite entries of D[G]
    lambda_ = np.mean(Dv)

    # efficiency: mean of inverse entries of D[G]
    efficiency = np.mean(1 / Dv)

    # eccentricity for each vertex (ignore inf)
    ecc = np.array(np.ma.masked_where(np.isnan(D), D).max(axis=1))

    # radius of graph
    radius = np.min(ecc)  # but what about zeros?

    # diameter of graph
    diameter = np.max(ecc)

    return lambda_, efficiency, ecc, radius, diameter

def community_louvain(W, gamma= 1, ci=None, B='modularity', seed=None):
    """
    The optimal community structure is a subdivision of the network into
    nonoverlapping groups of nodes which maximizes the number of within-group
    edges and minimizes the number of between-group edges.
    This function is a fast an accurate multi-iterative generalization of the
    louvain community detection algorithm. This function subsumes and improves
    upon modularity_[louvain,finetune]_[und,dir]() and additionally allows to
    optimize other objective functions (includes built-in Potts Model i
    Hamiltonian, allows for custom objective-function matrices).
    Parameters
    ----------
    W : NxN np.array
        directed/undirected weighted/binary adjacency matrix
    gamma : float
        resolution parameter. default value=1. Values 0 <= gamma < 1 detect
        larger modules while gamma > 1 detects smaller modules.
        ignored if an objective function matrix is specified.
    ci : Nx1 np.arraylike
        initial community affiliation vector. default value=None
    B : str | NxN np.arraylike
        string describing objective function type, or provides a custom
        NxN objective-function matrix. builtin values
            'modularity' uses Q-metric as objective function
            'potts' uses Potts model Hamiltonian.
            'negative_sym' symmetric treatment of negative weights
            'negative_asym' asymmetric treatment of negative weights
    seed : int | None
        random seed. default value=None. if None, seeds from /dev/urandom.
    Returns
    -------
    ci : Nx1 np.array
        final community structure
    q : float
        optimized q-statistic (modularity only)
    """
    np.random.seed(seed)

    n = len(W)
    s = np.sum(W)

    if np.min(W) < -1e-10:
        raise BCTParamError('adjmat must not contain negative weights')

    if ci is None:
        ci = np.arange(n) + 1
    else:
        if len(ci) != n:
            raise BCTParamError('initial ci vector size must equal N')
        _, ci = np.unique(ci, return_inverse=True)
        ci += 1
    Mb = ci.copy()

    if B in ('negative_sym', 'negative_asym'):
        W0 = W * (W > 0)
        s0 = np.sum(W0)
        B0 = W0 - gamma * np.outer(np.sum(W0, axis=1), np.sum(W, axis=0)) / s0

        W1 = W * (W < 0)
        s1 = np.sum(W1)
        if s1:
            B1 = (W1 - gamma * np.outer(np.sum(W1, axis=1), np.sum(W1, axis=0))
                  / s1)
        else:
            B1 = 0

    elif np.min(W) < -1e-10:
        raise BCTParamError('Input connection matrix contains negative '
                            'weights but objective function dealing with '
                            'negative weights was not selected')

    if B == 'potts' and np.any(np.logical_not(np.logical_or(W == 0, W == 1))):
        raise BCTParamError('Potts hamiltonian requires binary input matrix')

    if B == 'modularity':
        B = W - gamma * np.outer(np.sum(W, axis=1), np.sum(W, axis=0)) / s
    elif B == 'potts':
        B = W - gamma * np.logical_not(W)
    elif B == 'negative_sym':
        B = B0 / (s0 + s1) - B1 / (s0 + s1)
    elif B == 'negative_asym':
        B = B0 / s0 - B1 / (s0 + s1)
    else:
        try:
            B = np.array(B)
        except BCTParamError:
            print('unknown objective function type')

        if B.shape != W.shape:
            raise BCTParamError('objective function matrix does not match '
                                'size of adjacency matrix')
        if not np.allclose(B, B.T):
            print('Warning: objective function matrix not symmetric, '
                  'symmetrizing')
            B = (B + B.T) / 2

    Hnm = np.zeros((n, n))
    for m in range(1, n + 1):
        Hnm[:, m - 1] = np.sum(B[:, ci == m], axis=1)  # node to module degree
    H = np.sum(Hnm, axis=1)  # node degree
    Hm = np.sum(Hnm, axis=0)  # module degree

    q0 = -np.inf
    # compute modularity
    q = np.sum(B[np.tile(ci, (n, 1)) == np.tile(ci, (n, 1)).T]) / s

    first_iteration = True

    while q - q0 > 1e-10:
        it = 0
        flag = True
        while flag:
            it += 1
            if it > 1000:
                raise BCTParamError('Modularity infinite loop style G. '
                                    'Please contact the developer.')
            flag = False
            for u in np.random.permutation(n):
                ma = Mb[u] - 1
                dQ = Hnm[u, :] - Hnm[u, ma] + B[u, u]  # algorithm condition
                dQ[ma] = 0

                max_dq = np.max(dQ)
                if max_dq > 1e-10:
                    flag = True
                    mb = np.argmax(dQ)

                    Hnm[:, mb] += B[:, u]
                    Hnm[:, ma] -= B[:, u]  # change node-to-module strengths

                    Hm[mb] += H[u]
                    Hm[ma] -= H[u]  # change module strengths

                    Mb[u] = mb + 1

        _, Mb = np.unique(Mb, return_inverse=True)
        Mb += 1

        M0 = ci.copy()
        if first_iteration:
            ci = Mb.copy()
            first_iteration = False
        else:
            for u in range(1, n + 1):
                ci[M0 == u] = Mb[u - 1]  # assign new modules

        n = np.max(Mb)
        b1 = np.zeros((n, n))
        for i in range(1, n + 1):
            for j in range(i, n + 1):
                # pool weights of nodes in same module
                bm = np.sum(B[np.ix_(Mb == i, Mb == j)])
                b1[i - 1, j - 1] = bm
                b1[j - 1, i - 1] = bm
        B = b1.copy()

        Mb = np.arange(1, n + 1)
        Hnm = B.copy()
        H = np.sum(B, axis=0)
        Hm = H.copy()

        q0 = q
        q = np.trace(B) / s  # compute modularity

    return ci, q

def get_agreement_matrix(W, reps = 100, tau = 0.5, gamma = 1, ci = None, B = 'modularity', seeds = None):
    nnodes = W.shape[0]
    n_p = reps #number of partitions
    D_matrix = np.zeros((nnodes,nnodes)) #Agreement matrix

    if seeds == None:
        seed_vector = np.arange(0, n_p, 1)
    elif len(seed_vector) == n_p:
        seed_vector = seeds
    else:
        print('seeds must be an array of length equal to reps, or None')

    for rand in range(0,n_p):
        partition = community_louvain(W, gamma, ci, B, seed_vector[rand])[0]
        for row in range(0,nnodes):
            for col in range(0,nnodes):
                D_matrix[row,col] += (partition[row] == partition[col]) * 1
    D_matrix /= n_p
    D_matrix[D_matrix < tau] = 0
    np.fill_diagonal(D_matrix, 0)

    return(D_matrix)

def distance_wei(G):
    """
    The distance matrix contains lengths of shortest paths between all
    pairs of nodes. An entry (u,v) represents the length of shortest path
    from node u to node v. The average shortest path length is the
    characteristic path length of the network.
    Parameters
    ----------
    G : NxN :obj:`numpy.ndarray`
        Directed/undirected connection-length matrix.
        NB L is not the adjacency matrix. See below.
    Returns
    -------
    D : NxN :obj:`numpy.ndarray`
        distance (shortest weighted path) matrix
    B : NxN :obj:`numpy.ndarray`
        matrix of number of edges in shortest weighted path
    Notes
    -----
       The input matrix must be a connection-length matrix, typically
    obtained via a mapping from weight to length. For instance, in a
    weighted correlation network higher correlations are more naturally
    interpreted as shorter distances and the input matrix should
    consequently be some inverse of the connectivity matrix.
       The number of edges in shortest weighted paths may in general
    exceed the number of edges in shortest binary paths (i.e. shortest
    paths computed on the binarized connectivity matrix), because shortest
    weighted paths have the minimal weighted distance, but not necessarily
    the minimal number of edges.
       Lengths between disconnected nodes are set to Inf.
       Lengths on the main diagonal are set to 0.
    Algorithm: Dijkstra's algorithm.
    """
    n = len(G)
    D = np.zeros((n, n))  # distance matrix
    D[np.logical_not(np.eye(n))] = np.inf
    B = np.zeros((n, n))  # number of edges matrix

    for u in range(n):
        # distance permanence (true is temporary)
        S = np.ones((n,), dtype=bool)
        G1 = G.copy()
        V = [u]
        while True:
            S[V] = 0  # distance u->V is now permanent
            G1[:, V] = 0  # no in-edges as already shortest
            for v in V:
                W, = np.where(G1[v, :])  # neighbors of shortest nodes

                td = np.array(
                    [D[u, W].flatten(), (D[u, v] + G1[v, W]).flatten()])
                d = np.min(td, axis=0)
                wi = np.argmin(td, axis=0)

                D[u, W] = d  # smallest of old/new path lengths
                ind = W[np.where(wi == 1)]  # indices of lengthened paths
                # increment nr_edges for lengthened paths
                B[u, ind] = B[u, v] + 1

            if D[u, S].size == 0:  # all nodes reached
                break
            minD = np.min(D[u, S])
            if np.isinf(minD):  # some nodes cannot be reached
                break

            V, = np.where(D[u, :] == minD)

    return D, B

def participation_coef(W, ci, degree = 'undirected'):
    """
    Participation coefficient is a measure of diversity of intermodular
    connections of individual nodes.
    Parameters
    ----------
    W : NxN :obj:`numpy.ndarray`
        binary/weighted directed/undirected connection matrix
    ci : Nx1 :obj:`numpy.ndarray`
        community affiliation vector
    degree : {'undirected', 'in', 'out'}, optional
        Flag to describe nature of graph. 'undirected': For undirected graphs,
        'in': Uses the in-degree, 'out': Uses the out-degree
    Returns
    -------
    P : Nx1 :obj:`numpy.ndarray`
        participation coefficient
    """
    if degree == 'in':
        W = W.T

    _, ci = np.unique(ci, return_inverse=True)
    ci += 1

    n = len(W)  # number of vertices
    Ko = np.sum(W, axis=1)  # (out) degree
    Gc = np.dot((W != 0), np.diag(ci))  # neighbor community affiliation
    Kc2 = np.zeros((n,))  # community-specific neighbors

    for i in range(1, int(np.max(ci)) + 1):
        Kc2 += np.square(np.sum(W * (Gc == i), axis=1))

    P = np.ones((n,)) - Kc2 / np.square(Ko)
    # P=0 if for nodes with no (out) neighbors
    P[np.where(np.logical_not(Ko))] = 0

    return(P)

def modularity_und(A, gamma=1, kci=None):
    """
    The optimal community structure is a subdivision of the network into
    nonoverlapping groups of nodes in a way that maximizes the number of
    within-group edges, and minimizes the number of between-group edges.
    The modularity is a statistic that quantifies the degree to which the
    network may be subdivided into such clearly delineated groups.
    Parameters
    ----------
    A : NxN :obj:`numpy.ndarray`
        undirected weighted/binary connection matrix
    gamma : float
        resolution parameter. default value=1. Values 0 <= gamma < 1 detect
        larger modules while gamma > 1 detects smaller modules.
    kci : Nx1 :obj:`numpy.ndarray` | None
        starting community structure. If specified, calculates the Q-metric
        on the community structure giving, without doing any optimzation.
        Otherwise, if not specified, uses a spectral modularity maximization
        algorithm.
    Returns
    -------
    ci : Nx1 :obj:`numpy.ndarray`
        optimized community structure
    Q : float
        maximized modularity metric
    Notes
    -----
    This algorithm is deterministic. The matlab function bearing this
    name incorrectly disclaims that the outcome depends on heuristics
    involving a random seed. The louvain method does depend on a random seed,
    but this function uses a deterministic modularity maximization algorithm.
    """
    from scipy import linalg
    n = len(A)  # number of vertices
    k = np.sum(A, axis=0)  # degree
    m = np.sum(k)  # number of edges (each undirected edge
    # is counted twice)
    B = A - gamma * np.outer(k, k) / m  # initial modularity matrix

    init_mod = np.arange(n)  # initial one big module
    modules = []  # output modules list

    def recur(module):
        n = len(module)
        modmat = B[module][:, module]
        modmat -= np.diag(np.sum(modmat, axis=0))

        vals, vecs = linalg.eigh(modmat)  # biggest eigendecomposition
        rlvals = np.real(vals)
        max_eigvec = np.squeeze(vecs[:, np.where(rlvals == np.max(rlvals))])
        if max_eigvec.ndim > 1:  # if multiple max eigenvalues, pick one
            max_eigvec = max_eigvec[:, 0]
        # initial module assignments
        mod_asgn = np.squeeze((max_eigvec >= 0) * 2 - 1)
        q = np.dot(mod_asgn, np.dot(modmat, mod_asgn))  # modularity change

        if q > 0:  # change in modularity was positive
            qmax = q
            np.fill_diagonal(modmat, 0)
            it = np.ma.masked_array(np.ones((n,)), False)
            mod_asgn_iter = mod_asgn.copy()
            while np.any(it):  # do some iterative fine tuning
                # this line is linear algebra voodoo
                q_iter = qmax - 4 * mod_asgn_iter * \
                    (np.dot(modmat, mod_asgn_iter))
                qmax = np.max(q_iter * it)
                imax = np.argmax(q_iter * it)
                # imax, = np.where(q_iter == qmax)
                # if len(imax) > 1:
                #     imax = imax[0]
                # does switching increase modularity?
                mod_asgn_iter[imax] *= -1
                it[imax] = np.ma.masked
                if qmax > q:
                    q = qmax
                    mod_asgn = mod_asgn_iter
            if np.abs(np.sum(mod_asgn)) == n:  # iteration yielded null module
                modules.append(np.array(module).tolist())
                return
            else:
                mod1 = module[np.where(mod_asgn == 1)]
                mod2 = module[np.where(mod_asgn == -1)]

                recur(mod1)
                recur(mod2)
        else:  # change in modularity was negative or 0
            modules.append(np.array(module).tolist())

    # adjustment to one-based indexing occurs in ls2ci
    if kci is None:
        recur(init_mod)
        ci = ls2ci(modules)
    else:
        ci = kci
    s = np.tile(ci, (n, 1))
    q = np.sum(np.logical_not(s - s.T) * B / m)
    return ci, q

def ls2ci(ls, zeroindexed=False):
    """
    Convert from a 2D python list of modules to a community index vector.
    The list is a pure python list, not requiring numpy.
    Parameters
    ----------
    ls : listof(list)
        pure python list with lowest value zero-indexed
        (regardless of value of zeroindexed parameter)
    zeroindexed : bool
        If True, ci uses zero-indexing (lowest value is 0). Defaults to False.
    Returns
    -------
    ci : Nx1 :obj:`numpy.ndarray`
        community index vector
    """
    if ls is None or np.size(ls) == 0:
        return ()  # list is empty
    nr_indices = sum(map(len, ls))
    ci = np.zeros((nr_indices,), dtype=int)
    z = int(not zeroindexed)
    for i, x in enumerate(ls):
        for j, y in enumerate(ls[i]):
            ci[ls[i][j]] = i + z
    return ci


from django.shortcuts import render
from .models import Post
from .models import Student
from django.utils import timezone
from .forms import PostForm
from .forms import StudentForm
from django.shortcuts import redirect
import Parse
import openpyxl as ox

Curriculum = ""
History = ""
Obligatory = ""
Schedule = ""
Credits = 0
Probation = False

# Create your views here.
def post_list(request):
    posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
    return render(request, 'WebApp/post_list.html', {'posts': posts})

def post_new(request):
    if request.method == "POST":
        form = PostForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.published_date = timezone.now()
            post.save()
            return redirect('post_new')
    else:
        form = PostForm()
    return render(request, 'WebApp/post_edit.html', {'form': form})

def enter_data(request):
    if request.method == "POST":
        form = StudentForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.save()
            # Parse Here
            Schedule = Parse.parse_excel(ox.load_workbook("WebApp/data.xlsx"))
            Curriculum = Parse.parse_curr(post.curriculum)
            History = Parse.parse_history(post.history)
            Obligatory = Parse.parse_oblig(post.obligatory)

            Credits = post.credits
            Probation = post.probation

            # print("Curriculum")
            # print("------------")
            # print(Curriculum)
            # print("__________________________________________")
            # print("History")
            # print("------------")
            # print(History)
            # print("__________________________________________")
            # print("Obligatory")
            # print("------------")
            # print(Obligatory)
            # print("__________________________________________")
            # print("Schedule")
            # print("------------")
            # print(Schedule)
            # print("__________________________________________")
            # print("Credits")
            # print("------------")
            # print(str(Credits))
            # print("__________________________________________")
            # print("Probation")
            # print("------------")
            # print(str(Probation))
            # print("__________________________________________")
            query = Parse.final_parse(Curriculum, History, Obligatory, Schedule, Credits, Probation)
            print(Parse.run_prolog(query))

            return redirect('enter_data')
    else:
        form = StudentForm()
    return render(request, 'WebApp/index.html', {'form': form})

# Some Extra Parsing


#code to play with AI
import numpy as np
from tttBoard import tttBoard
from deepNeuralNetwork_SL import dnNetwork
board1DSize = 3
#from convNeuralNetwork import cnNetwork
#alphaZero = cnNetwork(inputShape=(board1DSize,board1DSize,7),
#                  outputSize=board1DSize*board1DSize+1)
alphaZero = dnNetwork(2*board1DSize*board1DSize+1,board1DSize*board1DSize+1)
alphaZero.loadModel()
board = tttBoard(board1DSize)
board.display()
def gameOver(board):
    if (board.winner()):
        if(board.winner()==1):
            print("O wins!")
            return 1
        elif(board.winner()==2):
            print("X wins!")
            return 1
        elif(board.winner()==-1):
            print("It's a Draw!")
            return 1
    else:
        return 0

while len(board.legalMoves()) > 0 and not(gameOver(board)):
    print(board.legalMoves())
    pMove = int(input("choose from above list of moves"))
    board.makeMove(pMove)
    board.display()
    if gameOver(board):
        break
    print('Thinking...')
#    alphaZeroPredict = alphaZero.predict(board.decodeStateCNN(board._stateHistory))
    s = np.zeros((1,19))
    s[0,:] = board.decodeState(board.getState())
    alphaZeroPredict = alphaZero.predict(s)
    alphaZeroMovesProbs = alphaZeroPredict[0]
    print(alphaZeroMovesProbs)
    board.makeMove(np.argmax(alphaZeroMovesProbs))
#    print('Thinking Tic Tac Toe move...'+str(alphaZeroMove))
    board.display()
    if gameOver(board):
        break


#!/usr/bin/env python
import rospy
import tf2_ros
from math import atan2, sqrt
from geometry_msgs.msg import Twist

if __name__ == '__main__':

    rospy.init_node('tf2_turtle_listener')
    turtle_name = rospy.get_param('~turtle_name')
    goal_name = rospy.get_param('~goal_name')

    tfBuffer = tf2_ros.Buffer()
    listener = tf2_ros.TransformListener(tfBuffer)

    pub_vel = rospy.Publisher('%s/cmd_vel' % turtle_name, Twist, queue_size=1)

    rate = rospy.Rate(10.0)
    while not rospy.is_shutdown():

        try:
            past = rospy.Time.now() - rospy.Duration(5.0)
            trans = tfBuffer.lookup_transform_full(target_frame=turtle_name, target_time=rospy.Time.now(), source_frame=goal_name, source_time=past, fixed_frame='lake', timeout=rospy.Duration(1.0))
            rospy.logdebug("Leading turtle's position: [%f, %f]" % (trans.transform.translation.y,
                                                                    trans.transform.translation.x))
        except:
            rospy.logerr("Unable to find transform!")
            rate.sleep()
            continue

        msg = Twist()
        msg.angular.z = 4.0 * atan2(trans.transform.translation.y, trans.transform.translation.x)
        msg.linear.x = 0.5 * sqrt(trans.transform.translation.x ** 2 + trans.transform.translation.y ** 2)
        pub_vel.publish(msg)

        rate.sleep()