import random
import sys
from timeit import default_timer as timer
from copy import deepcopy

def dfs(adj_list, visited, sublist):
    visited[sublist] = 1
    print(sublist)
    for tail in adj_list[sublist]:
        if visited[tail] == 0:
            dfs(adj_list, visited, tail)

def print_dfs(adj_list):
    visited = [0] * len(adj_list)
    for sublist in range(len(adj_list)):
        if visited[sublist] == 0:
            dfs(adj_list, visited, sublist)

def bfs(adj_list, visited, sublist_index, queue):
    visited[sublist_index] = 1
    print(sublist_index)
    queue.append(sublist_index)
    while queue:
        v = queue.pop()
        for u in adj_list[v]:
            if visited[u] == 0:
                visited[u] = 1
                print(u)
                queue.append(u)

def print_bfs(adj_list):
    visited = [0] * len(adj_list)
    queue = []
    for sublist_index in range(len(adj_list)):
        if visited[sublist_index] == 0:
            bfs(adj_list, visited, sublist_index, queue)

def hamiltonian(v, visited, visited_nodes, nodes, start, path):
    visited[v] = True
    visited_nodes += 1
    for successor in nodes[v]:
        if successor == start and visited_nodes == len(nodes):
            return True
        if not visited[successor]:
            if hamiltonian(successor, visited, visited_nodes, nodes, start, path):
                path.append(successor)
                return True
    visited[v] = False
    visited_nodes -= 1
    return False

def print_hamiltonian(adjacency_list):
    visited = [False] * len(adjacency_list)
    path = [0]
    start = 0
    visited_nodes = 0
    cycle = hamiltonian(start, visited, visited_nodes, adjacency_list, start, path)
    if cycle:
        print("\nCykl Hamiltona:")
        for i in path:
            print(i)
    else:
        print("\nBrak cyklu Hamiltona.")

def euler(v, list_copy, stack):
    while list_copy[v]:
        u = list_copy[v].pop()
        list_copy[u].remove(v)
        euler(u, list_copy, stack)
    stack.append(v)

def print_euler(adjacency_list):
    stack = []
    list_copy = deepcopy(adjacency_list)
    euler(0, list_copy, stack)
    cycle = True
    for node in list_copy:
        if node != []:
            cycle = False
            break
    if cycle and stack[0] == stack[-1]:
        print("\nCykl Eulera:")
        for i in stack:
            print(i)
    else:
        print("\nBrak cyklu Eulera.")

def undirected_graph(n, saturation):
    successful = False
    while not successful:
        successful = True
        lists = [[] for i in range(n)]
        number_of_edges = int(((n * (n - 1)) // 2) * saturation)
        hamiltonian_cycle = random.sample(range(n), n)
        # tworzymy cykl hamiltona
        for index, node in enumerate(hamiltonian_cycle):
            if index != len(hamiltonian_cycle) - 1:
                lists[node].append(hamiltonian_cycle[index + 1])
                lists[hamiltonian_cycle[index + 1]].append(node)
        lists[hamiltonian_cycle[-1]].append(hamiltonian_cycle[0])
        lists[hamiltonian_cycle[0]].append(hamiltonian_cycle[-1])
        number_of_edges -= len(hamiltonian_cycle)
        # dopelniamy graf
        for i in range(number_of_edges):
            first = random.randint(0, n - 1)
            second = random.randint(0, n - 1)
            start = timer()
            while second == first or second in lists[first]:
                second = random.randint(0, n - 1)
                if timer() - start > 0.1:
                    successful = False
                    break
            if successful:
                lists[first].append(second)
                lists[second].append(first)
                successful = True
            else:
                break
    return lists

def graph_even_deg(n, saturation):
    successful = False
    while not successful:
        successful = True
        lists = [[] for i in range(n)]
        number_of_edges = int(((n*(n-1))//2) * saturation)
        hamiltonian_cycle = random.sample(range(n), n)
        # tworzymy cykl hamiltona
        for index, node in enumerate(hamiltonian_cycle):
            if index != len(hamiltonian_cycle)-1:
                lists[node].append(hamiltonian_cycle[index+1])
                lists[hamiltonian_cycle[index+1]].append(node)
        lists[hamiltonian_cycle[-1]].append(hamiltonian_cycle[0])
        lists[hamiltonian_cycle[0]].append(hamiltonian_cycle[-1])
        number_of_edges -= len(hamiltonian_cycle)
        # dopelniamy graf
        for i in range(number_of_edges//3):
            first = random.randint(0, n-1)
            second = random.randint(0, n-1)
            start = timer()
            while second == first or second in lists[first]:
                second = random.randint(0, n - 1)
                if timer()-start > 0.1:
                    successful = False
                    break
            third = random.randint(0, n - 1)
            start = timer()
            while third == first or third == second or third in lists[first] or third in lists[second]:
                third = random.randint(0, n - 1)
                if timer()-start > 0.1:
                    successful = False
                    break
            if successful:
                lists[first].append(second)
                lists[first].append(third)
                lists[second].append(first)
                lists[second].append(third)
                lists[third].append(first)
                lists[third].append(second)
                successful = True
            else:
                break
    return lists

def graph_non_hamiltonian(n, saturation):
    successful = False
    while not successful:
        successful = True
        lists = [[] for i in range(n)]
        number_of_edges = int(((n * (n - 1)) // 2) * saturation)
        hamiltonian_cycle = random.sample(range(n), n)
        # tworzymy cykl hamiltona
        for index, node in enumerate(hamiltonian_cycle):
            if index != len(hamiltonian_cycle) - 1:
                lists[node].append(hamiltonian_cycle[index + 1])
                lists[hamiltonian_cycle[index + 1]].append(node)
        lists[hamiltonian_cycle[-1]].append(hamiltonian_cycle[0])
        lists[hamiltonian_cycle[0]].append(hamiltonian_cycle[-1])
        number_of_edges -= len(hamiltonian_cycle)
        # dopelniamy graf
        for i in range(number_of_edges // 3):
            first = random.randint(0, n - 1)
            second = random.randint(0, n - 1)
            start = timer()
            while second == first or second in lists[first]:
                second = random.randint(0, n - 1)
                if timer() - start > 0.1:
                    successful = False
                    break
            third = random.randint(0, n - 1)
            start = timer()
            while third == first or third == second or third in lists[first] or third in lists[second]:
                third = random.randint(0, n - 1)
                if timer() - start > 0.1:
                    successful = False
                    break
            if successful:
                lists[first].append(second)
                lists[first].append(third)
                lists[second].append(first)
                lists[second].append(third)
                lists[third].append(first)
                lists[third].append(second)
                successful = True
            else:
                break
            number_of_edges -= 3
        if successful:
            # izolujemy wierzcholek
            isolated = random.randint(0, n-1)
            number_of_edges += len(lists[isolated])
            lists[isolated] = []
            for i in lists:
                try:
                    i.remove(isolated)
                except:
                    pass
            # stracilismy troche krawedzi przy izolacji wiec odpowiednio dopelniamy o liczbe krawedzi ktora stracilismy
            for i in range(number_of_edges//3):
                first = random.randint(0, n-1)
                start = timer()
                while first == isolated:
                    first = random.randint(0, n - 1)
                    if timer() - start > 0.1:
                        successful = False
                        break
                second = random.randint(0, n-1)
                while second == first or second in lists[first] or second == isolated:
                    second = random.randint(0, n - 1)
                    if timer() - start > 0.1:
                        successful = False
                        break
                third = random.randint(0, n - 1)
                while third == first or third == second or third in lists[first] or third in lists[second] or third == isolated:
                    third = random.randint(0, n - 1)
                    if timer() - start > 0.1:
                        successful = False
                        break
                if successful:
                    lists[first].append(second)
                    lists[first].append(third)
                    lists[second].append(first)
                    lists[second].append(third)
                    lists[third].append(first)
                    lists[third].append(second)
                    successful = True
                else:
                    break
    return lists

sys.setrecursionlimit(100000)
option = None
created = False
while option != '0':
    print("-----------------------")
    print("1.Wpisz wlasny graf")
    print("2.Utwórz graf spójny nieskierowany o n wierzchołkach i zadanym nasyceniu grafu")
    print("3.Utworz graf z cyklem Hamiltona i nasyceniem krawedziami 30%")
    print("4.Utworz graf z cyklem Hamiltona i nasyceniem krawedziami 70%")
    print("5.Utwórz graf nieskierowany nie-hamiltonowski o nasyceniu 50%")
    print("6.Wyswietl graf(DFS)")
    print("7.Wyswietl graf(BFS)")
    print("8.Znajdz cykl Eulera")
    print("9.Znajdz cykl Hamiltona")
    print("0.Wyjscie")
    print("-----------------------")
    option = input(">")
    if option == '1':
        print("Podaj liczbe wierzcholkow:")
        n = int(input())
        graph = []
        print("Podaj sasiadujace wierzcholki kazdego z wierzcholkow:")
        for node in range(n):
            graph.append([])
            graph[-1] = [int(x) for x in input("[{}]: ".format(node)).split()]
        print(graph)
        created = True
    elif option == '2':
        print("Podaj liczbe wierzcholkow:")
        n = int(input())
        print("Podaj nasycenie w %:")
        s = float(input())/100
        graph = undirected_graph(n, s)
        created = True
    if option == '3':
        print("Podaj liczbe wierzcholkow:")
        n = int(input())
        graph = graph_even_deg(n, 0.3)
        created = True
    elif option == '4':
        print("Podaj liczbe wierzcholkow:")
        n = int(input())
        graph = graph_even_deg(n, 0.7)
        created = True
    elif option == '5':
        print("Podaj liczbe wierzcholkow:")
        n = int(input())
        graph = graph_non_hamiltonian(n, 0.5)
        created = True
    if created:
        if option == '6':
            print_dfs(graph)
            input("Wcisnij enter aby kontynuowac...")
        if option == '7':
            print_bfs(graph)
            input("Wcisnij enter aby kontynuowac...")
        elif option == '8':
            print_euler(graph)
            input("Wcisnij enter aby kontynuowac...")
        elif option == '9':
            print_hamiltonian(graph)
            input("Wcisnij enter aby kontynuowac...")
    else:
        print("Najpierw utworz graf dowolna metoda")
        input("Wcisnij enter aby kontynuowac...")

Finding Eulerian and Hamiltonian cycle in graph PL.py


from aiohttp import web
from aiohttp_apispec import setup_aiohttp_apispec, validation_middleware

from image_app.config import config
from image_app.web.middlewares import error_middleware
from image_app.web.routes import setup_routes

def create_app() -> web.Application:
    app = web.Application(
        middlewares=[error_middleware, validation_middleware],
        client_max_size=config["file_max_size"],
    )
    setup_routes(app)
    setup_aiohttp_apispec(
        app=app,
        title="Image APP",
        version="v1",
        url="/api/docs/json",
        swagger_path="/api/docs",
    )
    return app


"""Desafio 112: Dentro do pacote utilidadesCeV que criamos no desafio 111, temos um módulo chamado dado. Crie uma função
chamada leiaDinheiro() que seja capaz de funcionar como a função input(), mas com uma validação de dados para aceitar
apenas valores que sejam monetários."""

def resumo(valor=0, up=0, down=0,TF=False):
    print("-=" * 30)
    print(f"RESUMO DO VALOR")
    print("-=" * 30)
    print(f'Preço analisado --> {moeda(valor)}')
    print(f'{up}% de aumento --> {aumentar(valor, up, f=TF)}')
    print(f'{down}% de redução --> {diminuir(valor, down, f=TF)}')
    print(f'Dobro do preço --> {dobro(valor, f=TF)}')
    print(f'Metade do preço --> {metade(valor, f=TF)}')
    print("--" * 15)

def aumentar(num, perc, f=False):
    from time import sleep
    #print('-=' * 20)
    #print(f'R${num} será aumentado em {perc}%')
    #print('-->', end='')
    #sleep(2)
    resultado = float(num * (perc/100 + 1))
    if f:
        return moeda(resultado)
    else:
        return resultado

def diminuir(num, perc, f=False):
    from time import sleep
    #print('-=' * 20)
    #print(f'R${num} será diminuindo em {perc}%')
    #rint('-->', end='')
    #sleep(2)
    resultado = float(num * (1 - perc/100))
    if f:
        return moeda(resultado)
    else:
        return resultado

def dobro(num, f=False):
    if f:
        return moeda(num * 2)
    else:
        return num * 2

def metade(num, f=False):
    if f:
        return moeda(num/2)
    else:
        return num / 2

def moeda(preço=0, moeda= 'R$'):
    return f'{moeda}{preço:.2f}'.replace('.', ',')


from django import forms
from .models import STATUS_CHOICES

default_status = STATUS_CHOICES[0][0]

BROWSER_DATETIME_FORMAT = '%Y-%m-%d'

class TaskForm(forms.Form):
    description = forms.CharField(max_length=200, required=True, label='Описание')
    more = forms.CharField(max_length=3000, required=False, label='Подробное описание',
                           widget=forms.Textarea)
    status = forms.ChoiceField(choices=STATUS_CHOICES, required=True, label='Статус',
                               initial=default_status)
    date = forms.DateTimeField(required=False, label='Время выполнения',
                                     input_formats=['%Y-%m-%dT%H:%M', BROWSER_DATETIME_FORMAT,
                                                    '%Y-%m-%dT%H:%M:%S', '%Y-%m-%d %H:%M',
                                                    '%Y-%m-%d %H:%M:%S'],
                                     widget=forms.DateTimeInput(attrs={'type': 'datetime-local'}))


import math
import random

import pygame

def extract_color(img, color, add_surf=None):
    img = img.copy()
    img.set_colorkey(color)
    mask = pygame.mask.from_surface(img)
    surf = mask.to_surface(setcolor=(0, 0, 0, 0), unsetcolor=color)
    if add_surf:
        base_surf = pygame.Surface(img.get_size())
        base_surf.fill(color)
        add_surf = (add_surf[0].convert(), add_surf[1])
        add_surf[0].set_colorkey(add_surf[1])
        base_surf.blit(add_surf[0], (0, 0))
        base_surf.blit(surf, (0, 0))
        base_surf.set_colorkey((0, 0, 0))
        return base_surf
    else:
        return surf

class AnimatedFoliage:
    def __init__(self, image, color_chain, motion_scale=1):
        self.motion_scale = motion_scale
        self.base_image = image.copy()
        self.color_chain = color_chain
        self.layers = []

        for i, color in enumerate(color_chain[::-1]):
            if i == 0:
                self.layers.append(extract_color(self.base_image, color))
            else:
                self.layers.append(extract_color(self.base_image, color, add_surf=(self.layers[-1], color_chain[::-1][i - 1])))

        self.layers = self.layers[::-1]

    def find_leaf_point(self):
        while True:
            point = (int(random.random() * self.layers[0].get_width()), int(random.random() * self.layers[0].get_height()))
            color = self.layers[0].get_at(point)
            if list(color)[:3] != [0, 0, 0]:
                return point

    def render(self, surf, pos, m_clock=0, seed=14):
        surf.blit(pygame.transform.rotate(self.layers[0], math.sin(m_clock * 0.8 + (2.7 * seed)) * 1.2), (pos[0] + math.sin(m_clock * 1.7 + (2.7 * seed)) * 3 * self.motion_scale, pos[1] + math.sin(m_clock + (2.2 * seed)) * 2 * self.motion_scale))
        surf.blit(self.base_image, pos)
        for i, layer in enumerate(self.layers):
            if i != 0:
                surf.blit(pygame.transform.rotate(layer, math.sin(m_clock * 1.1) * 1.5), (pos[0] + math.sin(m_clock * (1.25 * i) + (2.7 * seed)) * 3 * self.motion_scale, pos[1] + math.sin(m_clock * (1.25 * i) + (2.2 * seed)) * 2 * self.motion_scale))
            else:
                surf.blit(layer, pos)


import classes_traj as t
import numpy as np
import matplotlib.pyplot as plt

#We define the coordinates of the first box, considering our set of trajectories
def initBox(listTraj):
    xmin = listTraj.trajectories[0].traj[0].x
    xmax = listTraj.trajectories[0].traj[0].x
    ymin = listTraj.trajectories[0].traj[0].y
    ymax = listTraj.trajectories[0].traj[0].y
    for j in listTraj.trajectories:
        for k in j.traj:
            if (k.x<xmin):
                xmin = k.x
            elif (k.x>xmax):
                xmax = k.x
            if (k.y<ymin):
                ymin = k.y
            elif (k.y>ymax):
                ymax = k.y
    return [xmin, xmax, ymin, ymax]

#We define a function that create our boxes, depending on the subdivision parameter K
def createBoxes(xmin, xmax, ymin, ymax, K):
    boxes = {}
    x_len = (xmax-xmin)/K
    y_len = (ymax-ymin)/K
    xbox_min = xmin
    xbox_max = xmin + x_len
    ybox_min = ymin
    ybox_max = ymin + y_len
    i = 1
    for j in range(K):
        for k in range(K):
            boxes[i] = t.Box(xbox_min+k*x_len,xbox_max+k*x_len,ybox_min+j*y_len,ybox_max+j*y_len,i)
            i+=1
    return boxes

#For a chosen box and a chosen trajectory, we spot the points where the trajectory crosses one of the borders of the box
#We return a List which contains pairs of points (one in the box and one outside), and the value -1 or 1 (1 if the trajectory is entering the box, -1 if not)

def crossingPoints(boxes,Traj):
    t = Traj.traj
    # We store in L all pairs of points which defines an entry or an out of the box
    #If one point is outside the box and the next one is inside, we have a spot where the trajectory is entering
    #If one point is inside the box and the next one is outside, we have a spot where the trajectory is going out
    for i in range(len(t)-1) :
        for j in boxes:
            Box=boxes[j]
            if t[i].x <= Box.xmax and t[i].x >= Box.xmin and t[i].y <= Box.ymax and t[i].y >= Box.ymin :
                if t[i+1].x >= Box.xmax or t[i+1].x <= Box.xmin or t[i+1].y >= Box.ymax or t[i+1].y <= Box.ymin :
                    Traj.pairs.append([t[i],t[i+1],j,"out"])

            else :
                if t[i+1].x >= Box.xmin and t[i+1].x <= Box.xmax and t[i+1].y >= Box.ymin and t[i+1].y <= Box.ymax :
                    Traj.pairs.append([t[i],t[i+1],j,"in"])
    pass

#After we created the list with crossingPoints(), we can determine the point where the trajectory crosses the box, and the border it crosses

def defImpactPoints(boxes,Traj):
    L = Traj.pairs
    for i in range(len(L)-1):
        if L[i][0].ind==L[i+1][0].ind and L[i][-1]=="in":
            L[i],L[i+1]=L[i+1],L[i]

    for pair in L:
        P1 = pair[0]
        P2 = pair[1]
        Box=boxes[pair[2]]

        A=t.Point(Box.xmin, Box.ymin, None)
        B=t.Point(Box.xmax, Box.ymin, None)
        C=t.Point(Box.xmax, Box.ymax, None)
        D=t.Point(Box.xmin, Box.ymax, None)

        #if the two points of a pair have the same x coordinates, we won't be able to compute the intersection with the box, so we must define it here
        #the intersection point will have the same x coordinates as P1 and P2, and its y coordinate will be ymin or ymax of the box
        if P1.x==P2.x:
            if min(P1.y,P2.y)<=Box.ymax and max(P1.y,P2.y)>=Box.ymax:
                y_impact = Box.ymax
                border='u'
            else:
                y_impact = Box.ymin
                border='b'
            x_impact = P1.x

        #same reasonning with the same y coordinates
        elif P1.y==P2.y:
            if min(P1.x,P2.x)<=Box.xmax and max(P1.x,P2.x)>=Box.xmax:
                x_impact = Box.xmax
                border='r'
            else:
                x_impact = Box.xmin
                border='l'
            y_impact = P1.y

        #In the other cases, we compute the intersection point with every border of the box, and we deduce the one that is appropriate
        elif defInter(P1, P2, A, B)[0]<=max(P1.x,P2.x) and defInter(P1, P2, A, B)[0]>=min(P1.x,P2.x) and defInter(P1, P2, A, B)[0]<=Box.xmax and defInter(P1, P2, A, B)[0]>=Box.xmin :
            #Intersection with the bottom border
            x_impact = defInter(P1, P2, A, B)[0]
            y_impact = Box.ymin
            border = 'b'
        elif defInter(P1, P2, D, C)[0]<=max(P1.x,P2.x) and defInter(P1, P2, D, C)[0]>=min(P1.x,P2.x) and defInter(P1, P2, D, C)[0]<=Box.xmax and defInter(P1, P2, D, C)[0]>=Box.xmin :
            #Intersection with the upper border
            x_impact = defInter(P1, P2, D, C)[0]
            y_impact = Box.ymax
            border = 'u'
        elif defInter(P1, P2, B, C)[1]<=max(P1.y,P2.y) and defInter(P1, P2, B, C)[1]>=min(P1.y,P2.y) and defInter(P1, P2, B, C)[1]<=Box.ymax and defInter(P1, P2, B, C)[1]>=Box.ymin :
            #Intersection with the right border
            x_impact = Box.xmax
            y_impact = defInter(P1, P2, B, C)[1]
            border = 'r'
        elif defInter(P1, P2, A, D)[1]<=max(P1.y,P2.y) and defInter(P1, P2, A, D)[1]>=min(P1.y,P2.y) and defInter(P1, P2, A, D)[1]<=Box.ymax and defInter(P1, P2, A, D)[1]>=Box.ymin :
            #Intersection with the left border
            x_impact = Box.xmin
            y_impact = defInter(P1, P2, A, D)[1]
            border = 'l'

        #We define the impact point its index in the trajectory

        P_impact=t.PointImpact(x_impact,y_impact, P1.ind, Box.ind, border)

        #We add it to the list of impact points of the trajectory

        Traj.impactPoints.append(P_impact)

        #We add this point in the list of impact points of the box
        Box.impactPoints[border].append(P_impact)
        pass

#We define a function that returns the coordinates of the intersection of two lines defined by four points
def defInter(A, B, C, D):
    if C.x==D.x:
        x_inter = C.x
        y_inter = (B.y-A.y)/(B.x-A.x)*(x_inter-A.x)+A.y
    elif C.y==D.y:
        y_inter = C.y
        x_inter = (B.x-A.x)/(B.y-A.y)*(y_inter-A.y)+A.x
    return [x_inter, y_inter]

#for every box, we compute the average coordinates of the intersection points of each border
def AvgPoints(boxes,K):
    lAVGPoints = t.AVGPoints().AVGPoints
    for i in boxes:
        Box=boxes[i]
        for border in Box.impactPoints:
            if border=='u':
                if i<=K*(K-1):
                    x_avg = np.mean([P.x for P in Box.impactPoints[border]]+[P.x for P in boxes[i+K].impactPoints['b']])
                    y_avg = np.mean([P.y for P in Box.impactPoints[border]]+[P.y for P in boxes[i+K].impactPoints['b']])
                else:
                    x_avg = np.mean([P.x for P in Box.impactPoints[border]])
                    y_avg = np.mean([P.y for P in Box.impactPoints[border]])
            elif border=='b':
                if i>K:
                    x_avg = np.mean([P.x for P in Box.impactPoints[border]]+[P.x for P in boxes[i-K].impactPoints['u']])
                    y_avg = np.mean([P.y for P in Box.impactPoints[border]]+[P.y for P in boxes[i-K].impactPoints['u']])
                else:
                    x_avg = np.mean([P.x for P in Box.impactPoints[border]])
                    y_avg = np.mean([P.y for P in Box.impactPoints[border]])
            elif border=='r':
                if i%K!=0:
                    x_avg = np.mean([P.x for P in Box.impactPoints[border]]+[P.x for P in boxes[i+1].impactPoints['l']])
                    y_avg = np.mean([P.y for P in Box.impactPoints[border]]+[P.y for P in boxes[i+1].impactPoints['l']])
                else:
                    x_avg = np.mean([P.x for P in Box.impactPoints[border]])
                    y_avg = np.mean([P.y for P in Box.impactPoints[border]])
            else :
                if i%K!=1:
                    x_avg = np.mean([P.x for P in Box.impactPoints[border]]+[P.x for P in boxes[i-1].impactPoints['r']])
                    y_avg = np.mean([P.y for P in Box.impactPoints[border]]+[P.y for P in boxes[i-1].impactPoints['r']])
                else:
                    x_avg = np.mean([P.x for P in Box.impactPoints[border]])
                    y_avg = np.mean([P.y for P in Box.impactPoints[border]])
            lAVGPoints.append(t.PointImpact(x_avg,y_avg,None,Box.ind,border))
    return lAVGPoints

def consTraj(boxes,Traj,K):
    lAVGPoints = AvgPoints(boxes,K)
    for i in range(len(Traj.impactPoints)):
        for P in lAVGPoints:
            if Traj.impactPoints[i].border==P.border and Traj.impactPoints[i].box==P.box:
                Traj.impactPoints[i]=P
    pass


#!/bin/python

# A 0-1 knapsack problem consists in choosing a subset of items with maximal
# value subject the a constraint of weight.
#
# Suppose that there are N items i_1, ..., i_N, each with weight w_1, ..., w_N
# and value v_1, ..., v_N. The problem consists in choosing the items in such
# a way that a bag of capacity W can be filled such the value of the items is
# maximal.
#
# It is called a 0-1 knapsack problem because each item can either be chosen
# or not.

from ortools.algorithms import pywrapknapsack_solver
from ortools.algorithms.pywrapknapsack_solver import KnapsackSolver

# The problem data.
values = [
    360, 83, 59, 130, 431, 67, 230, 52, 93, 125, 670, 892, 600, 38, 48, 147,
    78, 256, 63, 17, 120, 164, 432, 35, 92, 110, 22, 42, 50, 323, 514, 28,
    87, 73, 78, 15, 26, 78, 210, 36, 85, 189, 274, 43, 33, 10, 19, 389, 276,
    312
]
weights = [[
    7, 0, 30, 22, 80, 94, 11, 81, 70, 64, 59, 18, 0, 36, 3, 8, 15, 42, 9, 0,
    42, 47, 52, 32, 26, 48, 55, 6, 29, 84, 2, 4, 18, 56, 7, 29, 93, 44, 71,
    3, 86, 66, 31, 65, 0, 79, 20, 65, 52, 13
]]
capacity = [850]

solver = KnapsackSolver(KnapsackSolver.KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER, '0-1 knapsack')

solver.Init(values, weights, capacity)
solution = solver.Solve()

selection = []
item_wt = []
total_weight = 0

print(f'Total value = {solution}')
for i in range(0, len(values)):
    if solver.BestSolutionContains(i):
        selection.append(i)
        item_wt.append(weights[0][i])
        total_weight += weights[0][i]

print(f'Total weight = {total_weight}')
print(f'Selection = {selection}')
print(f'Item weights = {item_wt}')


import pandas as pd
import numpy as np

from datetime import datetime,timedelta, date
import requests
import io

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import seaborn as sns

from sklearn.neighbors import KernelDensity
from scipy.signal import argrelextrema

import warnings
warnings.filterwarnings("ignore")

### 0. Dataset preparation

#dataset loading
bond_url = 'https://iss.moex.com/iss/apps/infogrid/emission/rates.csv?iss.dp=comma&iss.df=%25d.%25m.%25Y&iss.tf=%25H:%25M:%25S&iss.dtf=%25d.%25m.%25Y%25H:%25M:%25S&iss.only=rates&limit=unlimited&lang=ru'
s=requests.get(bond_url).content
df = pd.read_csv(io.StringIO(s.decode('windows-1251')), sep=';', header=1)

### 1. Data cleaning

# 1. Choosing bonds in RUB

#available currencies
print('Available currencies:\n', df['FACEUNIT'].value_counts())
df_2 = df[df['FACEUNIT'] == 'RUB'].reset_index(drop = True)

# 2. Selecting relevant columns

#available columns
print('Available columns:',[col for col in df.columns])

#renaming for convenience
df_2.rename(columns = {'SECID':'ID',
                       'FACEVALUE':'NOMINAL_VALUE',
                       'INITIALFACEVALUE':'INITIAL_NOMINAL_VALUE',
                       'FACEUNIT':'NOMINAL_CURRENCY',
                       'COUPONDATE':'COUPONDATE_NEXT',
                       'INN':'EMITENT_INN',
                       'MATDATE':'REDEMPTIONDATE'}, inplace = True)

#selecting relevant columns
df_2 = df_2[['EMITENTNAME', 'NAME','INITIAL_NOMINAL_VALUE',
             'COUPONPERCENT','COUPONVALUE','COUPONFREQUENCY',
             'COUPONDATE_NEXT','COUPONDAYSPASSED','COUPONDAYSREMAIN',
             'COUPONLENGTH','PRICE_RUB','ISSUEDATE','REDEMPTIONDATE',
             'DAYSTOREDEMPTION','HIGH_RISK']]

# 3. Translation to the correct data format

#data types before correction
print('Data types before correction:\n', df_2.dtypes, '\n')

#a function that corrects data types
def type_changer(x):
    if isinstance(x,str):
        x_new_type = float(x.replace(',','.'))
    else:
        x_new_type = x
    return x_new_type

#converting string data to numeric data
for col in ['INITIAL_NOMINAL_VALUE', 'COUPONPERCENT', 'COUPONVALUE',
            'COUPONDAYSPASSED', 'COUPONDAYSREMAIN','COUPONLENGTH', 'PRICE_RUB']:
    df_2[col] = df_2[col].apply(type_changer)

#converting string data to datetime
for col in ['COUPONDATE_NEXT','ISSUEDATE', 'REDEMPTIONDATE']:
    df_2[col] = pd.to_datetime(df_2[col])

#data types after correction
print('Data types after correction:\n',df_2.dtypes)

# 4. Missings processing

#n_samples before missings processing
samples_before = df_2.shape[0]
print('Missings before processing:\n',df_2.isnull().sum(), '\n')

#replacement missings of coupon value in relative
df_2['COUPONPERCENT'].replace(np.nan,0,inplace = True) # missings may mean a coupon-free bond

#replacement missings of coupon value in absolute
condition = (df_2['COUPONPERCENT'].notna()) & (df_2['COUPONVALUE'].isna()) & (df_2['COUPONFREQUENCY'].notna())
df_2['COUPONVALUE'].loc[condition] = ((df_2['COUPONPERCENT']*df_2['INITIAL_NOMINAL_VALUE'])/df_2['COUPONFREQUENCY'])/100 # COUPONVALUE - вычисляем по формуле

#deleting missings in other columns
for col in ['COUPONFREQUENCY','COUPONDATE_NEXT','COUPONDAYSPASSED',
            'COUPONDAYSREMAIN','COUPONLENGTH','PRICE_RUB',
            'ISSUEDATE','REDEMPTIONDATE','DAYSTOREDEMPTION']:

    df_2 = df_2[df_2[col].isnull() == False].reset_index(drop = True)

#n_samples after missing processing
print('Missing after processing:\n',df_2.isnull().sum())

# 5. Adding necessary fields

#bond maturity
df_2['DAYSTOREDEMPTION_SINCE_START'] = df_2['REDEMPTIONDATE'] - df_2['ISSUEDATE']
df_2['DAYSTOREDEMPTION_SINCE_START'] = df_2['DAYSTOREDEMPTION_SINCE_START'].apply(lambda x: x.days)

#today date
df_2['TODAY'] = date.today()
df_2['TODAY'] = pd.to_datetime(df_2['TODAY'])

#date of coupon payment
df_2['COUPONDATE_LAST'] = df_2.apply(lambda x: x['TODAY'] - timedelta(days = x['COUPONDAYSPASSED']), axis = 1)

# 6. Removing errors in calculating coupon payment date

#deleting rows where date of next coupon payment is incorrectly calculated
df_3 = df_2.copy()
df_3['is_mistake_in_coupon_date'] = df_3.apply(lambda x: 0 if x['COUPONDATE_NEXT'] - timedelta(days = x['COUPONDAYSREMAIN']) == x['TODAY'] else 1, axis = 1)
df_3 = df_3[df_3['is_mistake_in_coupon_date'] == 0].reset_index(drop = True)

#deleted rows
samples_after = df_3.shape[0]
print('Deleted samples:', samples_before - samples_after, '\n')

# 7. Exclude bonds with a difference in price and face value >= 20%

df_3 = df_3[df_3['PRICE_RUB']>= (df_3['INITIAL_NOMINAL_VALUE']*0.8)].reset_index(drop = True)

# 8. Forming the final processed dataset with convenient arrangement of columns

df_3 = df_3[['EMITENTNAME','NAME','INITIAL_NOMINAL_VALUE','COUPONPERCENT','COUPONVALUE',
             'COUPONFREQUENCY','COUPONDATE_LAST','COUPONDAYSPASSED','TODAY','COUPONDAYSREMAIN',
             'COUPONDATE_NEXT','COUPONLENGTH','PRICE_RUB','ISSUEDATE','REDEMPTIONDATE',
             'DAYSTOREDEMPTION_SINCE_START','DAYSTOREDEMPTION','HIGH_RISK']]
df_3.head()

### 2. Calculation of full coupon yield

#accumulated coupon income(ACI)
df_3['ACI_period'] = df_3['COUPONDAYSPASSED']/df_3['COUPONLENGTH']
df_3['ACI'] = df_3['ACI_period'] * (((df_3['COUPONPERCENT']/100)/df_3['COUPONFREQUENCY']) * df_3['INITIAL_NOMINAL_VALUE'])

#future coupon income(FCI)
df_3['FCI_period'] = 365/df_3['COUPONLENGTH'] #the calculation period is 365 days from the current date
df_3['FCI'] = df_3['FCI_period'] * (((df_3['COUPONPERCENT']/100)/df_3['COUPONFREQUENCY']) * df_3['INITIAL_NOMINAL_VALUE']) * 0.87

#tax
df_3['TAX'] = 0.87
df_3['TAX'].loc[df_3['INITIAL_NOMINAL_VALUE'] <= df_3['PRICE_RUB']] = 1

#input
N = df_3['INITIAL_NOMINAL_VALUE']
P = df_3['PRICE_RUB']
tax = df_3['TAX']
FCI = df_3['FCI']
ACI = df_3['ACI']

#broker and exchange commissions
broker_com = 0.06/100
exchange_com = 0.0125/100

#calculation
df_3['PROFIT'] = ((N-P)*tax - ACI + 0.87*FCI)/(1+broker_com+exchange_com)
df_3['COSTS'] = P+ACI
df_3['YIELD_FULL'] = (df_3['PROFIT']/df_3['COSTS']) * 100

##### Emissions analysis

#boxplot of full coupon yield
plt.style.use('fivethirtyeight')
plt.figure(figsize = (15,4))
sns.boxplot(df_3['YIELD_FULL'])
plt.title('Emissions analysis, step one')
plt.xticks(np.linspace(-30,30,13))
plt.show()

# Removing emissions with a yield greater lower 1% quantile and higher 99% quantile (emissions)
q_1 = np.quantile(df_3['YIELD_FULL'], 0.01)
q_99 = np.quantile(df_3['YIELD_FULL'], 0.99)
df_4 = df_3[(df_3['YIELD_FULL'] >= q_1) & (df_3['YIELD_FULL'] <= q_99)].reset_index(drop = True)

# boxplot of full coupon yield after removing emissions
plt.figure(figsize = (15,4))
sns.boxplot(df_4['YIELD_FULL'])
plt.title("Emissions analysis, step two")
plt.show()

# Coclusion: Only three emissions left, other bons are within 1- 99% interval

### 3. Ruble bond market

# issuers with the most bonds
df_4_emitents = df_4.groupby('EMITENTNAME').agg({'NAME':'count'}).reset_index().sort_values('NAME', ascending = False)

# average nominal value, coupon, purchase price, maturity and full yield to maturity by market
df_4.describe().loc['mean'][['INITIAL_NOMINAL_VALUE',
                             'COUPONPERCENT',
                             'PRICE_RUB',
                             'DAYSTOREDEMPTION_SINCE_START',
                             'YIELD_FULL']]

#### 3.1 Distributions

#distribution of COUPONPERCENT
plt.style.use('fivethirtyeight')
df_4['COUPONPERCENT'].plot(kind = 'hist',
                           figsize = (15,4),
                           bins = 200,
                           title = 'Distribution of COUPONPERCENT',
                           xlabel = 'COUPONPERCENT')
plt.show()

#distribution of full coupon yield (hist)
df_4['YIELD_FULL'].plot(kind = 'hist',
                   figsize = (15,4),
                   bins = 200,
                   title = 'Distribution of full coupon yield',
                   xlabel = 'YIELD_FULL')
plt.show()

### 3. Full coupon yield clustering

#input
X = df_4['YIELD_FULL'].values.reshape(-1, 1)

#### 3.1 distribution of full coupon yield (density)

#plot params
plt.figure(figsize = (15,4))
true_dens = sns.distplot(df_4['YIELD_FULL'],bins = 200)
plt.title(f'Distplot of YIELD')
plt.show()

# Coclusion: Distribution is close to normal

#### 3.2 Available kernel estimators(K)

#input
X_plot = np.linspace(np.min(X), np.max(X), len(X)).reshape(-1,1)
X_src = np.zeros((1, 1))
kernels = ["gaussian", "tophat", "epanechnikov", "exponential", "linear", "cosine"]

#plot params
fig, ax = plt.subplots(2, 3, sharex=True, sharey=True)
fig.subplots_adjust(left=0.05, right=0.95, hspace=0.05, wspace=0.05)

def format_func(x, loc):
    if x == 0:
        return "0"
    elif x == 1:
        return "h"
    elif x == -1:
        return "-h"
    else:
        return "%ih" % x

#plotting available kernels
for i, kernel in enumerate(kernels):

    axi = ax.ravel()[i]
    kde = KernelDensity(kernel=kernel).fit(X_src)
    log_dens = kde.score_samples(X_plot)

    axi.fill(X_plot, np.exp(log_dens), "-k", fc="#AAAAFF")
    axi.text(-2.6, 0.95, kernel)

    axi.xaxis.set_major_formatter(plt.FuncFormatter(format_func))
    axi.xaxis.set_major_locator(plt.MultipleLocator(1))
    axi.yaxis.set_major_locator(plt.NullLocator())

    axi.set_ylim(0, 1.05)
    axi.set_xlim(-2.9, 2.9)

fig.set_size_inches([12,4])
ax[0, 1].set_title("Available Kernels")
plt.show()

#### 3.3 Kernel estimators of YIELD_FULL (1/nh * sum(K((x-x_i/h))))

#input
X_plot = np.linspace(np.min(X), np.max(X), len(X)).reshape(-1,1)
h = 1.06 * X.std() * (len(X)**(-1/5)) #bandwidth

colors = ["navy", "cornflowerblue", "darkorange",'red','green','blue']
kernels = ["gaussian", "tophat", "epanechnikov", "exponential", "linear", "cosine"]

#plot true density
fig, ax = plt.subplots()
sns.distplot(X, bins = 20, norm_hist = True, color = 'black', label = 'true_dens' )

#plot kernel density
for color, kernel in zip(colors, kernels):

    kde = KernelDensity(kernel=kernel, bandwidth=h).fit(X)
    log_dens = kde.score_samples(X_plot)

    ax.plot(X_plot,np.exp(log_dens),color=color,linestyle="-",label=f'kernel = {kernel}')

fig.set_size_inches([12,4])
ax.set_xlabel('YIELD')
ax.set_ylabel('density')
ax.set_title('Distributions by different kernels')
ax.legend(loc='best', fontsize = 'xx-small')

plt.show()

# Coclusion: Judging by the plot, cosine best estimates the distribution

#### 3.4 Clustering with different kernel estimators. Chossing the best

#input
rows = len(kernels)
cols = 1
axs = plt.figure(figsize=(15,30),
                 constrained_layout=True).subplots(rows,cols,sharex=True,sharey=True)

#plot clustering with each kernel estimator
for ax, kernel in zip(axs,kernels):

    kde = KernelDensity(kernel=kernel, bandwidth=h).fit(X)
    log_dens = kde.score_samples(X_plot)
    min_, max_ = argrelextrema(log_dens, np.less)[0], argrelextrema(log_dens, np.greater)[0]

    print(f'Grouping kernel {kernel}:')

    if len(min_) > 1:
        print(X_plot[min_])
        ax.set_title('kernel=%s' % str(kernel))
        ax.plot(X_plot, log_dens, 'black', X_plot[min_], log_dens[min_], 'ro')

    else:
        print('error')

#### 3.5 Clustering with the best kernel estimator

#Calculation of boundaries of clustering intervals
kde = KernelDensity(kernel='cosine', bandwidth=h).fit(X)
log_dens = kde.score_samples(X_plot)
min_, max_ = argrelextrema(log_dens, np.less)[0], argrelextrema(log_dens, np.greater)[0]

#Plot a graph visualizing clustering
fig, ax = plt.subplots()

ax.plot(X_plot[:min_[0]+1], log_dens[:min_[0]+1])
for i in range(len(min_)-1):
    ax.plot(X_plot[min_[i]:min_[i+1]+1], log_dens[min_[i]:min_[i+1]+1])

ax.plot(X_plot[min_[len(min_)-1]:], log_dens[min_[len(min_)-1]:])
ax.plot(X_plot[min_], log_dens[min_], 'ro')

fig.set_size_inches([15,4])
ax.set_xlabel('YIELD')
ax.set_title('Clustering YIELD_FULL')
ax.xaxis.set_major_locator(ticker.MultipleLocator(10))

plt.show()

#output of boundaries
intervals = [round(i,2) for i in X_plot[min_].reshape(1,-1)[0]]
print('Clustering boundaries:\n',intervals)


import math

from kivy.uix.screenmanager import Screen

class InductorCalculateScreen(Screen):

    def inductor_calculate_turns(self, henrys, diameter, oneturn):
        try:
            henrys = float(henrys)
            diameter = float(diameter) / 10                                 # в формуле сантиметры, во вводе миллиметры
            oneturn = float(oneturn) / 10
            inductor_length = (50 * oneturn ** 2 * henrys + math.sqrt(5) * math.sqrt(500 * oneturn ** 4 * henrys ** 2 +
                               9 * oneturn ** 2 * diameter ** 3 * henrys)) / diameter ** 2

            inductor_turns = inductor_length / oneturn
            inductor_turns_int = round(inductor_turns, 0)
            inductor_length_int = inductor_turns_int * oneturn * 10

            self.ids.inductor_length.text = "{:g} мм".format(inductor_length * 10)
            self.ids.inductor_length_int.text = "{:g} мм".format(inductor_length_int)
            self.ids.inductor_turns.text = "{:g} витка(ов)".format(inductor_turns)
            self.ids.inductor_turns_int.text = "{:g} витка(ов)".format(inductor_turns_int)
        except Exception:
            self.ids.inductor_length.text = "Неверный ввод!"
            self.ids.inductor_length_int.text = "Неверный ввод!"
            self.ids.inductor_turns.text = "Неверный ввод!"
            self.ids.inductor_turns_int.text = "Неверный ввод!"

    def inductor_calculate_henrys(self, turns, diameter, length):
        try:

            turns = float(turns)
            diameter = float(diameter)
            length = float(length)

            formfactor = length / diameter

            induction = 0.0002 * math.pi * diameter * turns ** 2 * (math.log(1 + math.pi/(2 * formfactor)) +
                                                                    1 / (2.3004 + 3.437 * formfactor + 1.7636 *
                                                                         formfactor ** 2 - 0.47 / (0.755 + 1 /
                                                                                formfactor) ** 1.44))
            self.ids.induction.text = "{:g} мкГн".format(induction)
        except Exception:
            self.ids.induction.text = "Неверный ввод!"


import falcon
import json
import jsonschema
from libservice.base.meta_info import MetaResource
from libservice.base.token import Token
from libservice.base.context import ContextInfo

def extract_json_request_body(req):
    """
    Read the body data stream inside http request, transform it to UTF-8 and cache it bace inside input request.
    If the stream is already cached, function returns it.
    This function allows multiple read of the same stream (not allowed by direct call to req.stream.read()).

    :param req: Http Request object
    :return: The request body in utf-8.  If not stream is found in req, return None if no body.
    """
    if 'stream_utf8' not in req.context:
        req.context['stream_utf8'] = '{}'
        if req.content_type in ('application/json', 'application/json; charset=UTF-8'):
            if req.content_length not in (None, 0):
                stream = req.stream.read()
                req.context['stream_utf8'] = stream.decode('utf-8')

    return req.context['stream_utf8']

class API(falcon.API):
    def __init__(self, *args, **kwargs):
        # Initialize falcon
        super().__init__(*args, **kwargs)

class ServiceException(Exception):
    def __init__(self, error_id, message=None, detail=None, custom_data=None, module=None):

        # http code no longer found at raise time
        # Matching is performed in service_exception_handler() function.
        self.error_id = error_id
        self.message = message
        self.detail = detail
        self.custom_data = custom_data
        self.module = module

def service_exception_handler(ex, req, resp, params):
    """
    This static method is called by falcon framework when ServiceException reach it
    """
    error = {
        "http_code":"400 Invalid Request",
        "error_title":"INVALID - REQUEST",
        "error_message":"INVALID REQUEST",
        "service_code": "400"
    }
    raise falcon.HTTPError(error['http_code'], ex.error_id, ex.message, None, None, None, error['service_code'])

def base_exception_handler(ex, req, resp, params):
    """
    This function is called when Exception isn't a ServiceException, so when error 500 is returned.

    :param ex: Exception thrown
    :param req: Request where the exception was thrown
    """

    raise falcon.HTTPError("500 Internal Error", "Internal ERR", None, None, None, None, "1")

class Validate(object):

    def process_request(self,req,resp):

        try:
            req.context['body'] = json.loads(extract_json_request_body(req))
            print(req.context['body'])
        except (ValueError, UnicodeDecodeError) as ex:
            raise ServiceException('INVALID_FORMAT', "Malformed JSON detected.", str(ex),
                                   module=find_module_associated_to_uri(req.path))
        except Exception as ex:
            raise ServiceException('INVALID_FORMAT', "Cannot decode the request body.", str(ex),
                                   module=find_module_associated_to_uri(req.path))

    def process_resource(self,req,resp,resource,params):

        schema = MetaResource._meta_resource_content['data'][resource.__class__.__name__][req.method]

        try:
            jsonschema.validate(req.context['body'], schema)
        except jsonschema.ValidationError as ex:
            str_path = ''
            for i, node in enumerate(ex.schema_path):
                if i != 0:
                    str_path = str_path + '>'
                str_path += node

            str_error = "parameter %r, reason %r" % (str_path, ex.message)
            raise ServiceException('INVALID_REQUEST', "JSON request body doesn't match expected format.", str_error,
                                   module=find_module_associated_to_uri(req.path))
        except Exception as ex:
            raise ServiceException('INVALID_REQUEST', "Cannot validate JSON request body.", str(ex),
                                   module=find_module_associated_to_uri(req.path))

    # def process_response(self,req,resp, resource, req_succeeded):
    #     print("processing response")
    #     # pass

class ValidateAuthToken(object):
    """
    This class is a middleware used to intercept all HTTP requests.
    It adds the result to request.context['auth_token'].
    """

    def process_request(self, request, response):
        """
        This method extracts and decodes the API-Key and Auth-Token headers.
        It's called before a request is routed to the proper resource.

        :param request: HTTP request structure
        :param response: HTTP response structure
        :return:
        """
        ContextInfo.clear_auth_token()

        auth_token = request.get_header('Auth-Token')
        if auth_token is None:
            return

        # Decode the token
        try:
            decoded_token = Token(auth_token)
        except:
            raise ServiceException('INVALID_REQUEST', "Failed to decode 'Auth-Token'.", "Check SSL certificate.",
                                   module=find_module_associated_to_uri(request.path))

        # Make sure it's a Auth-Token
        if not decoded_token.is_type('Auth-Token'):
            raise ServiceException('INVALID_FORMAT', "'Auth-Token' is not valid.", "Not a 'Auth-Token'.",
                                   module=find_module_associated_to_uri(request.path))

        # Validate the format
        if not decoded_token.contains('uuid'):
            raise ServiceException('INVALID_FORMAT', "'Auth-Token' is malformed.", "'uuid' is missing.",
                                   module=find_module_associated_to_uri(request.path))
        if not decoded_token.contains('type'):
            raise ServiceException('INVALID_FORMAT', "'Auth-Token' is malformed.", "'type' is missing.",
                                   module=find_module_associated_to_uri(request.path))

        request.context['auth_token'] = decoded_token

        ContextInfo.set_auth_token(decoded_token)

    def process_resource(self, request, response, resource, params):
        """
        This method validate that the user is authenticated if required by the entry point.
        """
        # meta_resource = MetaResource.get_meta_resource(resource)
        meta_resource = MetaResource._meta_resource_content['data'][resource.__class__.__name__]
        if meta_resource is not None:
            if request.method in meta_resource:
                method_meta_info = meta_resource[request.method]
                # Check authentication
                if 'auth_token_required' in method_meta_info:
                    user_types = method_meta_info['auth_token_required']
                    auth_token = ContextInfo.get_context_data('auth_token')

                    # Authenticated?
                    if auth_token is None:
                        raise ServiceException('UNAUTHORIZED_NOT_AUTHENTICATED',
                                               module=find_module_associated_to_uri(request.path))

                    # Still valid?
                    if auth_token.is_expired():
                        raise ServiceException('UNAUTHORIZED_SESSION_EXPIRED',
                                               module=find_module_associated_to_uri(request.path))

                    # Has access?
                    if auth_token['type'] not in user_types:
                        raise ServiceException('FORBIDDEN', "User doesn't have access to this resource.",
                                               module=find_module_associated_to_uri(request.path))

def find_resource_associated_to_uri(uri):
    """
    Search for the api resource associated with path as defined through falcon framework

    :param req: Http Request object
    :return: string: The resource of the api module associated with path in request object.  If the requested path doesn't match any api, returns None.
    """

    # Search with app router, so we are always up to date.
    resource = falcon_app._router.find(uri)

    if resource is not None:
        return resource
    else:
        return None

def find_module_associated_to_uri(uri):
    """
    Search for the api module associated with path as defined through falcon framework

    :param req: Http Request object
    :return: string: The name of the api module associated with path in request object.  If the requested path doesn't match any api, returns None.
    """
    resource = find_resource_associated_to_uri(uri)

    if resource and resource[0] is not None:
        return resource[0].__module__
    else:
        return None

middleware_list = [Validate(), ValidateAuthToken()]

falcon_app = API(middleware=middleware_list)

# The 'BaseException' handler must be declared BEFORE the other ones
falcon_app.add_error_handler(BaseException, base_exception_handler)

# Always catch ServiceException
falcon_app.add_error_handler(ServiceException, service_exception_handler)


# -*- coding: utf-8 -*-

"""
Functions for managing tasks

"""

import os
import sys
import random
import hashlib

from itertools import product

from .conf import settings
from .exceptions import (
    AbedHashCollissionException,
    AbedExperimentTypeException,
)
from .results.walk import walk_hashes
from .io import error

def cartesian(params):
    return (dict(list(zip(params, x))) for x in product(*params.values()))

def check_size():
    if not sys.maxsize == 9223372036854775807:
        error("Running on a non 64-bit system. This may cause problems with " "hashes.")
        raise SystemExit

def task_hash(task):
    """
    This yields a hash of a list by combining the hashes of all list elements.
    """
    as_tuples = sorted(task.items())
    hasher = hashlib.blake2b(digest_size=8)
    for key, value in as_tuples:
        k = repr(key)
        v = repr(value)
        hasher.update(k.encode())
        hasher.update(v.encode())
    return hasher.hexdigest()

def init_tasks():
    if settings.TYPE in ["ASSESS_GRID", "ASSESS"]:
        task_func = init_tasks_assess_grid
    elif settings.TYPE == "ASSESS_LIST":
        task_func = init_tasks_assess_list
    elif settings.TYPE == "CV_TT":
        task_func = init_tasks_cv_tt
    elif settings.TYPE == "RAW":
        task_func = init_tasks_raw
    else:
        raise ValueError(f"Unknown TYPE value in settings: {settings.TYPE}")

    try:
        return task_func()
    except AbedHashCollissionException:
        error(
            "A hash collision occured. This rarely occurs naturally, so it"
            " is most likely caused by duplicate tasks in the task list. "
            "Abed does not currently support duplicate tasks."
        )
        raise SystemExit
    raise AbedExperimentTypeException

def init_tasks_assess_grid():
    out = {}
    for dset in settings.DATASETS:
        for method in settings.METHODS:
            for prmset in cartesian(settings.PARAMS[method]):
                task = {key: value for key, value in prmset.items()}
                task["dataset"] = dset
                task["method"] = method
                hsh = task_hash(task)
                if hsh in out:
                    raise AbedHashCollissionException
                out[hsh] = task
    return out

def init_tasks_assess_list():
    out = {}
    for dset in settings.DATASETS:
        for method in settings.METHODS:
            for prmset in settings.PARAMS[method]:
                task = {key: value for key, value in prmset.items()}
                task["dataset"] = dset
                task["method"] = method
                hsh = task_hash(task)
                if hsh in out:
                    raise AbedHashCollissionException
                out[hsh] = task
    return out

def init_tasks_cv_tt():
    out = {}
    rng = random.Random(x=settings.CV_BASESEED)
    for train, test in settings.DATASETS:
        seed = rng.randint(0, 2 ** 31 - 1)
        for method in settings.METHODS:
            for prmset in cartesian(settings.PARAMS[method]):
                task = {key: value for key, value in prmset.items()}
                task["train_dataset"] = train
                task["test_dataset"] = test
                task["method"] = method
                task["cv_seed"] = seed
                hsh = task_hash(task)
                if hsh in out:
                    raise AbedHashCollissionException
                out[hsh] = task
    return out

def init_tasks_raw():
    out = {}
    with open(settings.RAW_CMD_FILE, "r") as fid:
        tasks = [x.strip() for x in fid.readlines() if x.strip()]
    for txttask in tasks:
        hsh = hash(txttask)
        hsh %= (sys.maxsize + 1) * 2
        if hsh in out:
            raise AbedHashCollissionException
        out[hsh] = txttask
    return out

def read_tasks():
    with open(settings.TASK_FILE, "r") as fid:
        tasks = [l.strip() for l in fid.readlines() if l.strip()]
    grid = init_tasks()
    out = {}
    for key in tasks:
        out[key] = grid[key]
    return out

def update_tasks(tasks):
    delcnt = 0
    if not os.path.exists(settings.RESULT_DIR):
        return 0
    for hsh in walk_hashes():
        try:
            del tasks[hsh]
            delcnt += 1
        except KeyError:
            pass
    return delcnt

def explain_tasks(all_tasks):
    for task in sorted(all_tasks.keys()):
        if settings.TYPE == "RAW":
            cmd = all_tasks[task]
        else:
            d = {k: v for k, v in all_tasks[task].items()}
            command = settings.COMMANDS[d["method"]]
            d["datadir"] = "{datadir}"
            d["execdir"] = "{execdir}"
            cmd = command.format(**d)
        print("%s : %s" % (task, cmd))

def filter_tasks(all_tasks, query_words=None):
    """Keep only those tasks that match all words in the query list"""
    if query_words is None:
        return all_tasks

    keep = {}
    for task_id in all_tasks:
        task = all_tasks[task_id]
        if all(q in task_id for q in query_words):
            keep[task_id] = task.copy()
            continue

        # Match if all query words match some task value
        all_match = True
        for q in query_words:
            match = any(q in str(v) for v in task.values())
            if not match:
                all_match = False
                break

        if all_match:
            keep[task_id] = task.copy()

    return keep


import pickle
from os import path
from deap import algorithms as deap_algs
from deap import tools

def eval_pop(population, toolbox, eval_func):
    # invalid_ind = [ind for ind in population if not ind.fitness.valid]
    fitnesses = toolbox.map(eval_func, population)
    for ind, fit in zip(population, fitnesses):
        ind.fitness.values = tuple(fit)
    return population

def remove_duplicate_indis(population):
    fitnesses = set()
    new_pop = []
    for i in population:
        if not i.fitness.values in fitnesses:
            new_pop.append(i)
            fitnesses.add(i.fitness.values)
    print('removed: %s / %s' % (len(population) - len(new_pop), len(population)))
    population[:] = new_pop

def create_clean_pop(mu, toolbox):
    population = []
    while len(population) < mu:
        pop_temp = toolbox.population(n=5*mu)
        pop_temp = eval_pop(pop_temp, toolbox, toolbox.evaluate_train)
        for i in pop_temp:
            if i.fitness.values[0] != -10:
                population.append(i)
        print('pop_len', len(population))
    return population

import time
def eaMuPlusLambda(toolbox, mu, lambda_, cxpb, mutpb, ngen, metric_infos_train, manager, replace_invalids=False, folder=None,
                   stats=None, halloffame=None, verbose=__debug__):

    logbook = tools.Logbook()
    logbook.header = ['gen', 'nevals'] + (stats.fields if stats else [])
    # replace_invalids = False
    # ttt = time.perf_counter()
    if replace_invalids:
        population = create_clean_pop(mu, toolbox)
    else:
        population = toolbox.population(n= mu)
        population = eval_pop(population, toolbox, toolbox.evaluate_train)
    # print(time.perf_counter() - ttt)
    # print('mi', metric_infos_train[0].get())
    # print(manager.close())
    # exit()
    if halloffame is not None:
        halloffame.update(population)

    record = stats.compile(population) if stats is not None else {}
    logbook.record(gen=0, nevals=len(population), **record)
    if verbose:
        print(logbook.stream)

    # Begin the generational process
    for gen in range(1, ngen + 1):

        toolbox.change_data(gen)
        for m in metric_infos_train:
            m.set_gen_trial(gen*lambda_)

        if replace_invalids:
            offspring = []
            while len(offspring) <= lambda_:
                new_offspring = deap_algs.varOr(population, toolbox, lambda_, cxpb, mutpb)
                new_offspring = eval_pop(new_offspring, toolbox, toolbox.evaluate_train)
                offspring += new_offspring
                remove_duplicate_indis(offspring)
        else:
            offspring = deap_algs.varOr(population, toolbox, lambda_, cxpb, mutpb)
            offspring = eval_pop(offspring, toolbox, toolbox.evaluate_train)
        population = offspring + population

        # Update the hall of fame with the generated individuals
        if halloffame is not None:
            halloffame.update(population)

        # Select the next generation population
        population[:] = toolbox.select(population, mu)
        if folder is not None:
            fn = path.join(folder, 'pop_{:0>5}.pcl'.format(gen))
            pickle.dump(population, open(fn, 'wb'))
        # Update the statistics with the new population
        record = stats.compile(population) if stats is not None else {}
        logbook.record(gen=gen, nevals=len(population), **record)

        # best = halloffame[0]
        best = toolbox.select_best(population)[0]
        best_fitness_val = toolbox.evaluate_val(best)
        if verbose:
            print(logbook.stream, '## val ##', (', '.join(['{:.6f}']*len(best_fitness_val))).format(*best_fitness_val))
    return population, logbook


import math

def is_different_digit(n1, n2):
    str_n1, str_n2 = str(n1), str(n2)
    for i in range(len(str_n1)):
        if str_n1[i] in str_n2:
            return False
    for i in range(len(str_n2)):
        if str_n2[i] in str_n1:
            return False
    return True

def is_pandigital(n1, n2):
    product = n1 * n2
    str_n1, str_n2, str_n3 = str(n1), str(n2), str(product)
    length = len(str_n1) + len(str_n2) + len(str_n3)
    if length != 9: return False
    if not is_different_digit(n1, n2):
        return False
    if not is_different_digit(n1, product):
        return False
    if not is_different_digit(n2, product):
        return False

    count = 0
    for i in range(1,10):
        if str(i) in str_n1:
            count += 1
        if str(i) in str_n2:
            count += 1
        if str(i) in str_n3:
            count += 1
    return count == 9

result = 0
List_all_product = []
for n1 in range(1, 10 ** 4):
    exp = int(math.log10(n1)) + 1
    b_min, b_max = 4 - exp, 5 - exp
    for n2 in range(10 ** b_min, 10 ** b_max + 1):
        if (is_pandigital(n1, n2)):
            product = n1 * n2
            if product not in List_all_product:
                List_all_product.append(product)
                result += product

print(result)


"""Test class for alphabets module."""

from config.settings.base import DEFAULT_LANGUAGES as LANGUAGES
from tests.BaseTestWithDB import BaseTestWithDB
from utils.alphabets import get_alphabet

class AlphabetsTest(BaseTestWithDB):
    """Test class for alphabets module."""

    def test_all_languages_available(self):
        # Check all available langages return alphabet
        for language_code, language_name in LANGUAGES:
            try:
                get_alphabet(language_code)
            except KeyError:
                error_message = "Alphabet missing for '{}' ({})"
                raise LookupError(error_message.format(language_code, language_name))


import numpy as np
import tensorflow as tf
from tensorflow.contrib.layers.python.layers import initializers

def py_ortho_init(scale):
    def _init(shape):
        u, s, v = np.linalg.svd(np.random.uniform(size=shape))
        return np.cast['float32'](u * scale)

    return _init

class OrthogonalInitializer(object):
    def __init__(self, scale=1.1):
        self.scale = scale

    def __call__(self, shape, dtype=tf.float32, *args, **kwargs):
        result, = tf.py_func(py_ortho_init(self.scale), [shape], [tf.float32])
        result.set_shape(shape)
        return result

class PolicyNetwork():
    def __init__(self, args):
        # Placeholder for data
        self.inputs = tf.placeholder(
            tf.float32, [args.batch_size, args.state_dim], name="inputs")
        self.targets = tf.placeholder(
            tf.float32, [args.batch_size, args.action_dim], name="targets")
        self.learning_rate = tf.Variable(
            0.0, trainable=False, name="learning_rate")

        # Create the computational graph
        self._create_mlp(args)
        if args.gru_input_dim > 0:
            self._create_gru(args)
        self._create_optimizer(args)

    def _create_mlp(self, args):

        # Create fully connected network of desired size
        W = tf.get_variable("mlp_policy/hidden_0/W",
                            [args.state_dim, args.mlp_size[0]], initializer=initializers.xavier_initializer())
        b = tf.get_variable("mlp_policy/hidden_0/b",
                            [args.mlp_size[0]])
        output = args.mlp_activation(tf.nn.xw_plus_b(self.inputs, W, b))

        # Hidden layers
        for i in xrange(1, len(args.mlp_size)):
            W = tf.get_variable("mlp_policy/hidden_" + str(i) + "/W",
                                [args.mlp_size[i - 1], args.mlp_size[i]], initializer=initializers.xavier_initializer())
            b = tf.get_variable("mlp_policy/hidden_" + str(i) + "/b",
                                [args.mlp_size[i]])
            output = args.mlp_activation(tf.nn.xw_plus_b(output, W, b))

        if args.gru_input_dim > 0:
            # Output/input to GRU
            W = tf.get_variable("mlp_policy/output/W",
                                [args.mlp_size[-1], args.gru_input_dim], initializer=initializers.xavier_initializer())
            b = tf.get_variable("mlp_policy/output/b",
                                [args.gru_input_dim])

            self.gru_input = args.mlp_activation(tf.nn.xw_plus_b(output, W, b))
        else:
            # Map to actions
            W = tf.get_variable("mlp_policy/mean_network/output_flat/W",
                                [args.mlp_size[-1], args.action_dim], initializer=initializers.xavier_initializer())
            b = tf.get_variable("mlp_policy/mean_network/output_flat/b",
                                [args.action_dim])
            self.a_mean = tf.nn.xw_plus_b(output, W, b)

            # Initialize logstd
            self.a_logstd = tf.Variable(np.zeros(
                args.action_dim), name="mlp_policy/output_log_std/param", dtype=tf.float32)

    def _create_gru(self, args):

        # Weights for the initial hidden state
        self.hprev = tf.get_variable("gru_policy/mean_network/gru/h0",
                                     [args.batch_size, args.gru_size], initializer=tf.zeros_initializer, trainable=False)
        # Weights for the reset gate
        W_xr = tf.get_variable("gru_policy/mean_network/gru/W_xr",
                               [args.gru_input_dim, args.gru_size], initializer=initializers.xavier_initializer())
        W_hr = tf.get_variable("gru_policy/mean_network/gru/W_hr",
                               [args.gru_size, args.gru_size], initializer=OrthogonalInitializer())
        b_r = tf.get_variable("gru_policy/mean_network/gru/b_r",
                              [args.gru_size], initializer=tf.zeros_initializer)
        # Weights for the update gate
        W_xu = tf.get_variable("gru_policy/mean_network/gru/W_xu",
                               [args.gru_input_dim, args.gru_size], initializer=initializers.xavier_initializer())
        W_hu = tf.get_variable("gru_policy/mean_network/gru/W_hu",
                               [args.gru_size, args.gru_size], initializer=OrthogonalInitializer())
        b_u = tf.get_variable("gru_policy/mean_network/gru/b_u",
                              [args.gru_size], initializer=tf.zeros_initializer)
        # Weights for the cell gate
        W_xc = tf.get_variable("gru_policy/mean_network/gru/W_xc",
                               [args.gru_input_dim, args.gru_size], initializer=initializers.xavier_initializer())
        W_hc = tf.get_variable("gru_policy/mean_network/gru/W_hc",
                               [args.gru_size, args.gru_size], initializer=OrthogonalInitializer())
        b_c = tf.get_variable("gru_policy/mean_network/gru/b_c",
                              [args.gru_size], initializer=tf.zeros_initializer)

        # Concatenate matrices
        self.W_x_ruc = tf.concat(1, [W_xr, W_xu, W_xc])
        self.W_h_ruc = tf.concat(1, [W_hr, W_hu, W_hc])
        self.b_ruc = tf.concat(0, [b_r, b_u, b_c])

        # Compute output from GRU layer
        xb_ruc = tf.matmul(self.gru_input, self.W_x_ruc) + \
            tf.reshape(self.b_ruc, (1, -1))
        h_ruc = tf.matmul(self.hprev, self.W_h_ruc)
        self.xb_r, self.xb_u, self.xb_c = tf.split(
            split_dim=1, num_split=3, value=xb_ruc)
        self.h_r, self.h_u, self.h_c = tf.split(
            split_dim=1, num_split=3, value=h_ruc)
        self.r = tf.nn.sigmoid(self.xb_r + self.h_r)
        self.u = tf.nn.sigmoid(self.xb_u + self.h_u)
        self.c = tf.nn.tanh(self.xb_c + self.r * self.h_c)
        self.h = (1 - self.u) * self.hprev + self.u * self.c

        # Map to actions
        W = tf.get_variable("gru_policy/mean_network/output_flat/W",
                            [args.gru_size, args.action_dim], initializer=initializers.xavier_initializer())
        b = tf.get_variable("gru_policy/mean_network/output_flat/b",
                            [args.action_dim])
        self.a_mean = tf.nn.xw_plus_b(self.h, W, b)

        # Initialize logstd
        self.a_logstd = tf.Variable(np.zeros(
            args.action_dim), name="gru_policy/output_log_std/param", dtype=tf.float32)

    def _create_optimizer(self, args):
        # Find negagtive log-likelihood of true actions
        std_a = tf.exp(self.a_logstd)
        pl_1 = 0.5 * tf.to_float(args.action_dim) * np.log(2. * np.pi)
        pl_2 = tf.to_float(args.action_dim) * tf.reduce_sum(tf.log(std_a))
        pl_3 = 0.5 * \
            tf.reduce_mean(tf.reduce_sum(
                tf.square((self.targets - self.a_mean) / std_a), 1))
        policy_loss = pl_1 + pl_2 + pl_3

        # Find overall loss
        self.cost = policy_loss
        self.summary_policy = tf.scalar_summary(
            "Policy loss", tf.reduce_mean(policy_loss))

        # Perform parameter update
        tvars = tf.trainable_variables()
        grads, _ = tf.clip_by_global_norm(
            tf.gradients(self.cost, tvars), args.grad_clip)
        optimizer = tf.train.AdamOptimizer(self.learning_rate)
        self.train = optimizer.apply_gradients(zip(grads, tvars))


import torch
import torchvision
import torchvision.transforms as transforms

class Data:
    """This class contains the attributes that all datasets have in common.
    All datasets will inherit from this class.

    ...
    Attributes
    ----------
    train_loader : PyTorch Dataloader
        The dataloader for the training set
    train_loader : PyTorch Dataloader
        The dataloader for the testing set
    data_x_dim : int
        The size of the x-dimension for each image in the dataset
    data_y_dim : int
        The size of the y-dimension for each image in the dataset
    """

    def __init__(self):
        self.train_loader = None
        self.test_loader = None
        self.data_x_dim = None
        self.data_y_dim = None

#     @property
#     @abstractmethod
#     def train_loader(self):
#         raise NotImplementedError

#     @property
#     def test_loader(self):
#         raise NotImplementedError

#     @property
#     def data_x_dim(self):
#         raise NotImplementedError

#     @property
#     def data_y_dim(self):
#         raise NotImplementedError

class MNIST(Data):
    """The MNIST Dataset (Handwritten Digits)

    ...
    Attributes
    ----------
    train_loader : PyTorch Dataloader
        The dataloader for the training set
    train_loader : PyTorch Dataloader
        The dataloader for the testing set
    data_x_dim : int
        The size of the x-dimension for each image in the dataset
    data_y_dim : int
        The size of the y-dimension for each image in the dataset
    train_batch_size : int
        The number of training examples per batch
    test_batch_size : int
        The number of testing examples per batch
    dataloaders : dict
        A dictionary that contains the 2 dataloaders. The keys are
        "train" and "test"
    """

    def __init__(self, train_batch=64, test_batch=64):
        self.train_batch_size = train_batch
        self.test_batch_size = test_batch

        self.train_loader = torch.utils.data.DataLoader(
                                torchvision.datasets.MNIST('./data',
                                   train=True,
                                   download=True,
                                   transform=torchvision.transforms.Compose([
                                       torchvision.transforms.ToTensor(),
                                       torchvision.transforms.Normalize((0.1307,), (0.3081,))])),
                                batch_size=self.train_batch_size, shuffle=True)

        dataset = torch.utils.data.Subset(self.train_loader.dataset, range(0, 4000))

        self.train_loader = torch.utils.data.DataLoader(dataset, batch_size=self.train_batch_size,
                                                        shuffle=True)

        self.test_loader = torch.utils.data.DataLoader(
                        torchvision.datasets.MNIST('./data',
                           train=False,
                           download=True,
                           transform=torchvision.transforms.Compose([
                               torchvision.transforms.ToTensor(),
                               torchvision.transforms.Normalize((0.1307,), (0.3081,))])),
                        batch_size=self.test_batch_size, shuffle=False)

        # Only use a subset of the MNIST dataset for MLP
        self.dataloaders = {'train': self.train_loader,
                            'test': self.test_loader}

        self.data_x_dim = self.train_loader.dataset[0][0].shape[1]
        self.data_y_dim = self.train_loader.dataset[0][0].shape[2]

#         @property
#         def train_batch_size(self):
#             return self.train_batch_size

#         @property
#         def test_batch_size(self):
#             return self.test_batch_size

#         @property
#         def test_loader(self):
#             return self.test_loader

#         @property
#         def data_x_dim(self):
#             return self.train_loader.dataset[0][0].shape[1]

#         @property
#         def data_y_dim(self):
#             return self.train_loader.dataset[0][0].shape[2]


"""Test machine map generator.

Unfortunately, due to the highly visual nature of this utility, these unit tests
are limited to simply checking for crashes and that outputs are of the correct
format and dimensions.
"""

import pytest

from mock import Mock

from tempfile import mkstemp

import os

from PIL import Image

from spinner.scripts.machine_map import main

@pytest.yield_fixture
def png_file():
	_, filename = mkstemp(suffix="map.png")

	yield filename

	os.remove(filename)

@pytest.yield_fixture
def pdf_file():
	_, filename = mkstemp(suffix="map.pdf")

	yield filename

	os.remove(filename)

def test_png_output(png_file):
	"""
	Run the utility and make sure it generates a PNG of the correct size (doesn't
	verify its contents; this is assumed to be correct).
	"""

	# Shouldn't crash
	assert main("{} 123 234 -n 3".format(png_file).split()) == 0

	# Should be able to read the file with PIL
	im = Image.open(png_file)
	assert im.format == "PNG"
	assert im.size == (123, 234)

def test_pdf_output(pdf_file):
	"""
	Run the utility and make sure it generates a PDF. Due to difficulties in
	easily working with PDF files, this simply checks that the file starts with a
	PDF magic byte.
	"""

	# Shouldn't crash
	assert main("{} 123 234 -n 3".format(pdf_file).split()) == 0

	# Should be a valid PDF file
	with open(pdf_file, "rb") as f:
		assert f.read(4) == b"%PDF"


# library to create multilayer systems
from copy import deepcopy
import numpy as np
from scipy.sparse import csc_matrix as csc
from scipy.sparse import bmat

def bilayer_aa(h,t = 0.1):
  """ Creates a bilayer from a honeycomb ribbon"""
  nlayers = 2 # number of layers
  g = h.geometry # get the geometry
  go = deepcopy(g) # copy the geometry
  go.x = []
  go.y = []
  go.z = []
  if g.name == "honeycomb_armchair_ribbon": dx ,dy = 1. ,0.
  if g.name == "honeycomb_zigzag_ribbon": dx ,dy = 0. ,-1.
  for (xi,yi) in zip(g.x,g.y):  # modify the geometry
    go.x.append(xi)
    go.x.append(xi+dx)
    go.y.append(yi)
    go.y.append(yi+dy)
    go.z.append(1.)
    go.z.append(-1.)
  go.x,go.y,go.z = np.array(go.x),np.array(go.y),np.array(go.z) # put arrays
  # now modify the hamiltonian
  ho = deepcopy(h)
  n = len(ho.intra) # dimension
  intra = [[0. for i in range(2*n)] for j in range(2*n)]
  inter = [[0. for i in range(2*n)] for j in range(2*n)]
  norb = n # number of orbitals
  # get the atoms which hop according to monolayer type...
  if h.has_spin:
    norb = norb/2
    tl = [] # interlayer pairs
    x, y, z = go.x, go.y, go.z
    for i in range(len(x)): # loop over atoms
      for j in range(len(x)): # loop over atoms
        if 1.9 < np.abs(z[i]-z[j]) < 2.1: # if in contiguous layers
          dd = (x[i]-x[j])**2 + (y[i] - y[j])**2 + (z[i] - z[j])**2
          if 3.9<dd<4.1:
            tl.append([i,j])
    for i in range(norb):
      for j in range(norb):  # assign interlayer hopping
        for s in range(2):
         for l in range(nlayers):
          intra[2*nlayers*i+s+2*l][2*nlayers*j+s+2*l] = h.intra[2*i+s,2*j+s]
          inter[2*nlayers*i+s+2*l][2*nlayers*j+s+2*l] = h.inter[2*i+s,2*j+s]
    # now put the interlayer hopping
    for p in tl:
      for s in range(2): # loop over spin
        intra[2*p[0]+s][2*p[1]+s] = t
        intra[2*p[1]+s][2*p[0]+s] = np.conjugate(t)
  else: raise # not implemented...
  if h.has_eh: raise # not implemented ....
  ho.intra = np.matrix(intra)
  ho.inter = np.matrix(inter)
  ho.geometry = go
  return ho

def add_electric_field(h,e = 0.0):
  """Adds electric field to the system"""
  if h.has_spin: # if has spin
    z = h.geometry.z # z coordinates
    for i in range(len(z)):
      h.intra[2*i,2*i] += z[i]*e
      h.intra[2*i+1,2*i+1] += z[i]*e

def multilayered_hamiltonian(h,dr=np.array([0.,0.,0.])):
  """ Creates a multilayered hamiltonian by adding several layers """

def build_honeycomb_bilayer(h,t=0.1):
  """Builds a multilayer based on a hamiltonian, it is assumed that
  there are no new intercell hoppings in the multilayer"""
  g = h.geometry  # save geometry
  ho = deepcopy(h) # copy the hamiltonian
  go = deepcopy(g) # copy the geometry
  def mono2bi(m):
    """Increase the size of the matrices"""
    return bmat([[csc(m),None],[None,csc(m)]]).todense()
  # modify the geometry
  x,y,z = np.array(g.x), np.array(g.y) ,np.array(g.z) # store positions
  go.x = np.concatenate([x,x+1.])  # move one unit to the right
  go.y = np.concatenate([y,y])  # leave invariant
  go.z = np.concatenate([z-1.0,z+1.0])  # separate two units
  go.xyz2r() # update r coordinates
  ho.geometry = go # update geometry
  # modify the hamiltonian
  ho.intra = mono2bi(h.intra)  # increase intracell matrix
  for i in range(len(go.r)): # add the interlayer
    for j in range(len(go.r)): # add the interlayer
      rij = go.r[i] - go.r[j] # distance between atoms
      dz = go.z[i] - go.z[j] # vertical distance
      if (3.9<rij.dot(rij)<4.1) and (3.9<(dz*dz)<4.1): # check if connect
        if ho.has_spin: # spin polarized
          ho.intra[2*i,2*j] = t
          ho.intra[2*i+1,2*j+1] = t
        else:  # spin unpolarized
          ho.intra[i,j] = t
  if h.dimensionality==2: # two dimensional system
    ho.tx = mono2bi(h.tx)  # increase intracell matrix
    ho.ty = mono2bi(h.ty)  # increase intracell matrix
    ho.txy = mono2bi(h.txy)  # increase intracell matrix
    ho.txmy = mono2bi(h.txmy)  # increase intracell matrix
  else:
    raise
  return ho

def build_honeycomb_trilayer(h,t=0.1):
  """Builds a multilayer based on a hamiltonian, it is assumed that
  there are no new intercell hoppings in the multilayer"""
  g = h.geometry  # save geometry
  ho = deepcopy(h) # copy the hamiltonian
  go = deepcopy(g) # copy the geometry
  def mono2tri(m):
    """Increase the size of the matrices"""
    mo = [[None for i in range(3)] for j in range(3)]
    for i in range(3):
      mo[i][i] = csc(m)
    return bmat(mo).todense()
  # modify the geometry
  x,y,z = np.array(g.x), np.array(g.y) ,np.array(g.z) # store positions
  go.x = np.concatenate([x-1.,x,x+1.])  # move one unit to the right
  go.y = np.concatenate([y,y,y])  # leave invariant
  go.z = np.concatenate([z-2.0,z,z+2.0])  # separate two units
  go.xyz2r() # update r coordinates
  ho.geometry = go # update geometry
  # modify the hamiltonian
  ho.intra = mono2tri(h.intra)  # increase intracell matrix
  for i in range(len(go.r)): # add the interlayer
    for j in range(len(go.r)): # add the interlayer
      rij = go.r[i] - go.r[j] # distance between atoms
      dz = go.z[i] - go.z[j] # vertical distance
      if (3.9<rij.dot(rij)<4.1) and (3.9<(dz*dz)<4.1): # check if connect
        if ho.has_spin: # spin polarized
          ho.intra[2*i,2*j] = t
          ho.intra[2*i+1,2*j+1] = t
        else:  # spin unpolarized
          ho.intra[i,j] = t
  if h.dimensionality==2: # two dimensional system
    ho.tx = mono2tri(h.tx)  # increase intracell matrix
    ho.ty = mono2tri(h.ty)  # increase intracell matrix
    ho.txy = mono2tri(h.txy)  # increase intracell matrix
    ho.txmy = mono2tri(h.txmy)  # increase intracell matrix
  else:
    raise
  return ho


class model1:
	def __getattr__(self,x):
		getter = 'var_'+x
		if getter in self.__dict__:
			return self.__dict__[getter]()
		else:
			return self.__dict__[x]
	def chain(self,other):
		for k,v in other.__dict__.items():
			self.__dict__[k]=v
		return self

if __name__=="__main__":
	x = model1()
	x.var_a = lambda: 20
	x.var_b = lambda: x.a+1
	y = model1().chain(x)
	y.var_c = lambda: y.b*2
	print(y.c)


import contextlib
import random
import string
import sys

import sqlalchemy
import sqlalchemy.exc
import sqlalchemy.orm
import sqlalchemy.orm.dynamic
import sqlalchemy.orm.query
from sqlalchemy import Column, Index, Integer, Table, and_
from sqlalchemy.orm import defer, relationship

from .. import config, core, temporary_halolist
from ..config import DOUBLE_PRECISION
from .one_hop import HopStrategy

class MultiHopStrategy(HopStrategy):
    """An extension of the HopStrategy class that takes multiple hops across
    HaloLinks, up to a specified maximum, before finding the target halo."""

    halo_old = sqlalchemy.orm.aliased(core.halo.SimulationObjectBase, name="halo_old")
    halo_new = sqlalchemy.orm.aliased(core.halo.SimulationObjectBase, name="halo_new")
    timestep_old = sqlalchemy.orm.aliased(core.timestep.TimeStep, name="timestep_old")
    timestep_new = sqlalchemy.orm.aliased(core.timestep.TimeStep, name="timestep_new")

    def __init__(self, halo_from, nhops_max=None, directed=None, target=None,
                 order_by=None, combine_routes=True, min_aggregated_weight=0.0,
                 min_onehop_weight=0.0, min_onehop_reverse_weight=None,
                 include_startpoint=False, one_simulation=None):
        """Construct a strategy for finding Halos via multiple "hops" along HaloLinks

        :param halo_from:   The halo to start hopping from
        :type halo_from:    core.SimulationObjectBase

        :param nhops_max:   The maximum number of hops to take

        :param directed:    The direction in which to step, which can be
              'backwards' - only take hop if it's backwards in time
              'forwards'  - only take hop if it's forwards in time
              'across'    - only take hop if it's to the same time
              None        - take all available hops

              Note that specifying a direction causes a slight slow-down in each hop because the hop results have
              to be joined to the TimeStep object to work out the direction. But the overall query times are
              often much shorter because the pruning cuts down the number of paths to be explored.

        :param target:      Only return results in target, which can be a TimeStep or Simulation object.
            If None, return all results. Intermediate hops are not restricted by target, only the results returned.

        :param one_simulation:  Only follow links to objects in the same simulation, if True. If False, follow
            links to any simulation. By default this is None, and the behaviour is dictated by directed: if
            directed is 'backwards' or 'forwards' one_simulation is True, whereas for other values it is False.

        :param order_by:    Return results in specified order; see HopStrategy for more information

        :param combine_routes:  If True (default), discard multiple routes to the same halo and keep only the
            strongest route. When False, one result is returned for each possible route,
            even if it ultimately reaches the same halo. This can result in a significant
            slow-down.

        :param min_aggregated_weight: Threshold cumulative weight below which routes will be discarded.
              The cumulative weight is defined as the product of the individual hop weights.
              Small values indicate that a small fraction of mass is in common.

        :param min_onehop_weight:     Threshold individual weight below which a route will be discarded.

        :param min_onehop_reverse_weight: Threshold individual weight for the link pointing in the opposite
                                          direction to that being followed, or None for no restriction.
                                          Note that this requires an extra join (if not None) and if there
                                          is no reverse link at all, the result will be dropped.

        :param include_startpoint:    Return the starting halo in the results (default False)
        """
        super().__init__(halo_from, target, order_by)
        if nhops_max is None:
            nhops_max = config.num_multihops_max_default
        self.nhops_max = nhops_max
        self.directed = directed
        self._min_aggregated_weight = min_aggregated_weight
        self._min_onehop_weight = min_onehop_weight
        self._min_onehop_reverse_weight = min_onehop_reverse_weight
        self._include_startpoint = include_startpoint
        if one_simulation is None:
            one_simulation = directed=='backwards' or directed=='forwards'
        self._one_simulation = one_simulation
        self._connection = self.session.connection()
        self._combine_routes = combine_routes
        self._debug_output = False # set to True to see information about discovered links as hops progress

    def temp_table(self):
        """Execute the strategy and return results as a temp_table (see temporary_halolist module)"""
        if self._all is None:
            return self._temp_table_without_leaving_sql()
        else:
            return temporary_halolist.temporary_halolist_table(self.session, [x.halo_to_id for x in self._all])

    def _temp_table_without_leaving_sql(self):
        tt = self._manage_temp_table()
        tt.__enter__()
        try:
            self._generate_multihop_results()
        except:
            tt.__exit__(*sys.exc_info())
            raise

        thl = temporary_halolist.temporary_halolist_table(self.session,
                                                          self._order_query(self._generate_query(halo_ids_only=True)),
                                                          callback=lambda: tt.__exit__(None, None, None)
                                                          )
        return thl

    def _generate_multihop_results(self):
        self._seed_temp_table()
        self._make_hops()

    def _execute_query(self):
        with self._manage_temp_table():
            self._generate_multihop_results()
            try:
                results = self._order_query(self._generate_query(halo_ids_only=False)).all()
            except sqlalchemy.exc.ResourceClosedError:
                results = []

        self._all = results

    def _supplement_halolink_query_with_filter(self, query, table=None):

        if table is None:
            table = core.halo_data.HaloLink.__table__

        if self._needs_join_for_link_filter():
            query = query. \
                join(self.halo_old, table.c.halo_from_id == self.halo_old.id). \
                join(self.halo_new, table.c.halo_to_id == self.halo_new.id). \
                join(self.timestep_old, self.halo_old.timestep). \
                join(self.timestep_new, self.halo_new.timestep)

        filter = self._generate_link_filter(self.timestep_old, self.timestep_new, table)
        query = query.filter(filter)

        return query

    def _needs_join_for_link_filter(self):
        return self.directed is not None

    def _generate_link_filter(self, timestep_old, timestep_new, table):

        recursion_filter = table.c.weight > self._min_aggregated_weight

        if self.directed is not None:
            directed = self.directed.lower()
            if self._one_simulation:
                recursion_filter &= (timestep_new.simulation_id == timestep_old.simulation_id)
            if directed == 'backwards':
                recursion_filter &= (timestep_new.time_gyr < timestep_old.time_gyr*(1.0-config.max_relative_time_difference))
            elif directed == 'forwards':
                recursion_filter &= (timestep_new.time_gyr > timestep_old.time_gyr*(1.0+config.max_relative_time_difference))
            elif directed == 'across':
                existing_timestep_ids = self.session.query(core.SimulationObjectBase.timestep_id).\
                    select_from(self._link_orm_class).join(self._link_orm_class.halo_to).distinct()
                recursion_filter &= ~timestep_new.id.in_(existing_timestep_ids)
                recursion_filter &= sqlalchemy.func.abs(timestep_new.time_gyr - timestep_old.time_gyr) \
                                    < config.max_relative_time_difference * timestep_old.time_gyr
            else:
                raise ValueError("Unknown direction %r" % directed)

        return recursion_filter

    def _delete_temp_table(self):
        self._table_index.drop(bind=self._connection)
        self._table.drop(checkfirst=True, bind=self._connection)
        self._prelim_table.drop(checkfirst=True, bind=self._connection)
        # self._index.drop(bind=self.connection)
        core.Base.metadata.remove(self._table)
        core.Base.metadata.remove(self._prelim_table)

    def _create_temp_table(self):
        rstr = ''.join(random.choice(string.ascii_lowercase) for _ in range(4))

        # The intent of the following two tables is that they are temporary. With SQLite, it is
        # essential that they are implemented literally as TEMPORARY tables as otherwise the performance
        # is hugely degraded. However, MySQL places two crippling limitations on TEMPORARY tables:
        #  1) No foreign keys allowed referring to columns in the permanent database
        #  2) Cannot join a TEMPORARY table to itself
        #
        # Restriction (1) can be evaded by not declaring the foreign key in the schema, and then
        # providing foreign_keys information in _construct_orm_class. However restriction (2) is
        # completely debilitating. Luckily the performance in MySQL is fine even if we don't
        # declare these tables as TEMPORARY, so we simply switch off the prefix.
        #
        # There is a strange third issue with MySQL which is that, if we declare the foreign key
        # when creating a table, the connection hangs. This seems to be because of a deadlock;
        # other open connections prevent creating the association to the existing tables (the
        # MySQL 'metadata lock'). So, even though MySQL will NOT be using temporary tables,
        # we still don't declare the foreign key dependence.

        dialect = self._connection.dialect.dialect_description.split("+")[0].lower()
        if dialect == 'mysql':
            prefixes = []
        else:
            prefixes = ['TEMPORARY']

        multi_hop_link_table = Table(
            'multihoplink_final_' + rstr,
            core.Base.metadata,
            Column('id', Integer, primary_key=True),
            Column('source_id', Integer),
            # Foreign keys below NOT declared at schema-level, see note above re MySQL
            Column('halo_from_id', Integer),
            Column('halo_to_id', Integer),
            Column('weight', DOUBLE_PRECISION),
            Column('nhops', Integer),
            prefixes = prefixes
        )

        multi_hop_link_prelim_table = Table(
            'multihoplink_prelim_' + rstr,
            core.Base.metadata,
            Column('id', Integer, primary_key=True),
            Column('source_id', Integer),
            # Foreign keys below NOT declared at schema-level, see note above re MySQL
            Column('halo_from_id', Integer),
            Column('halo_to_id', Integer),
            Column('weight', DOUBLE_PRECISION),
            Column('nhops', Integer),
            prefixes = prefixes
        )

        self._table_index = Index('temp.source_id_index_' + rstr, multi_hop_link_table.c.source_id, multi_hop_link_table.c.nhops)

        self._table = multi_hop_link_table
        self._prelim_table = multi_hop_link_prelim_table

        self._table.create(checkfirst=True, bind=self._connection)
        self._prelim_table.create(checkfirst=True, bind=self._connection)

    @contextlib.contextmanager
    def _manage_temp_table(self):
        self._create_temp_table()
        self._link_orm_class = self._construct_orm_class()
        yield
        self._delete_temp_table()

    def _seed_temp_table(self):
        insert_statement = self._table.insert().values(halo_from_id=self.halo_from.id, halo_to_id=self.halo_from.id,
                                    weight=1.0, nhops=0, source_id=0)

        self._connection.execute(insert_statement)

    def _make_hops(self):
        for i in range(0, self.nhops_max):
            self._nhops_taken = i
            generated_count = self._generate_next_level_prelim_links(i)
            if generated_count != 0:
                filtered_count = self._filter_prelim_links_into_final()
            else:
                filtered_count = 0

            if self._hopping_finished(filtered_count):
                break

    def _hopping_finished(self, filtered_count):
        return filtered_count==0

    def _generate_next_level_prelim_links(self, from_nhops=0):

        new_weight = self._table.c.weight * core.halo_data.HaloLink.weight

        recursion_query = \
            self.session.query(
                core.halo_data.HaloLink.halo_from_id.label('halo_from_id'),
                core.halo_data.HaloLink.halo_to_id.label("halo_to_id"),
                new_weight.label('new_weight'),
                (self._table.c.nhops + sqlalchemy.literal(1)).label("nhops"),
                self._table.c.source_id.label('source_id')). \
                select_from(self._table). \
                outerjoin(core.halo_data.HaloLink, and_(self._table.c.nhops == from_nhops,
                                                           self._table.c.halo_to_id == core.halo_data.HaloLink.halo_from_id)). \
                filter(core.halo_data.HaloLink.weight > self._min_onehop_weight
                       )

        insert = self._prelim_table.insert().from_select(
            ['halo_from_id', 'halo_to_id', 'weight', 'nhops', 'source_id'],
            recursion_query)

        num_inserted = self._connection.execute(insert).rowcount

        if self._combine_routes:
            # Ideally, before self._prelim_table.insert(), one would adapt recursion_query to return the argmax of
            # new_weight, grouped by halo_to_id and source_id. That could have been achieved using:
            #
            #   from ..util.sql_argmax import sql_argmax
            #   recursion_query = sql_argmax(recursion_query, "new_weight",
            #                               ["halo_to_id", "source_id"])
            #
            # However, this turns out to be very slow and there are no obvious ways to optimise it. It's essentially
            # using a huge CTE/subquery to replace a native argmax facility in SQL. Originally, with sqlite, we simply did:
            #
            #   recursion_query = recursion_query.group_by(core.halo_data.HaloLink.halo_to_id, self._table.c.source_id)
            #
            # It is actually unclear to me now why this ever worked, but anyway it doesn't work in MySQL. The balance of
            # remaining correct without killing performance is to actually insert all the rows into prelim_table, and
            # then delete the ones which are not wanted afterwards. This is presumably faster because the temp table
            # has relevant indices.
            from ..util.sql_argmax import delete_non_maximal_rows

            deleted_count = delete_non_maximal_rows(self._connection, self._prelim_table,
                                                    self._prelim_table.c.weight,
                                                    [self._prelim_table.c.halo_to_id, self._prelim_table.c.source_id])
            num_inserted-=deleted_count

        return num_inserted

    def _debug_print_links(self, tab):
        halo_from = sqlalchemy.orm.aliased(core.halo.Halo)
        halo_to = sqlalchemy.orm.aliased(core.halo.Halo)
        halo_source = sqlalchemy.orm.aliased(core.halo.Halo)
        q = self.session.query(halo_from, halo_to, tab.c.source_id, tab.c.weight, tab.c.nhops).\
            select_from(tab).\
            join(halo_from, tab.c.halo_from_id == halo_from.id).\
            join(halo_to, tab.c.halo_to_id == halo_to.id)
        for row in q.all():
            print(f"[s{row[2]} i{row[4]}] {row[0].path} -> {row[1].path} w={row[3]:.2f}")

    def _filter_prelim_links_into_final(self):
        if self._debug_output:
            print()
            print(f"[{self._nhops_taken}] Preliminary links:")
            self._debug_print_links(self._prelim_table)

        q = self.session.query(self._prelim_table.c.halo_from_id,
                               self._prelim_table.c.halo_to_id,
                               self._prelim_table.c.weight,
                               self._prelim_table.c.nhops,
                               self._prelim_table.c.source_id)

        q = self._supplement_halolink_query_with_reverse_hop_filter(q, self._prelim_table)
        q = self._supplement_halolink_query_with_filter(q, self._prelim_table)

        added_rows = self._connection.execute(
            self._table.insert().from_select(['halo_from_id', 'halo_to_id', 'weight', 'nhops', 'source_id'],
                                             q)).rowcount

        if self._debug_output:
            print(f"[{self._nhops_taken}] Accepted links:")
            self._debug_print_links(self._table)

        self._connection.execute(self._prelim_table.delete())
        return added_rows

    def _supplement_halolink_query_with_reverse_hop_filter(self, query, table=None):
        if self._min_onehop_reverse_weight is None:
            return query

        if table is None:
            table = core.halo_data.HaloLink.__table__

        hl_alias = sqlalchemy.orm.aliased(core.HaloLink)

        query = query. \
                join(hl_alias,
                     and_(hl_alias.halo_from_id==table.c.halo_to_id,
                     hl_alias.halo_to_id==table.c.halo_from_id)).\
                filter(hl_alias.weight>self._min_onehop_reverse_weight)

        return query

    def _construct_orm_class(self):
        rstr = ''.join(random.choice(string.ascii_lowercase) for _ in range(4))
        class_name = "MultiHopHaloLink_"+rstr
        class_base = (core.Base,)
        class_attrs = {"__table__": self._table,
                       "halo_from": relationship(core.halo.SimulationObjectBase, primaryjoin=self._table.c.halo_from_id == core.halo.SimulationObjectBase.id,
                                                 foreign_keys = [self._table.c.halo_from_id]),
                       "halo_to"  : relationship(core.halo.SimulationObjectBase, primaryjoin=(self._table.c.halo_to_id == core.halo.SimulationObjectBase.id),
                                                 foreign_keys = [self._table.c.halo_to_id]),
                       "source_id" : self._table.c.source_id,
                       "weight" : self._table.c.weight,
                       "nhops" : self._table.c.nhops
                       }

        return type(class_name,class_base,class_attrs)

    def _generate_query(self, halo_ids_only):
        if halo_ids_only:
            query = self.session.query(self._link_orm_class.halo_to_id)
        else:
            query = self.session.query(self._link_orm_class)

        if not self._include_startpoint:
            query = query.filter(self._table.c.nhops>0)

        query = self._filter_query_for_target(query, self._target)

        return query

    def _generate_order_arg_from_name(self, name, halo_alias, timestep_alias):
        if name == 'nhops':
            return self._link_orm_class.c.nhops
        else:
            return super()._generate_order_arg_from_name(name, halo_alias, timestep_alias)