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

"""tests for app.api.decorators"""
import collections
from mock import Mock, MagicMock, patch, call

from tests.unit import UnitTestCase
from app.api import (anonymous_required, token_auth_required, http_auth_required,
                     session_auth_required, auth_required, roles_required, roles_accepted, one_of,
                     permissions_required, permissions_accepted)

from app.exceptions import BadRequestException, UnauthorizedException

class DecoratorsTestCase(UnitTestCase):

    @patch('app.api.decorators.authenticated')
    def test_anonymous_required(self, mock_authenticated):
        mock_authenticated.return_value = False

        @anonymous_required
        def test():
            return 'anonymous_required'

        self.assertEqual(test(), 'anonymous_required',
                         'test() should return {}'.format('anonymous_required'))

        mock_authenticated.return_value = True
        self.assertRaises(BadRequestException, test)

    @patch('app.api.decorators.current_user')
    @patch('app.api.decorators.verify_jwt')
    def test_token_auth_required_unauthorized(self, mock_verify_jwt, mock_current_user):

        mock_current_user.is_authenticated.return_value = False

        @token_auth_required('realm')
        def test():
            return 'token_auth_required'

        with self.assertRaises(UnauthorizedException) as uae:
            test()
        ex = uae.exception
        self.assertIsInstance(ex, UnauthorizedException)
        self.assertTrue(ex.message, 'Invalid Token')
        mock_verify_jwt.assert_called_once_with('realm')
        mock_current_user.is_authenticated.assert_called_once_with()

    @patch('app.api.decorators.identity_changed')
    @patch('app.api.decorators._request_ctx_stack')
    @patch('app.api.decorators.current_app')
    @patch('app.api.decorators.current_user')
    @patch('app.api.decorators.verify_jwt')
    def test_token_auth_required_authorized(self, mock_verify_jwt, mock_current_user,
                                            mock_current_app, mock_request_ctx_stack,
                                            mock_identity_changed):

        mock_current_user.is_authenticated.return_value = True

        @token_auth_required('realm')
        def test():
            return 'token_auth_required'

        self.assertEqual(test(), 'token_auth_required',
                         'test() should return {}'.format('token_auth_required'))

        mock_verify_jwt.assert_called_once_with('realm')
        mock_current_user.is_authenticated.assert_called_once_with()
        mock_current_app._get_current_object.assert_called_once_with()
        self.assertEqual(mock_request_ctx_stack.top.user, mock_current_user)
        self.assertEqual(mock_identity_changed.send.call_count, 1)

    @patch('app.api.decorators.http_authenticated')
    def test_http_auth_required(self, mock_http_authenticated):
        mock_http_authenticated.return_value = True

        @http_auth_required
        def test():
            return 'http_auth_required'

        self.assertEqual(test(), 'http_auth_required',
                         'test() should return {}'.format('http_auth_required'))

        mock_http_authenticated.return_value = False
        self.assertRaises(UnauthorizedException, test)

    @patch('app.api.decorators.session_authenticated')
    def test_session_auth_required(self, mock_session_authenticated):
        mock_session_authenticated.return_value = True

        @session_auth_required
        def test():
            return 'session_auth_required'

        self.assertEqual(test(), 'session_auth_required',
                         'test() should return {}'.format(session_auth_required))

        mock_session_authenticated.return_value = False
        self.assertRaises(UnauthorizedException, test)

    @patch('app.api.decorators.token_authenticated')
    @patch('app.api.decorators.http_authenticated')
    @patch('app.api.decorators.session_authenticated')
    def test_auth_required(self, mock_token_authenticated, mock_http_authenticated,
                           mock_session_authenticated):

        mock_token_authenticated.return_value = False
        mock_http_authenticated.return_value = False
        mock_session_authenticated.return_value = False

        @auth_required('token', 'basic', 'session')
        def test():
            return 'authenticated'

        self.assertRaises(UnauthorizedException, test)

        mock_token_authenticated.return_value = True

        self.assertEqual(test(), 'authenticated', 'test() should return {}'.format('authenticated'))

        mock_token_authenticated.return_value = False
        mock_http_authenticated.return_value = True
        self.assertEqual(test(), 'authenticated', 'test() should return {}'.format('authenticated'))

        mock_http_authenticated.return_value = False
        mock_session_authenticated.return_value = True
        self.assertEqual(test(), 'authenticated', 'test() should return {}'.format('authenticated'))

    @patch('app.api.decorators.auth_permissions_required')
    def test_permissions_required(self, mock_permissions_required):
        permissions = [Mock(), Mock()]

        permissions_required(*permissions)

        self.assertEqual(mock_permissions_required.call_count, 1)
        # see: http://stackoverflow.com/questions/9623114/check-if-two-unordered-lists-are-equal
        self.assertEqual(collections.Counter(mock_permissions_required.call_args[0]),
                         collections.Counter(permissions))
        exception_handler = mock_permissions_required.call_args[1].get('exception_handler')
        self.assertTrue(callable(exception_handler))
        self.assertRaises(UnauthorizedException, exception_handler, permissions[1])

    @patch('app.api.decorators.auth_permissions_accepted')
    def test_permissions_accepted(self, mock_auth_permission_accepted):
        perm1 = MagicMock()
        perm1.__str__.return_value = 'perm1'
        perm2 = MagicMock()
        perm2.__str__.return_value = 'perm2'
        permissions = [perm1, perm2]

        permissions_accepted(*permissions)

        self.assertEqual(mock_auth_permission_accepted.call_count, 1)
        self.assertEqual(len(mock_auth_permission_accepted.call_args), 2)
        # see: http://stackoverflow.com/questions/9623114/check-if-two-unordered-lists-are-equal
        self.assertEqual(collections.Counter(mock_auth_permission_accepted.call_args[0]),
                         collections.Counter(permissions))
        exception_handler = mock_auth_permission_accepted.call_args[1].get('exception_handler')
        self.assertTrue(callable(exception_handler))
        with self.assertRaises(UnauthorizedException) as uae:
            exception_handler(permissions)
            ex = uae.exception
            self.assertIsInstance(ex, UnauthorizedException)
            self.assertEqual(ex.message, 'Invalid Permission')
            self.assertEqual(ex.description,
                             'required perm1 or perm2 permission was invalid or not provided')

    @patch('app.api.decorators.auth_roles_required')
    def test_roles_required(self, mock_roles_required):
        roles = ['admin', 'editor']
        roles_required(*roles)

        self.assertEqual(mock_roles_required.call_count, 1)
        self.assertEqual(len(mock_roles_required.call_args), 2)
        # see: http://stackoverflow.com/questions/9623114/check-if-two-unordered-lists-are-equal
        self.assertEqual(collections.Counter(mock_roles_required.call_args[0]),
                         collections.Counter(roles))
        exception_handler = mock_roles_required.call_args[1].get('exception_handler')
        self.assertTrue(callable(exception_handler))
        self.assertRaises(UnauthorizedException, exception_handler, roles)

    @patch('app.api.decorators.auth_roles_accepted')
    def test_roles_accepted(self, mock_roles_accepted):
        roles = ['admin', 'editor']
        roles_accepted(*roles)

        self.assertEqual(mock_roles_accepted.call_count, 1)
        self.assertEqual(len(mock_roles_accepted.call_args), 2)
        # see: http://stackoverflow.com/questions/9623114/check-if-two-unordered-lists-are-equal
        self.assertEqual(collections.Counter(mock_roles_accepted.call_args[0]),
                         collections.Counter(roles))
        exception_handler = mock_roles_accepted.call_args[1].get('exception_handler')
        self.assertTrue(callable(exception_handler))
        self.assertRaises(UnauthorizedException, exception_handler, roles)

    def test_one_of(self):

        from app.exceptions import ApplicationException

        def result():
            return 'one_of'

        mock_decorator_1 = Mock(return_value=result)
        mock_decorator_2 = Mock(return_value=result)
        mock_decorator_3 = Mock(side_effect=Exception('exception'))
        mock_decorator_4 = Mock(
            side_effect=ApplicationException('app exception', description='description exception'))

        @one_of(mock_decorator_1, mock_decorator_2)
        def test1():
            return result

        self.assertEqual(test1(), 'one_of', 'test1() should return {}'.format('one_of'))

        @one_of(mock_decorator_3, mock_decorator_2)
        def test2():
            return result

        self.assertEqual(test2(), 'one_of', 'test2() should return {}'.format('one_of'))

        @one_of(mock_decorator_3, mock_decorator_4)
        def test3():
            return result

        with self.assertRaises(ApplicationException) as ex:
            test3()
        ex_3 = ex.exception
        self.assertIsInstance(ex_3, ApplicationException)
        self.assertEqual(ex_3.message, 'app exception or exception')
        self.assertEqual(ex_3.description, 'description exception')

        mock_decorator_3.side_effect = Exception('exception')
        mock_decorator_4.side_effect = ApplicationException('app exception',
                                                            description='description exception')

        @one_of(mock_decorator_4, mock_decorator_3)
        def test_4():
            return result

        with self.assertRaises(ApplicationException) as ex:
            test_4()

        ex_4 = ex.exception
        self.assertIsInstance(ex_4, ApplicationException)
        self.assertEqual(ex_4.message, 'exception or app exception')
        self.assertEqual(ex_4.description, 'description exception')

        @one_of()
        def test5():
            return result()
        self.assertEqual(test5(), 'one_of', 'test5() should return {}'.format('one_of'))

    @patch('app.api.decorators.OffsetPagination')
    def test_paginated_one(self, mock_offset_pagination):

        from app.api.decorators import paginated

        mock_query = MagicMock()
        mock_args = MagicMock()

        mock_query.one.return_value = '1'

        mock_args.get.side_effect = [
            True
        ]

        @paginated
        def test():
            return mock_query, mock_args

        result = test()

        expected_result = {
            'data': ['1'],
            'paging': {
                'count': 1,
                'offset': 0,
                'limit': 1,
                'previous': None,
                'next': None
            }
        }

        self.assertEqual(result, expected_result)
        mock_args.get.assert_called_once_with('one', False)
        mock_query.one.assert_called_once_with()
        self.assertFalse(mock_offset_pagination.called)

    @patch('app.api.decorators.OffsetPagination')
    def test_paginated_many(self, mock_offset_pagination):

        from app.api.decorators import paginated

        pagination = MagicMock()
        mock_offset_pagination.return_value = pagination

        pagination.data = ['hi', 'there']
        pagination.count = 7
        pagination.offset = 0
        pagination.limit = 2
        pagination.prev_url = None
        pagination.next_url = 'next_url'

        mock_query = MagicMock()
        mock_args = MagicMock()

        mock_args.get.side_effect = [
            None,
            0,
            2
        ]

        @paginated
        def test():
            return mock_query, mock_args

        result = test()

        expected_result = {
            'data': ['hi', 'there'],
            'paging': {
                'count': 7,
                'offset': 0,
                'limit': 2,
                'previous': None,
                'next': 'next_url'
            }
        }

        mock_offset_pagination.assert_called_once_with(mock_query, offset=0, limit=2)
        mock_args.get.assert_has_calls([call('one', False),
                                        call('offset', None),
                                        call('limit', None)])
        self.assertEqual(result, expected_result)


# -*- coding: utf-8 -*-
"""
Created on Wed Aug  1 10:45:11 2018

@author: Sebin
"""

import numpy as np
import pandas as pd

#Set the file url of csv datasheet here
df1 = pd.read_csv(r"YOUR CSV File URL HERE")
#Set the column head of features in the csv sheet here
df1 = df1.set_index('DATA')

count = 0

#limiting operation to end result of 1
while (len(df1.columns) > 1):

    #Sorting to find the feature with most "1" or "0"
    df3 = df1.assign(f =  df1.sum(axis=1)).sort_values('f',ascending=False)
    #decision on starting with most "0" or "1"
    choice = df3['f'].idxmin()

    print('Do you have an issue of',choice,'?')
    #getting user input
    user_input = eval(input())

    if user_input==1:

        print('So you have an issue of',choice)

        #Selecting rows with choice
        df4 = df1.loc[[choice,  ]]
        df4 = df4.T
        #finding column heads wit6h feature value inmatch with user input
        drop_colmn = df4.index[df4[choice] == 0].tolist()
        #dropping columns with columns(tests) that are not in match with user input
        df1 = df1.drop(drop_colmn,axis=1)
         #calculating the number of columns
        column_numb = len(df1.columns)
        #transposing for operations on row.
        df2 = df1.T

        #if(df2[choice].sum() == column_numb):
        #if all values of a feature turn 0 for all column heads,it will be dropped from further iteration
        if(df2[choice].sum() == 0):

            df1 = df1.T.drop(choice,axis = 1)
            df1 = df1.T
            print(list(df1))
            # df5 = df1[df1[choice]].squeeze()

            # print(df5)
            #print(df4)
        else:print(list(df1))

    elif user_input==0:

        print('So you dont have an issue of',choice)
        df4 = df1.loc[[choice,  ]]
        df4 = df4.T
       # drop_value_colhead = df4['']
        drop_colmn = df4.index[df4[choice] == 1].tolist()

        df1 = df1.drop(drop_colmn,axis=1)
        print(list(df1))
        df2 = df1.T
        column_numb = len(df1.columns)

        #if(df2[choice].sum() == column_numb):
        #if all values of a feature turn 0 for all column heads,it will be dropped from further iteration

        if(df2[choice].sum() == 0):
            df1 = df1.T.drop(choice,axis = 1)
#            print('before transpose')
#            print(df1)
            df1 = df1.T
#            print('elif triggered')
            print(list(df1))

    else:

        print("Invalid Entry")

   # count = count + 1

#print(df3)
#print(df1)

   #Issue
   # 1. two tests with same features can create an issue
   # 2. Only single output for now.


__author__ = "JMullens"

# from cityDB import CityDB()

import random, os, math, turtle

import deckDB, playerDB, roleDB, counter_classes, cityDB

## Game Setup

## Information to get: # of Players, Difficulty (Beginner, Normal, Hard)

num_players = int(input("How many players? 2-4:"))

if num_players >= 2 and num_players <= 4:
    print("Ok, %s players." % num_players)
else:
    print("Choose a number between 2 and 4, please.")
    num_players = int(input("How many players? 2-4:"))

## Shuffle RoleCard, deal and assign 1 Role to each Player.

rand_role_card = roleDB.role_card_list[random.randrange(len(roleDB.role_card_list))]

input("Press ENTER to be assigned a Role Card")

roleDB.assign_role_card(num_players)

## Shuffle Playerdeck, deal StartingHand to each Player based on # of Players.

input("\nPress ENTER to shuffle Player Deck and Deal Starting Hands.")

pd = deckDB.Deck()

print("Cards in Player deck before shuffle: %s" % pd.card_list)
shuffled_player_deck = pd.shuffle()
print("Cards in Player deck after shuffling: %s" % shuffled_player_deck)

print("%s cards in Player Deck" % len(shuffled_player_deck))

## Deals starting hand to players based on number of players

def deal_starting_hand(num):
    if num == 2:
        ##Creates a player's starting hand for 2
        print("\n"
              "Player 1's hand:")
        for i in range(4):
            playerDB.player_1_hand.append(shuffled_player_deck[0])
            print("  ", playerDB.player_1_hand[i][1])
            shuffled_player_deck.remove(shuffled_player_deck[0])
        print("\n"
              "Player 2's hand:")
        for i in range(4):
            playerDB.player_2_hand.append(shuffled_player_deck[0])
            print("  ", playerDB.player_2_hand[i][1])
            shuffled_player_deck.remove(shuffled_player_deck[0])
    elif num == 3:
        ##Creates a player's starting hand for 3
        print("\n"
              "Player 1's hand:")
        for i in range(3):
            playerDB.player_1_hand.append(shuffled_player_deck[0])
            print("  ", playerDB.player_1_hand[i][1])
            shuffled_player_deck.remove(shuffled_player_deck[0])
        print("\n"
              "Player 2's hand:")
        for i in range(3):
            playerDB.player_2_hand.append(shuffled_player_deck[0])
            print("  ", playerDB.player_2_hand[i][1])
            shuffled_player_deck.remove(shuffled_player_deck[0])
        print("\n"
              "Player 3's hand:")
        for i in range(3):
            playerDB.player_3_hand.append(shuffled_player_deck[0])
            print("  ", playerDB.player_3_hand[i][1])
            shuffled_player_deck.remove(shuffled_player_deck[0])
    elif num == 4:
        ##Creates a player's starting hand for 4
        print("\n"
              "Player 1's hand:")
        for i in range(2):
            playerDB.player_1_hand.append(shuffled_player_deck[0])
            print("  ", playerDB.player_1_hand[i][1])
            shuffled_player_deck.remove(shuffled_player_deck[0])
        print("\n"
              "Player 2's hand:")
        for i in range(2):
            playerDB.player_2_hand.append(shuffled_player_deck[0])
            print("  ", playerDB.player_2_hand[i][1])
            shuffled_player_deck.remove(shuffled_player_deck[0])
        print("\n"
              "Player 3's hand:")
        for i in range(2):
            playerDB.player_3_hand.append(shuffled_player_deck[0])
            print("  ", playerDB.player_3_hand[i][1])
            shuffled_player_deck.remove(shuffled_player_deck[0])
        print("\n"
              "Player 3's hand:")
        for i in range(2):
            playerDB.player_4_hand.append(shuffled_player_deck[0])
            print("  ", playerDB.player_4_hand[i][1])
            shuffled_player_deck.remove(shuffled_player_deck[0])

deal_starting_hand(num_players)

print("\nCards in Player deck after dealing starting hands: %s" % shuffled_player_deck)

## Cut remaining PlayerDeck into 1 pile for each Epidemic Card added in (based on
## difficulty level) and add an Epidemic card randomly inside each one.

difficulty_level = int(input("\nChoose difficulty level, \n1)beginner \n2)regular \n3)hard. \n\n1-3:"))

def add_epidemic_cards(difficulty):
    if difficulty == 1:
        epidemic_cards = 4
        small_deck_size = len(shuffled_player_deck)//epidemic_cards
        for i in range(epidemic_cards):
            shuffled_player_deck.insert(random.randrange(small_deck_size*i, small_deck_size*(i+1), 1), "EPIDEMIC!")
            #print("%s cards in currently shuffled Player Deck" % len(shuffled_player_deck))
    elif difficulty == 2:
        epidemic_cards = 5
        small_deck_size = len(shuffled_player_deck)//epidemic_cards
        for i in range(epidemic_cards):
            shuffled_player_deck.insert(random.randrange(small_deck_size*i, small_deck_size*(i+1), 1), "EPIDEMIC!")
            #print("%s cards in currently shuffled Player Deck" % len(shuffled_player_deck))
    elif difficulty == 3:
        epidemic_cards = 6
        small_deck_size = len(shuffled_player_deck)//epidemic_cards
        for i in range(epidemic_cards):
            shuffled_player_deck.insert(random.randrange(small_deck_size*i, small_deck_size*(i+1), 1), "EPIDEMIC!")
            #print("%s cards in currently shuffled Player Deck" % len(shuffled_player_deck))

    else:
        print("Assert")

add_epidemic_cards(difficulty_level)

## Print out the finished deck that will be played with.
# for i in range(len(shuffled_player_deck)):
#    print(shuffled_player_deck[i])

print("\nPlayer Deck with Epidemics added: %s" % shuffled_player_deck)

input("\n"
      "Player Deck Complete. \n"
      "Press ENTER to build Infection Deck.")

## Shuffle Infection Deck

id = deckDB.Deck()
print("\nalphabetical Infection Deck: %s" % id.card_list)
id.deck = id.shuffle()
print("\nshuffled Infection Deck: %s" % id.deck)

## Instantiate the cities with CityDB(), these city 'tokens' will be used to
## track infections, research center flags, connected cities (for outbreaks
## and movement), color, and name.

## The methods in the CityDB class allow you to infect cities incrementally,
## set an infection level, and trigger an outbreak (infects every connected city
## with one infection counter)
city = cityDB.CityDB()

print("\n%s cards in shuffled Infection Deck" % len(id.deck))

##Deal top 3 InfectionCard face up, Add 3 DiseaseCube to each of the cities.

# def infect_city(city_name, num_disease_cubes):
#     cityDB.city_name.update(dict(infection_counter=num_disease_cubes))
#     print("%s was infected with %s cubes." % (city_name["name"], num_disease_cubes))

print("\nThe first 3 cities, infected with 3 Disease Cubes are:")

city._infect(id.deck[1], id.deck[2], 3)

# top_card = id.draw_card()

# city.infect(id.shuffled[0][1].name, id.shuffled[0][0], 3)

#infect_city(san_francisco, 3)
#print(shuffled_infection_deck[0][1])
#infect_city(shuffled_infection_deck[1][1], 3)
#infect_city(shuffled_infection_deck[2][1], 3)

##Sending these cards to InfectionDiscardPile.
##Deal next 3 InfectionCard face up, Add 2 DiseaseCube to each of the cities.
##Deal next 3 InfectionCard face up, Add 1 DiseaseCube to each of the cities.
##Set OutbreakMarker to 0, InfectionRate to 2 (0 position)

##Begin Play

##Player Turn Loop
#print("%s has %s Actions left." % (playerName_01, actionsRemaining) )

#def playerTurn_actionPhase():
#    print("1. Move")
#    print("2. Treat Disease")
#    print("3. Build Research Station")
#    print("4. Share Knowledge")
#    playerAction_01 = int(input())
#    if playerAction_01 == 1:

#    return playerAction_01

# def actionLoop():
#     for action in range (1,5):
#         print("Action", action)
#         step1 = int(input("1-5?"))
#         if step1 == 1:
#             print("Move. Where?")
#         elif step1 == 2:
#             print("Color disease treated. x remain.")
#         elif step1 == 3:
#             print("Research Station built.")
#         elif step1 == 4:
#             print("Knowledge shared.")
#         else:
#             turnEndConfirm = input("Are you SURE you want to END turn? y/n?")
#             if turnEndConfirm == "y":
#                 print("Turn ended.")
#                 break
#             else:
#                 print("exit cancelled")


import os

folders = ["gauge_coco_backgrounds", "gauges_random_backgrounds"]
starting_folder = folders[1]

folder = os.path.join("images", starting_folder)

if __name__ == '__main__':
    for i, file_name in enumerate(sorted(os.listdir(folder))):
        new_filename = f"gauge-{i}.jpg"
        os.rename(os.path.join(folder, file_name), os.path.join(folder, new_filename))


#!python3
#smart search for alibaba products

import requests,webbrowser,bs4,sys

print('hold a sec...')
res = requests.get('https://www.alibaba.com/trade/search?fsb=y&IndexArea=product_en&CatId=&SearchText='+''.join(sys.argv[1:]))
res.raise_for_status()

soup = bs4.BeautifulSoup(res.text)
links = soup.select('h2 a')

print (len(links))

numopen=min(5,len(links))
for i in range (numopen):
    webbrowser.open('https://www.alibaba.com/product-detail/'+ links[i].get('href'))


import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import r2_score
N = 100
LEARNING_RATE = 0.0001

def run_all_functions(inner_data):
    """Function that takes the change in weights, biases, loss functions, predictions ,
       and weight and bias as tuple and run all functions defined in this class"""
    get_r2score(inner_data[3], target_labels)
    plot_loss_epochs(inner_data[2])
    plot_thetas_with_loss(inner_data[2], inner_data[0], inner_data[1])
    plot_all_regression_lines(inner_data[3])
    plot_best_regression(inner_data[3])

def plot_data():
    """Function That plots the loss function vs number of trials"""
    plt.scatter(input_data, target_labels)
    plt.xlabel("Input Data")
    plt.ylabel("Target Data")
    plt.title("Plotting my Data")
    plt.show()

def plot_loss_epochs(loss_functions):
    """Function That plots the loss function vs number of trials"""
    plt.plot(loss_functions, np.arange(len(loss_functions)))
    plt.xlabel("Loss Functions")
    plt.ylabel("Epochs (Number Of full Iterations)")
    plt.title("Loss Functions vs Epochs")
    plt.show()

def plot_thetas_with_loss(loss_functions, theta0, theta1):
    """Function that plots the loss function vs the change in weight and bias"""
    plt.plot(theta0, loss_functions)
    plt.plot(theta1, loss_functions, 'r')
    plt.yscale("Log")
    plt.xlabel("Weight and Bias (Weight in red)")
    plt.ylabel("Loss Function")
    plt.title("Weights and Biases vs Loss Function")
    plt.show()

def plot_all_regression_lines(hypothesis_list):
    """Function that plots the input data  vs the change in prediction line"""
    for i in hypothesis_list:
        plt.plot(input_data, i)
    plt.xlabel("Input Data")
    plt.ylabel("Change in Hypothesis")
    plt.title("Input Data vs Change in Hypothesis")
    plt.show()

def plot_best_regression(hypothesis_list):
    """Function that plots the input data vs the target data and the best prediction line"""
    plt.scatter(input_data, target_labels)
    plt.plot(input_data, hypothesis_list[-1], 'r')
    plt.xlabel("Input Data")
    plt.ylabel("Best Hypothesis and Target Labels (Hypothesis in red)")
    plt.title("Input Data vs Best Hypothesis and Target Labels")
    plt.show()

def get_r2score(hypothesis_list, y):
    """Function that prints the accuracy of the system"""
    print("Accuracy = " + str(r2_score(y, hypothesis_list[-1])))

def get_gradiant(x, y, alpha, epochs=1, theta0=0, theta1=0):
    """Function that takes the input data, target labels, learning rate and the number of iterations to be
    performed then applies the mean  square error method and  returns the change in weight as float, bias as float
    ,loss functions as a list ,predictions as a list, and the final weight as float and bias as float"""
    m = len(x)
    all_theta0 = []
    all_theta1 = []
    all_loss_functions = []
    all_hypothesis = []
    for _ in range(epochs):
        hypothesis = theta0 + theta1 * x
        all_hypothesis.append(hypothesis)
        loss_function = (1 / 2 * m) * sum((hypothesis - y) ** 2)
        all_loss_functions.append(loss_function)
        gradient = ((1 / m) * sum(hypothesis - y), (1 / m) * sum((hypothesis - y) * x))
        theta0 = theta0 - alpha * gradient[0]
        all_theta0.append(theta0)
        theta1 = theta1 - alpha * gradient[1]
        all_theta1.append(theta1)
        magnitude_gradient = np.sqrt(sum(np.array(gradient) ** 2))
        if np.round(magnitude_gradient, 6) < 0.0001:
            break
    return all_theta0, all_theta1, all_loss_functions, all_hypothesis, theta0, theta1

def stochastic_GD(x_points, y_points, no_of_iterations):
    """Function that takes the input data and the target data and plots the loss function against the number of
    iterations and prints the accuracy of the system"""
    theta0 = 0
    theta1 = 0
    alpha = .001
    m = len(y_points)
    gradient = np.zeros(2)
    loss_function = []
    hypothesis_list = []
    for i in range(no_of_iterations):
        for j in range(m):
            hypothesis = theta0 + theta1 * x_points[j]
            hypothesis_list.append(hypothesis)
            gradient[0] = hypothesis - y_points[j]
            gradient[1] = (hypothesis - y_points[j]) * x_points[j]
            cost = ((hypothesis - y_points[j])**2)/2
            loss_function.append(cost)
            theta0 = theta0 - alpha * gradient[0]
            theta1 = theta1 - alpha * gradient[1]
        new_hypothesis = [(theta0 + theta1 * x) for x in x_points]
        print(r2_score(y_points, new_hypothesis))
    hypothesis_list = np.array(hypothesis_list)
    print("Final Accuracy = " + str(r2_score(y_points, hypothesis_list[-100:])))
    plt.plot(np.arange(no_of_iterations*m), loss_function)
    plt.show()

def using_mini_batch(x, y):
    """Function that takes the input data and the target data and prints the accuracy of the system using the
    mini batch system"""
    theta0 = 0
    theta1 = 0
    for j in range(10):
        for i in range(1, 101, 20):
            inner_data = get_gradiant(x[i:i + 20], y[i:i + 20], LEARNING_RATE, 100, theta0, theta1)
            theta0 = inner_data[4]
            theta1 = inner_data[5]
            get_r2score(inner_data[3], y[i:i + 20])
            plt.plot(np.arange(len(inner_data[2])), inner_data[2])
        plt.show()

def get_gradiant_using_momentum(x, y, alpha, epochs=1, gama=0.8):
    """Function that takes the input data, target labels, learning rate and the number of iterations to be
    performed then applies the mean  square error method and  returns the change in weight as float, bias as float
    ,loss functions as a list ,predictions as a list, and the final weight as float and bias as float"""
    m = len(x)
    theta0 = 0
    theta1 = 0
    all_theta0 = []
    all_theta1 = []
    all_loss_functions = []
    all_hypothesis = []
    old_vt = (0, 0)
    for _ in range(epochs):
        hypothesis = theta0 + theta1 * x
        all_hypothesis.append(hypothesis)
        loss_function = (1 / 2 * m) * sum((hypothesis - y) ** 2)
        gradient = ((1 / m) * sum(hypothesis - y), (1 / m) * sum((hypothesis - y) * x))
        vt = (gama * old_vt[0] + alpha * gradient[0], gama * old_vt[1] + alpha * gradient[1])
        theta0 = theta0 - vt[0]
        all_theta0.append(theta0)
        theta1 = theta1 - vt[1]
        all_theta1.append(theta1)
        all_loss_functions.append(loss_function)
        old_vt = vt
        magnitude_gradient = np.sqrt(sum(np.array(gradient) ** 2))
        if np.round(magnitude_gradient, 6) < 0.0001:
            break
    return all_theta0, all_theta1, all_loss_functions, all_hypothesis, theta0, theta1

def get_gradiant_using_momentum_nag(x, y, alpha, epochs=1, gama=0.8):
    """Function that implements (the nesterov accelerated gradient descent) that takes the input data, target labels,
     learning rate and the number of iterations to be performed then applies the mean  square error method and
       returns the change in weight as float, bias as float,loss functions as a list ,predictions as a list,
       and the final weight as float and bias as float"""
    m = len(x)
    w = np.zeros(2)
    all_theta0 = []
    all_theta1 = []
    all_loss_functions = []
    all_hypothesis = []
    old_vt = (0, 0)
    for _ in range(epochs):
        hypothesis = w[0] + w[1] * x
        all_hypothesis.append(hypothesis)
        loss_function = (1 / 2 * m) * sum((hypothesis - y) ** 2)
        w_temp = (w[0] - gama * old_vt[0], w[1] - gama * old_vt[1])
        hypothesis_temp = w_temp[0] + w_temp[1] * x
        gradient_w_temp = ((1 / m) * sum(hypothesis_temp - y), (1 / m) * sum((hypothesis_temp - y) * x))
        next_w = (w_temp[0] - alpha * gradient_w_temp[0], w_temp[1] - alpha * gradient_w_temp[1])
        vt = (gama * old_vt[0] + alpha * gradient_w_temp[0], gama * old_vt[1] + alpha * gradient_w_temp[1])
        w = next_w
        all_theta0.append(w[0])
        all_theta1.append(w[1])
        all_loss_functions.append(loss_function)
        old_vt = vt
        magnitude_gradient = np.sqrt(sum(np.array(gradient_w_temp) ** 2))
        if np.round(magnitude_gradient, 6) < 0.0001:
            break
    return all_theta0, all_theta1, all_loss_functions, all_hypothesis, w[0], w[1]

# a = -2
# b = 1
# input_data = np.linspace(0, 20, N)
# target_labels = a * input_data + b
# data = get_gradiant(input_data, target_labels, LEARNING_RATE, 1000)
# run_all_functions(data)
#
# a = -1
# b = 2
# input_data = np.linspace(0, 20, N)
# target_labels = a * input_data + b
# data = get_gradiant_using_momentum(input_data, target_labels, LEARNING_RATE, 50)
# run_all_functions(data)
#
# a = -1
# b = 2
# input_data = np.linspace(0, 20, N)
# target_labels = a * input_data + b
# data = get_gradiant_using_momentum_nag(input_data, target_labels, LEARNING_RATE, 100000)
# run_all_functions(data)

a = -2
b = 1
input_data = np.linspace(0, 20, N)
target_labels = a * input_data + b
using_mini_batch(input_data, target_labels)

# a = -2
# b = 1
# input_data = np.linspace(0, 20, N)
# target_labels = a * input_data + b
# stochastic_GD(input_data, target_labels, 3)


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

from odoo import models, fields, api, SUPERUSER_ID, _
from datetime import datetime
from odoo.exceptions import UserError, AccessError

class ResConfigSettings(models.TransientModel):
    _inherit = "res.config.settings"

    purchase_approve_active = fields.Boolean(
        related="company_id.purchase_approve_active",
        string="State 'Approved' in Purchase Orders",
        readonly=False,
    )

class ResCompany(models.Model):
    _inherit = "res.company"

    purchase_approve_active = fields.Boolean(
        string="Use State 'Approved' in Purchase Orders",
        help="Adds an extra state in purchase orders previous to 'Purchase "
        "Order'. After confirming and approving a purchase order it will "
        "go to state 'Approved'. In this state the incoming shipments "
        "are not created yet and you still can go back to draft. You "
        "can release the creation of the incoming shipments moving the "
        "purchase order to state 'Purchase Order'.",
    )

class PurchaseOrder(models.Model):
    _inherit = 'purchase.order'

    state = fields.Selection(selection_add=[('approved', 'Approved')])

    vendor_code = fields.Char('Vendor No', readonly=True)
    multi_location = fields.Boolean('Multiple Locations')
    logistic = fields.Selection([('dmc', 'DMC'), ('t_party', '3rd party'), ('provided', 'Provided')],
                                string="Logistics Type")

    def button_release(self):
        super(PurchaseOrder, self).button_approve()

    def button_approve(self, force=False):
        for order in self:
            if not order.logistic:
                raise UserError(_("Please assign the Logistic First"))
        new_sequence = ""
        new_sequence = (self.name).replace("RFQ", "PO")
        self.write({'name': new_sequence})
        approve_purchases = self.filtered(
            lambda p: p.company_id.purchase_approve_active
        )
        approve_purchases.write({"state": "approved"})
        return super(PurchaseOrder, self - approve_purchases).button_approve(
            force=force
        )

    @api.model
    def _prepare_picking(self):
        res = super(PurchaseOrder, self)._prepare_picking()
        res['logistic'] = self.logistic
        return res

    @api.onchange('partner_id')
    def partner_id_onchange(self):
        if self.partner_id:
            self.vendor_code = self.partner_id.supplier_no

    @api.model
    def create(self, vals):
        if vals.get('name', 'New') == 'New':
            vals['name'] = self.env['ir.sequence'].next_by_code('purchase.quotation') or '/'
        return super(PurchaseOrder, self).create(vals)

    def button_confirm(self):
        get_list = []
        msgs = {}
        partners = self.env['res.users'].search([])
        for rac in partners:
            if rac.has_group('account.group_account_user'):
                partner = rac.partner_id.id
                get_list.append(partner)
        for order in self:
            recipient_links = [(4, partner_id) for partner_id in get_list]
            ref = self.env['mail.message.subtype'].search([('name', '=', 'Discussions')])
            msgs = {
                'subject': 'PO Confirmed',
                'body': 'This is to inform you Purchase Order is confirmed. Please Pay advance amount against the PO ' + order.name,
                'message_type': 'notification',
                'record_name': order.name,
                'model': 'purchase.order',
                'partner_ids': recipient_links,
                'parent_id': False,
                'subtype_id': ref.id
            }
            msg_obj = self.env['mail.message']
            msg_obj.sudo().create(msgs)
            if order.state not in ['draft', 'sent', 'approved']:
                continue
            order._add_supplier_to_product()
            # Deal with double validation process
            if order.company_id.po_double_validation == 'one_step' \
                    or (order.company_id.po_double_validation == 'two_step' \
                        and order.amount_total < self.env.user.company_id.currency_id.compute(
                        order.company_id.po_double_validation_amount, order.currency_id)) \
                    or order.user_has_groups('purchase.group_purchase_manager'):
                order.button_approve()
            else:
                order.write({'state': 'to approve'})
        return True

    def button_draft(self):
        new_sequence = ""
        new_sequence = (self.name).replace("PO", "RFQ")
        self.write({'state': 'draft', 'name': new_sequence})
        return {}

    # def button_create_invoice(self):
    #     for order in self:
    #         inv_obj = self.env['account.move']
    #         journal = inv_obj._get_default_journal().id
    #         line_ids = []
    #         journal_domain = [
    #             ('type', '=', 'purchase'),
    #             ('company_id', '=', self.company_id.id),
    #         ]
    #         # default_journal_id = self.partner_id.partner_journal_id
    #         default_journal_id = self.env['account.journal'].search(journal_domain, limit=1)
    #         vals = {'partner_id': order.partner_id.id, 'company_id': order.company_id.id, }
    #         if type(vals) == list:
    #             vals = vals[0]
    #         invoice_id = inv_obj.new(vals)
    #         invoice_id._onchange_partner_id()
    #
    #         vals1 = {'purchase_id': order.id, }
    #         if type(vals1) == list:
    #             vals1 = vals1[0]
    #         invoice_id1 = inv_obj.new(vals1)
    #         invoice_id1._onchange_purchase_auto_complete()
    #         invoice_vals1 = invoice_id1._convert_to_write(invoice_id1._cache)
    #         if invoice_vals1:
    #             line_ids = invoice_vals1['invoice_line_ids']
    #         invoice_data = {
    #             'purchase_id': order.id,
    #             'supplier_code': order.vendor_code,
    #             'ref': order.partner_ref or order.name,
    #             # 'account_id': account_id,
    #             'type': 'in_invoice',
    #             'partner_id': order.partner_id.id,
    #             'journal_id': default_journal_id.id,
    #             'invoice_origin': order.name,
    #             # 'next_dates': order.next_payments,
    #             'currency_id': order.currency_id.id,
    #             'fiscal_position_id': order.fiscal_position_id.id or False,
    #             'invoice_payment_term_id': order.payment_term_id.id or False,
    #             'company_id': order.company_id.id,
    #             'invoice_line_ids': line_ids,
    #         }
    #         record = self.env['account.move'].create(invoice_data)
    #
    #     return record


import os
import sys
from pathlib import Path

from django.contrib.messages import constants as message_constants
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import ugettext_lazy as _

# Get key env values from the virtual environment.
def get_env_variable(var_name):
    try:
        return os.environ[var_name]
    except KeyError:
        error_msg = "Set the {} environment variable.".format(var_name)
        raise ImproperlyConfigured(error_msg)

BASE_DIR = Path(__file__).resolve().parent.parent.parent
PROJECT_DIR_NAME = "nublado"

sys.path.append(os.path.join(BASE_DIR, PROJECT_DIR_NAME))

PROJECT_ROOT = BASE_DIR / PROJECT_DIR_NAME
PROJECT_NAME = "Nublado"
PROJECT_HOME_URL = "app:home"
PROJECT_AUTH_HOME_URL = "app:home"

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = get_env_variable("DJANGO_SECRET_KEY")

# Custom User model
AUTH_USER_MODEL = "users.User"

# Social login details
SOCIAL_AUTH_URL_NAMESPACE = "social"
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = get_env_variable("SOCIAL_AUTH_GOOGLE_OAUTH2_KEY")
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = get_env_variable("SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET")
SOCIAL_AUTH_GOOGLE_LOGIN = "/auth/login/google-oauth2/"
SOCIAL_AUTH_POSTGRES_JSONFIELD = True
SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS = {
    "approval_prompt": "auto"
}
SOCIAL_AUTH_PIPELINE = (
    "social_core.pipeline.social_auth.social_details",
    "social_core.pipeline.social_auth.social_uid",
    "social_core.pipeline.social_auth.social_user",
    "social_core.pipeline.user.get_username",
    "social_core.pipeline.user.create_user",
    "social_core.pipeline.social_auth.associate_user",
    "social_core.pipeline.social_auth.load_extra_data",
    "social_core.pipeline.user.user_details",
    "social_core.pipeline.social_auth.associate_by_email",
    "users.pipeline.get_avatar",
)

# Authentication details
LOGIN_URL = "security:login"
LOGIN_REDIRECT_URL = PROJECT_AUTH_HOME_URL
LOGOUT_REDIRECT_URL = PROJECT_HOME_URL

MEDIA_ROOT = PROJECT_ROOT / "media"
MEDIA_URL = "/media/"

TMP_DIR = MEDIA_ROOT / "tmp"

STATIC_ROOT = PROJECT_ROOT / "staticfiles"
STATIC_URL = "/static/"
STATICFILES_DIRS = [PROJECT_ROOT / "static"]

ALLOWED_HOSTS = []

DJANGO_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

LOCAL_APPS = [
    "app",
    "users",
    "core",
    "security",
    "dbx",
    "vocab"
]

THIRD_PARTY_APPS = [
    "rest_framework",
    "rest_framework.authtoken",
    "widget_tweaks",
    "social_django",
]

INSTALLED_APPS = DJANGO_APPS + LOCAL_APPS + THIRD_PARTY_APPS

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
    "django.middleware.locale.LocaleMiddleware",

]

ROOT_URLCONF = "config.urls"
WSGI_APPLICATION = "config.wsgi.application"

# Internationalization and Localization
LANGUAGE_CODE = "en"
LANGUAGES = (
    ("en", _("English")),
    ("es", _("Spanish")),
)
LANGUAGES_DICT = dict(LANGUAGES)

LOCALE_PATHS = (
    PROJECT_ROOT / "app" / "locale",
)

TIME_ZONE = "America/Los_Angeles"
USE_I18N = True
USE_L10N = True
USE_TZ = False

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [PROJECT_ROOT / "templates"],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.contrib.auth.context_processors.auth",
                "django.template.context_processors.debug",
                "django.template.context_processors.i18n",
                "django.template.context_processors.media",
                "django.template.context_processors.static",
                "django.template.context_processors.tz",
                "django.template.context_processors.request",
                "django.contrib.messages.context_processors.messages",
                "social_django.context_processors.backends",
                "social_django.context_processors.login_redirect",
                "app.context_processors.global_settings"
            ],
        },
    },
]

MESSAGE_TAGS = {
    message_constants.DEBUG: "alert-primary",
    message_constants.INFO: "alert-info",
    message_constants.SUCCESS: "alert-success",
    message_constants.WARNING: "alert-warning",
    message_constants.ERROR: "alert-danger",
}

AUTHENTICATION_BACKENDS = (
    "social_core.backends.google.GoogleOAuth2",
    "django.contrib.auth.backends.ModelBackend",
)

# Oxford API
OXFORD_API_ID = os.environ["OXFORD_API_ID"]
OXFORD_API_KEY = os.environ["OXFORD_API_KEY"]
OXFORD_API_URL = "https://od-api.oxforddictionaries.com/api/v2"

# Dropbox
DBX = {
    "ACCESS_TOKEN": os.environ["DBX_ACCESS_TOKEN"]
}


# -*- coding: utf-8 -*-
import numpy as np
n= int(input('Dimensão da matriz'))
a= np.zeros((n,n))
ll=[]
lc=[]

for i in range(0,a.shape[0],1):
    for j in range(0,a.shape[0],1):
        a[i,j]= input('Digite o valor:')

for i in range(0,a.shape[0],1):
    somalinha=0
    somacoluna=0
    for j in range(0,a.shape[0],1):
        somalinha=somalinha + a[i,j]
        somacoluna=somacoluna+ a[j,i]
    ll.append(somalinha)
    lc.append(somacoluna)

linha=0
for i in range(0,len(ll),1):
    cont1=0
    for j in range(0,len(ll),1):
        if ll[i]!=ll[j]:
            cont1=cont1+1
    if cont1>1:
        linha=i
        break
coluna=0
for i in range(0,len(lc),1):
    cont2=0
    for j in range(0,len(lc),1):
        if lc[i]!=lc[j]:
            cont2=cont2+1
    if cont2>1:
        coluna=i
        break

for i in range (0,len(ll),1):
    if ll[linha]!=ll[i]:
        valormagico=i
        break

valor= a[linha,coluna]-(ll[linha]-ll[valormagico])
print('%d' % valor)
print('%d' % a[linha,coluna])


import numpy as np
import matplotlib.pyplot as plt
import struct

def vel_grid_to_vtk(basename, dx, dy, vx, vy):
    f = open(basename+".vtk", "w")
    f.write("# vtk DataFile Version 2.0\n")
    f.write("Grid-based velocities\n")
    f.write("ASCII\n")
    f.write("DATASET STRUCTURED_POINTS\n")
    f.write("DIMENSIONS "+str(vx.shape[0])+" "+str(vx.shape[1])+" 1\n")
    f.write("SPACING "+str(dx)+" "+str(dy)+" 1\n")
    f.write("ORIGIN 0 0 0\n")
    #f.write("ORIGIN "+str(vx.shape[0]/2)+" "+str(vx.shape[1]/2)+" 0\n")
    f.write("POINT_DATA "+str(vx.size)+"\n")
    f.write("VECTORS Veloctiy float\n")
    arr = np.vstack((vx.flatten(order='F'), vy.flatten(order='F'), np.zeros_like(vx).flatten(order='F'))).transpose()
    f.close()
    with open(basename+".vtk", "ab") as f:
        np.savetxt(f, arr.astype(np.float), fmt="%.4f", delimiter=" ")

nx=300
ny=300

lx=155.5
ly=155.5

dx, dy = lx/nx, ly/ny

omega = 0.5*0.001
lda = 8*10**5/10**6

xc, yc = 0.5*lx, 0.5*ly
ix, iy = np.indices((nx,ny))

rmag = np.sqrt((ix*dx - xc)**2 + (iy*dy-yc)**2)
rmag[rmag == 0] = 1e-10;

rxn = (ix*dx-xc)/rmag
ryn = (iy*dx-yc)/rmag
kx = -ryn
ky = rxn
prefac = np.minimum(omega*rmag, lda/rmag)
ugx = prefac*kx
ugy = prefac*ky

plt.quiver((ix*dx-xc)[::10,::10], (iy*dy-yc)[::10,::10], ugx[::10,::10], ugy[::10,::10])
plt.show()

vel_grid_to_vtk("test_grid_small",dx,dy,ugx,ugy)


from __future__ import print_function

import networkx as nx

"""
Python implementation of graphs, reduced graphs, directed graphs, fat
graphs (graphs with ordered adjacency lists) and fat directed graphs.

Vertices are arbitrary hashable python objects. Graph methods must
not change vertices.

To instantiate a graph, provide a list of edges, as pairs of vertices,
and an optional list of additional vertices, which may be isolated.
Vertices are saved as a set, so redundancies in the vertex list will
be ignored.  Edges are saved as a list, so multiple edges are allowed.

If G is a graph and v a vertex of G then G[v] is a list of vertices
adjacent to v.  If there are n edges joining v to w then w will
appear in this list n times.  If G is a digraph, then G[v] returns
a list of all endpoints of edges having either head or tail at v.
There additional Digraph methods to get lists of incoming or outgoing
edges.

For most graphs G, G(v) returns the set of edges incident to G.
In the case of a FatGraph, the return value is the ordered list
of incident edges.  (Loops will appear twice).

When called as a function, an edge is the non-trivial involution of
its endpoints; calling it with one endpoint returns the other one.

One can iterate over edges as:

for x, y in G.edges:
  ...

where x and y will be the two endpoints of the edge (ordered as
tail, head in the case of directed edges).
"""

_within_sage = False
try:
    import sage.all
    import sage.graphs.graph
    _within_sage = True
except ImportError:
    try:
        from .planarity import planar
        from spherogram.planarity import planar
    except ValueError: # Allow importing from source directory
        pass

from collections import deque, defaultdict
import operator

class CyclicList(list):
    def __getitem__(self, n):
        return list.__getitem__(self, n%len(self))
    def succ(self, x):
        return self[(self.index(x)+1)%len(self)]
    def pred(self, x):
        return self[(self.index(x)-1)%len(self)]

class BaseEdge(tuple):
    """
    Base class for edges: a 2-tuple of vertices with extra methods.
    Calling a BaseEdge with one of its vertices returns the other one.
    """

    def __new__(cls, x, y, **kwargs):
        return tuple.__new__(cls, (x,y))

    def __call__(self, end):
        """
        Calling an edge with one endpoint returns the other one.
        """
        if end is self[0]:
            return self[1]
        elif end is self[1]:
            return self[0]
        else:
            raise ValueError('Vertex is not an endpoint')

    def __hash__(self):
        return id(self)

    def incident_to(self):
        return list(self)

    def is_loop(self):
        return self[0] is self[1]

class Edge(BaseEdge):
    """
    An undirected edge.  We allow multiple Edges between the same
    vertices, so the __eq__ operator is overloaded.
    """

    def __repr__(self):
        return '%s --- %s'%self

    def __hash__(self):
        return id(self)

    def __eq__(self, other):
        return self is other

class MultiEdge(BaseEdge):
    """
    An undirected edge.  MultiEdges are equal if they have the
    same vertices.  The multiplicity is initialized to 1.
    """

    def __init__(self, x, y):
        self.multiplicity = 1

    def __repr__(self):
        return '%s --%d-- %s'%(self[0], self.multiplicity, self[1])

    def __hash__(self):
        return id(self)

    def __eq__(self, other):
        return set(self) == set(other)

class DirectedEdge(BaseEdge):
    """
    An Edge with a tail and a head.  The two vertices can be accessed as
    E.tail and E.head
    """

    def __repr__(self):
        return '%s --> %s'%self

    def __hash__(self):
        return id(self)

    def __eq__(self, other):
        return self is other

    @property
    def head(self):
        return self[1]

    @property
    def tail(self):
        return self[0]

class DirectedMultiEdge(DirectedEdge):
    """
    A DirectedEdge with multiplicity.  DirectedMultiEdges are equal if
    they have the same head and tail.  The multiplicity is initialized to 1.
    """

    def __init__(self, v, w):
        self.multiplicity = 1

    def __repr__(self):
        return '%s --%d-> %s'%(self[0], self.multiplicity, self[1])

    def __hash__(self):
        return id(self)

    def __eq__(self, other):
        return tuple(self) == tuple(other)

class FatEdge(Edge):
    """
    An Edge that knows its place among the edges incident to each
    of its vertices.  Initialize with two pairs (v,n) and (w,m) meaning
    that this edge joins v to w and has index n at v and m at w.
    The parity of the optional integer argument twists determines
    whether the edge is twisted or not.
    """
    def __new__(cls, x, y, twists=0):
        return tuple.__new__(cls, (x[0],y[0]))

    def __init__(self, x, y, twists=0):
        self.slots = [x[1],y[1]]
        self.twisted = True if twists%2 != 0 else False

    def __repr__(self):
        return '%s[%d] -%s- %s[%d]'%(self[0], self.slots[0],
                                     'x' if self.twisted else '-',
                                     self[1], self.slots[1])

    def __hash__(self):
        return id(self)

    def slot(self, vertex):
        try:
            return self.slots[self.index(vertex)]
        except ValueError:
            raise ValueError('Vertex is not an end of this edge.')

    def set_slot(self, vertex, n):
        try:
            self.slots[self.index(vertex)] = n
        except ValueError:
            raise ValueError('Vertex is not an end of this edge.')

class Graph(object):
    """
    A set of vertices and a set of edges joining pairs of vertices.
    Vertices are arbitrary hashable objects and should not be
    modified by Graph methods.
    """
    edge_class = Edge

    def __init__(self, pairs=[], singles=[]):
        self.vertices = set()
        self.edges = set()
        self.incidence_dict = {}
        self.Edge = self.__class__.edge_class
        for pair in pairs:
            self.add_edge(*pair)
        for vertex in singles:
            self.add_vertex(vertex)
        self._validate()

    def _validate(self):
        pass

    def __repr__(self):
        V = 'Vertices:\n  ' + '\n  '.join([str(v) for v in self.vertices])
        E = 'Edges:\n  ' + '\n  '.join([str(e) for e in self.edges])
        return '%s\n%s'%(V,E)

    def __call__(self, vertex):
        """
        Return a list of incident edges, including loops, which appear
        twice.
        """
        return self.incidence_dict[vertex]

    def __getitem__(self, vertex):
        """
        Return a list of adjacent vertices, one per incident edge.
        """
        return [e(vertex) for e in self.incidence_dict[vertex]]

    def incident(self, vertex):
        """
        Return the set of non-loops incident to the vertex.
        """
        return set(e for e in self.incidence_dict[vertex] if not e.is_loop())

    # Allow flows to go in either direction across an edge.
    flow_incident = incident

    def children(self, vertex):
        """
        Return the set of distinct adjacent vertices.
        """
        return set(e(vertex) for e in self.incidence_dict[vertex])

    def add_edge(self, *args, **kwargs):
        edge = self.Edge(*args, **kwargs)
        self.edges.add(edge)
        self.vertices.update(edge)
        for v in edge:
            try:
                self.incidence_dict[v].append(edge)
            except KeyError:
                self.incidence_dict[v] = [edge]
        return edge

    def remove_edge(self, edge):
        for vertex in edge:
            try:
                self.incidence_dict[vertex].remove(edge)
            except (KeyError, ValueError):
                pass
        self.edges.remove(edge)

    def add_vertex(self, hashable):
        self.vertices.add(hashable)
        if not self.incidence_dict.has_key(hashable):
            self.incidence_dict[hashable] = []

    def remove_vertex(self, hashable):
        incident = list(self.incidence_dict[hashable])
        for edge in incident:
            self.remove_edge(edge)
        self.vertices.remove(hashable)
        self.incidence_dict.pop(hashable)

    def valence(self, vertex):
        """
        Return the valence of a vertex.
        """
        return len(self.incidence_dict[vertex])

    def depth_first_search(self, start):
        """
        Generator which yields the vertices in the same component as
        start, in depth-first order beginning with start.
        """
        if start not in self.vertices:
            raise ValueError('That starting vertex is not in the graph.')
        stack = [start]
        seen = set(stack)
        while stack:
            current = stack.pop()
            for v in self.children(current):
                if v not in seen:
                    stack.append(v)
                    seen.add(v)
            yield current

    def breadth_first_edges(self, source, forbidden=set(), for_flow=False):
        """
        Generator for non-loop edges of a graph in the complement of
        a forbidden set, ordered by distance from the source.  Used for
        generating paths in the Ford-Fulkerson method.

        Yields triples (e, v, f) where e and f are edges containing v
        and e precedes f in the breadth-first ordering.

        The optional flag "for_flow" specifies that the flow_incident
        method should be used in place of the incident method for
        extending paths.  For example, Digraphs set flow_incident =
        outgoing.
        """
        incident = self.flow_incident if for_flow else self.incident
        initial = incident(source) - forbidden
        seen = forbidden | initial
        # Use a deque since we need to popleft.
        fifo = deque([ (None, source, e) for e in initial ])
        while fifo:
            parent, vertex, child = fifo.popleft()
            new_edges = incident(child(vertex)) - seen
            seen |= new_edges
            fifo.extend([(child, child(vertex), edge) for edge in new_edges])
            yield parent, vertex, child

    def next(self):  #For Python 2 compatibility
        return self.__next__()

    def components(self, deleted_vertices=[]):
        """
        Return the vertex sets of the connected components of the
        graph obtained by removing the deleted_vertices and any edges
        incident to them.

        >>> G = Graph([(0,1),(1,2),(2,0),(2,3),(3,4),(4,2)])
        >>> G.components()
        [frozenset([0, 1, 2, 3, 4])]
        >>> sorted(G.components(deleted_vertices=[2]))
        [frozenset([0, 1]), frozenset([3, 4])]
        >>> G.components(deleted_vertices=[0])
        [frozenset([1, 2, 3, 4])]
        """
        forbidden = set()
        for vertex in deleted_vertices:
            forbidden |= self.incident(vertex)
        vertices, result = self.vertices - set(deleted_vertices), []
        while vertices:
            component, start = set(), vertices.pop()
            component.add(start)
            generator = self.breadth_first_edges(
                source=start, forbidden=forbidden)
            for parent, vertex, child in generator:
                new_vertex = child(vertex)
                component.add(new_vertex)
                if new_vertex in vertices:
                    vertices.remove(new_vertex)
            result.append(frozenset(component))
        return result

    def is_connected(self, deleted_vertices=[]):
        """
        Determine whether the graph obtained by removing the
        deleted_vertices and incident edges is connected.

        >>> G = Graph([(0,1),(1,2),(2,0),(2,3),(3,4),(4,2)])
        >>> G.is_connected(deleted_vertices=[2])
        False
        """
        return len(self.components(deleted_vertices)) <= 1

    def one_min_cut(self, source, sink, capacity=None):
        """
        Find one minimal cut which separates source from sink, using
        the classical Ford-Fulkerson algorithm.te

        Returns a dict containing the set of vertices on the source
        side of the cut, the set of edges that cross the cut, a
        maximal family of weighted edge-disjoint paths from source to
        sink, the set of edges with non-zero residual, and the
        associated maximum flow.

        The edge capacities are supplied as a dictionary, with
        edges as keys and the capacity of the edge as value.  If
        no capacity dict is supplied, every edge is given capacity
        1.  Edges omitted from the capacity dict have infinite
        capacity.

        When called as a Graph method, the flow is relative to the
        implicit orientation of an undirected edge e from e[0] to
        e[1].  (This is determined when the edge is first constructed
        from a pair of vertices.) A negative flow value means the flow
        direction is from e[1] to e[0].  When called as a DiGraph
        method, paths are directed and flows go in the direction of
        the directed edge.

        >>> caps = {('s',0):3,('s',1):2,(0,1):2,(0,'t'):4,(1,'t'):1}
        >>> G = Graph(caps.keys())
        >>> cap_dict = dict((e, caps[tuple(e)]) for e in G.edges)
        >>> flow = G.one_min_cut('s', 't', cap_dict)['flow']
        >>> [flow[e] for e in sorted(G.edges)]
        [-1, 4, 1, 3, 2]
        >>> G = Digraph(caps.keys())
        >>> cap_dict = dict((e, caps[tuple(e)]) for e in G.edges)
        >>> flow = G.one_min_cut('s', 't', cap_dict)['flow']
        >>> [flow[e] for e in sorted(G.edges)]
        [0, 3, 1, 3, 1]
        """
        if sink == source:
            return None
        if capacity is None:
            residual = dict.fromkeys(self.edges, 1)
        else:
            residual = dict.copy(capacity)
            for e in self.edges:
                if e not in residual:
                    residual[e] = float('inf')
        full_edges = set([e for e in self.edges if residual[e] == 0])
        children = {}
        for vertex in self.vertices:
            children[vertex] = set()
        cut_set = set()
        path_list = []
        while True:
            # Try to find a new path from source to sink
            parents, cut_set, reached_sink = {}, set([source]), False
            generator = self.breadth_first_edges(
                source=source,
                forbidden=full_edges,
                for_flow=True)
            for parent, vertex, child in generator:
                 parents[child] = (parent, vertex)
                 cut_set.add(child(vertex))
                 if child(vertex) == sink:
                     reached_sink = True
                     break
            # If we did not get to the sink, we visited every vertex
            # reachable from the source, thereby providing the cut
            # set.
            if not reached_sink:
                break
            # If we got to the sink, do the bookkeeping and continue.
            path = deque()
            flow = residual[child]
            while True:
                path.appendleft( (vertex, child) )
                children[vertex].add(child)
                if vertex == source:
                    break
                child = parent
                parent, vertex = parents[child]
                flow = min(flow, residual[child])
            for vertex, edge in path:
                residual[edge] -= flow
                if residual[edge] == 0:
                    full_edges.add(edge)
            path_list.append( (flow, path) )
        # Find the cut edges.
        cut_edges = set()
        for vertex in cut_set:
            cut_edges |= set([edge for edge in self.edges
                              if vertex in edge
                              and edge(vertex) not in cut_set])
        # Find the unsaturated edges.
        unsaturated = [ e for e in self.edges if residual[e] > 0 ]
        flow_dict = dict.fromkeys(self.edges, 0)
        # Compute the flow.
        for flow, edges in path_list:
            for vertex, edge in edges:
                if vertex == edge[0]:
                    flow_dict[edge] += flow
                else:
                    flow_dict[edge] -= flow
        return {'set': cut_set, 'edges': cut_edges, 'paths': path_list,
                'residuals': residual, 'unsaturated': unsaturated,
                'flow': flow_dict}

    def reduced(self):
        R = ReducedGraph()
        for e in self.edges:
            R.add_edge(*e)
        return R

    def is_planar(self):
        return self.reduced().is_planar()

    def merge(self, V1, V2):
        """
        Merge two vertices and remove all edges between them.  The
        vertices must support the __or__ method (e.g. all vertices
        might be frozensets).  The two vertices V1 and V2 are replaced
        by V1|V2.

        >>> G = Graph([(0,1),(1,2),(2,0)]).mergeable()
        >>> F = lambda x: frozenset([x])
        >>> G.merge(F(1),F(2))
        >>> sorted(G.vertices)
        [frozenset([1, 2]), frozenset([0])]
        >>> sorted(G.edges)[0]
        frozenset([0]) --- frozenset([1, 2])
        """
        new_vertex = V1|V2
        if new_vertex in self.vertices:
            raise ValueError('Merged vertex already exists!')
        self.edges -= set([e for e in self.edges if V1 in e and V2 in e])
        self.vertices.remove(V1)
        self.vertices.remove(V2)
        old_vertices = (V1, V2)
        self.vertices.add(new_vertex)
        for edge in [e for e in self.edges if V1 in e or V2 in e]:
            self.edges.remove(edge)
            x, y = edge
            if x in old_vertices:
                self.edges.add( self.Edge(new_vertex, y) )
            if y in old_vertices:
                self.edges.add( self.Edge(x, new_vertex) )

    def mergeable(self):
        """
        Return a new graph whose vertices are singleton frozensets,
        each containing a vertex of this graph, and with edges
        corresponding to those of this graph.  The new graph
        will support merging vertices.
        """
        V = [frozenset([v]) for v in self.vertices]
        E = [self.Edge(frozenset([x]), frozenset([y])) for x, y in self.edges]
        return self.__class__(E,V)

    def to_networkx(self):
        """
        Return a copy of the graph in the networkx format
        """
        G = nx.Graph()
        G.add_nodes_from(self.vertices)
        G.add_edges_from(self.edges)
        return G

class ReducedGraph(Graph):
    """
    A graph with at most one edge between any two vertices,
    but having edges with multiplicities.
    """
    edge_class = MultiEdge

    def __init__(self, pairs=[], singles=[]):
        self.Edge = self.__class__.edge_class
        self.find_edge = dict()
        Graph.__init__(self, pairs, singles)

    def add_edge(self, x, y):
        if self.find_edge.has_key((x,y)):
            edge = self.find_edge[(x, y)]
            edge.multiplicity += 1
        else:
            edge = self.Edge(x, y)
            self.vertices.update([x,y])
            self.edges.add(edge)
            self.find_edge[(x,y)] = edge
            for v in edge:
                try:
                    self.incidence_dict[v].append(edge)
                except KeyError:
                    self.incidence_dict[v] = [edge]
        return edge

    def multi_valence(self, vertex):
        """
        Return the valence of a vertex, counting edge multiplicities.
        """
        valence = 0
        for e in self.incidence_dict[vertex]:
                valence += e.multiplicity
        return valence

    def is_planar(self):
        """
        Return the planarity.
        """

        verts_with_loops = set()
        non_loop_edges = set()
        for e in self.edges:
            v, w = e
            if v != w:
                non_loop_edges.add( (v, w) )
            else:
                verts_with_loops.add(v)

        sans_loops = ReducedGraph(non_loop_edges)
        if _within_sage:
            S = sans_loops.sage(loops=False, multiedges=False)
            is_planar = S.is_planar(set_embedding=True)
            embedding = S.get_embedding() if is_planar else None
        else:
            is_planar, embedding = planar(sans_loops)

        if is_planar:
            for v in verts_with_loops:
                embedding[v].append(v)
            self._embedding = embedding

        return is_planar

    def embedding(self):
        if self.is_planar():
            return self._embedding

    def one_min_cut(self, source, sink):
        capacity = dict((e, e.multiplicity) for e in self.edges)
        cut = Graph.one_min_cut(self, source, sink, capacity)
        cut['size'] = sum([e.multiplicity for e in cut['edges'] ])
        return cut

    def cut_pairs(self):
        """
        Return a list of cut_pairs.  The graph is assumed to be
        connected and to have no cut vertices.
        """
        pairs = []
        majors = [v for v in self.vertices if self.valence(v) > 2]
        if len(majors) == 2:
            v, V = majors
            if self.valence(v) == 3:
                return []
            edge = self.find_edge[(v,V)]
            if not edge or edge.multiplicity < 2:
                return []
            return majors
        major_set = set(majors)
        for n in xrange(1,len(majors)):
            for v in majors[:n]:
                pair = (v, majors[n])
                components = self.components(deleted_vertices=pair)
                if len(components) > 2:
                    pairs.append(pair)
                elif len(components) == 2:
                    M0 = len(major_set & components[0])
                    M1 = len(major_set & components[1])
                    edge = self.find_edge[pair]
                    if edge:
                        if M0 or M1:
                            pairs.append(pair)
                            continue
                    else:
                        if M0 and M1:
                            pairs.append(pair)
        return pairs

class FatGraph(Graph):
    """
    A FatGraph is a Graph which maintains a CyclicList of incident
    edges for each vertex.  The edges are FatEdges, which come in two
    flavors: twisted and untwisted.  Since the incident edges are in
    particulary cyclically ordered, a FatGraph has a canonical
    embedding as a spine of a surface with boundary.  However, a
    CyclicList has a first element.  This extra data, namely a choice
    of distinguished edge for each vertex, is used to give a canonical
    mapping from FatGraphs to link or tangle diagrams.

    A FatGraph should be initialized with a sequence of either double
    pairs ((v,n), (w,m)) or triples ((v,n), (w,m), T).  These indicate
    that there is an edge from v to w which has index n at v and m at
    w.  If T is supplied then the parity of T determines the twist.
    """
    edge_class = FatEdge

    def __call__(self, vertex):
        return self.incidence_dict[vertex]

    def add_edge(self, *args):
        incidences = self.incidence_dict
        edge = self.Edge(*args)
        self.edges.add(edge)
        self.vertices.update(edge)
        for v in edge:
            # the values of incidence_dict should be objects that keep
            # themselves sorted.
            if v in incidences:
                incidences[v].append(edge)
                incidences[v].sort(key=lambda e : e.slot(v))
            else:
                incidences[v] = CyclicList([edge])
        return edge

    def _validate(self):
        for v in self.vertices:
            slots = [e.slot(v) for e in self(v)]
            assert slots == range(len(slots))

    def reorder(self, vertex, cyclist):
        for e, n in zip(self(vertex), cyclist):
            e.set_slot(vertex, n)
        self.incidence_dict[vertex].sort(key=lambda e : e.slot(vertex))

    def boundary_cycles(self):
        left  = [(e[0], e, 'L') for e in self.edges]
        right = [(e[0], e, 'R') for e in self.edges]
        sides = left + right
        cycles = []
        while sides:
            cycle = []
            v, e, s = start = sides.pop()
            #print('start:', start)
            while True:
                cycle.append(e)
                # cross the edge
                v = e(v)
                if (
                    ( e.twisted and s=='L') or
                    ( not e.twisted and (s=='L')==(v==e[0]) )
                    ): # go counter-clockwise
                    e = self(v).succ(e)
                    s = 'R' if e.twisted or v == e[0] else 'L'
                else: # go clockwise
                    e = self(v).pred(e)
                    s = 'L' if e.twisted or v == e[0] else 'R'
                if (e[0],e,s) == start:
                    cycles.append(cycle)
                    break
                #print('next', (e[0],e,s))
                sides.remove( (e[0],e,s) )
        return cycles

    def filled_euler(self):
        return len(self.vertices) - len(self.edges) + len(self.boundary_cycles())

class Digraph(Graph):
    """
    A directed graph.  This subclass adds methods for finding incoming
    and outgoing edges, indegree and outdegree, and deals with all
    three notions of connectedness for digraphs.

    >>> G = Digraph([(0,1),(1,2),(2,3),(3,1),(4,2)])
    >>> G.indegree(1), G.outdegree(1)
    (2, 1)
    >>> G.is_weakly_connected()
    True
    >>> G.is_connected()
    False
    >>> G.is_strongly_connected()
    False
    >>> G.components()
    Traceback (most recent call last):
        ...
    ValueError: Not meaningful for Digraphs.
    Use weak_components() or strong_components()
    >>> G.weak_components()
    [frozenset([0, 1, 2, 3, 4])]
    >>> G.strong_components()
    [frozenset([1, 2, 3]), frozenset([0]), frozenset([4])]
    """

    edge_class = DirectedEdge

    def outgoing(self, vertex):
        """
        Return the set of non-loop edges which *begin* at the vertex.
        """
        return set(e for e in self(vertex)
                     if e.tail is vertex and not e.head is vertex)

    # Force flows to go in the direction of the edge.
    flow_incident = outgoing

    def incoming(self, vertex):
        """
        Return the set of non-loop edges which *end* at the vertex.
        """
        return set(e for e in self(vertex)
                     if e.head is vertex and not e.tail is vertex)

    def children(self, vertex):
        """
        Return the set of distinct vertices which are endpoints of
        outgoing non-loop edges.
        """
        return set(e(vertex) for e in self.outgoing(vertex))

    def indegree(self, vertex):
        return len([e for e in self.incidence_dict[vertex] if e.head is vertex])

    def outdegree(self, vertex):
        return len([e for e in self.incidence_dict[vertex] if e.tail is vertex])

    # For Digraphs we call them weak components.
    weak_components = Graph.components

    def components(self):
        raise ValueError('Not meaningful for Digraphs.\n'
                         'Use weak_components() or strong_components()')

    def is_weakly_connected(self):
        """
        A digraph is weakly connected if the associated undirected graph
        is connected.
        """
        return len(self.weak_components()) <= 1

    def is_connected(self):
        """
        A digraph is connected if, for every pair of vertices v, w,
        there is either a directed path from v to w or a directed path
        from w to v.  This is equivalent to the condition that the
        strong component DAG be a linear order.  Note that
        reachability is not a symmetric relation, so there is no
        associated notion of a connected component.
        """
        DAG = self.component_DAG()
        if not DAG.is_weakly_connected():
            return False
        elif [v for v in DAG.vertices
              if DAG.indegree(v)>1 or DAG.outdegree(v)>1]:
            return False
        else:
            return True

    def strong_components(self):
        """
        Return the vertex sets of the strongly connected components.

        >>> G = Digraph([(0,1),(0,2),(1,2),(2,3),(3,1)])
        >>> G.strong_components()
        [frozenset([1, 2, 3]), frozenset([0])]
        >>> G = Digraph([(0,1),(0,2),(1,2),(2,3),(1,3)])
        >>> G.strong_components()
        [frozenset([3]), frozenset([2]), frozenset([1]), frozenset([0])]
        """
        return StrongConnector(self).components

    def is_strongly_connected(self):
        """
        A digraph is strongly connected if, for every pair of vertices
        v, w, there is a directed path from v to w and a directed path
        from w to v.
        """
        return len(self.strong_components()) <= 1

    def component_DAG(self):
        """
        Return the acyclic digraph whose vertices are the strong
        components of this digraph.  Two components are joined by an
        edge if this digraph has an edge from one component to the
        other.

        >>> G = Digraph([(0,1),(0,2),(1,2),(2,3),(3,1)])
        >>> G.component_DAG()
        Vertices:
          frozenset([1, 2, 3])
          frozenset([0])
        Edges:
          frozenset([0]) --> frozenset([1, 2, 3])
        """
        return StrongConnector(self).DAG()

class StrongConnector(object):
    """
    Finds strong components of a digraph using Tarjan's algorithm;
    see http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
    """
    def __init__(self, digraph):
        self.digraph = digraph
        self.seen = []
        self.unclassified = []
        self.components = []
        self.root = {}
        self.which_component = {}
        self.links = set()
        for vertex in self.digraph.vertices:
            if vertex not in self.seen:
                self.search(vertex)

    def search(self, vertex):
        self.root[vertex] = len(self.seen)
        self.seen.append(vertex)
        self.unclassified.append(vertex)
        for child in self.digraph.children(vertex):
            if child not in self.seen:
                self.search(child)
                self.root[vertex] = min(self.root[child],
                                        self.root[vertex])
            elif child in self.unclassified:
                self.root[vertex] = min(self.seen.index(child),
                                        self.root[vertex])
            else:
                self.links.add((vertex, child))
        if self.root[vertex] == self.seen.index(vertex):
            component = []
            while True:
                child = self.unclassified.pop()
                component.append(child)
                if child == vertex:
                    break
            if self.unclassified:
                self.links.add((self.unclassified[-1], vertex))
            component = frozenset(component)
            self.components.append(component)
            for child in component:
                self.which_component[child] = component

    def DAG(self):
        """
        Return the directed acyclic graph whose vertices are the
        strong components of the underlying digraph.  There is an edge
        joining two components if and only if there is an edge of the
        underlying digraph having an endpoint in each component.
        Using self.links, rather than self.digraph.edges, is a slight
        optimization.
        """
        edges = set()
        for tail, head in self.links:
            dag_tail= self.which_component[tail]
            dag_head = self.which_component[head]
            if dag_head != dag_tail:
                edges.add( (dag_tail, dag_head) )
        return Digraph(edges, self.components)

class Poset(set):
    """
    A partially ordered set, generated from a directed acyclic graph.
    Instantiate with a Digraph.  A ValueError exception is raised if the
    Digraph contains a cycle.
    """
    def __init__(self, digraph):
        self.elements = set(digraph.vertices)
        self.larger = {}
        self.smaller = {}
        self.successors = {}
        self.closed = set()
        for vertex in self:
            self.larger[vertex] = set()
            self.smaller[vertex] = set()
            self.successors[vertex] = set(digraph[vertex])
        seen = []
        for vertex in self:
            if vertex not in seen:
                self.search(vertex, seen, digraph)

    def __iter__(self):
        return self.elements.__iter__()

    def __len__(self):
        return len(self.elements)

    def search(self, vertex, seen, digraph):
        seen.append(vertex)
        for child in digraph.children(vertex):
            if child in self.smaller[vertex]:
                raise ValueError('Digraph is not acyclic.')
            self.smaller[child].add(vertex)
            self.smaller[child] |= self.smaller[vertex]
            self.search(child, seen, digraph)
            self.larger[vertex].add(child)
            self.larger[vertex] |= self.larger[child]

    def compare(self, x, y):
        if x == y:
            return 0
        elif x in self.smaller[y]:
            return 1
        elif y in self.smaller[x]:
            return -1
        else:
            return None

    def incomparable(self, x):
        """
        Return the elements which are not comparable to x.
        """
        return self.elements - self.smaller[x] - self.larger[x] - set([x])

    def smallest(self):
        """
        Return the subset of minimal elements.
        """
        return frozenset( [ x for x in self if not self.smaller[x] ] )

    def largest(self):
        """
        Return the subset of maximal elements.
        """
        return frozenset( [ x for x in self if not self.larger[x] ] )

    def closure(self, A):
        """
        Return the smallest set X containing A which is is closed
        under < , i.e. such that (x in X and y < x) => y in X.
        """
        result = frozenset(A)
        for a in A:
            result |= self.smaller[a]
        if len(result) == len(A):
            return result
        else:
            return self.closure(result)

    def XXclosed_subsets(self, start=None):
        """
        Generator for all transitively closed subsets.
        """
        if start is None:
            if self.closed:
                for subset in self.closed:
                    yield subset
                return
            else:
                start = self.smallest()
        complement = self.elements - start
        if start not in self.closed:
            self.closed.add(start)
            yield start
        for element in complement:
           # print( 'adding ', element)
            extended = self.closure(start | set([element]))
            for subset in self.XXclosed_subsets(extended):
                yield subset

    def XXXclosed_subsets(self, start=None):
        """
        Generator for all transitively closed subsets.  The subsets
        are computed once, then cached for use in subsequent calls.
        """
        if start is None:
            if self.closed:
                for subset in self.closed:
                    yield subset
                return
            else:
                start = self.smallest()
        if start not in self.closed:
            self.closed.add(start)
            yield start
        children = set()
        for element in start:
            children.update(self.successors[element] - start)
        for child in children:
            extended = self.closure(start | set([child]))
            for subset in self.XXXclosed_subsets(extended):
                yield subset

    def closed_subsets(self):
        """
        Generator for all nonempty transitively closed subsets.
        """
        # A closed subset is the closure of its set of maximal
        # elements, which is an arbitrary subset of pairwise
        # incomparable elements.  For the empty relation, every subset
        # is pairwise incomparable.  So, in general, this can easily
        # generate exponentially many subsets.
        #
        # This is used for finding level transformations of group
        # presentations.  We should think about taking advantage of
        # the special features of that situation.  For now, though, we
        # just iterate through all subsets and check each one.

        for X in powerset(self.elements):
            if 0 < len(X):
                pairwise_incomparable = True
                for x in X:
                    if ( X.intersection(self.smaller[x]) or
                         X.intersection(self.larger[x]) ):
                        pairwise_incomparable = False
                        break
                if pairwise_incomparable:
                    yield self.closure(X)

def powerset(S):
    """Recursive generator for all subsets of a set."""
    X = S.copy()
    while X:
        for x in X: break
        X.remove(x)
        singleton = set([x])
        for Y in powerset(X):
            yield(singleton | Y)
        yield X

if _within_sage:
    def _to_sage(self, loops=True, multiedges=True):
        S = sage.graphs.graph.Graph(loops=loops, multiedges=multiedges)
        S.add_vertices(self.vertices)
        for e in self.edges:
            v, w = e
            S.add_edge(v, w, repr(e))
        return S
    Graph.sage = _to_sage

    def _to_sage_digraph(self):
        S = sage.graphs.graph.DiGraph(loops=True, multiedges=True)
        S.add_vertices(self.vertices)
        for e in self.edges:
            v, w = e
            S.add_edge(v, w, repr(e))
        return S
    Digraph.sage = _to_sage_digraph


from argparse import ArgumentParser
import logging
import pickle
import os

import numpy as np
from tqdm.notebook import tqdm
import PIL.Image as Image
import matplotlib.pyplot as plt
import faiss
import streamlit as st

from torchvision import transforms
from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader
import torchvision.datasets as datasets
import torch

st.set_option("deprecation.showPyplotGlobalUse", False)

device = "cuda" if torch.cuda.is_available() else "cpu"

class Indexer:
    # @st.cache(suppress_st_warning=True)
    def __init__(
        self, DATA_PATH, model, img_size=224, embedding_size=128, device=device
    ) -> None:
        self.model = get_model(model)
        self.DATA_PATH = DATA_PATH
        st.write("Generating embeddings")
        self.embeddings, images_list = get_matrix(
            self.model, self.DATA_PATH, img_size, embedding_size
        )
        self.images_list = [path[0] for path in images_list]
        self.index = index_gen(self.embeddings)
        # TODO Perform caching till this step, as an init and automatically process packets as they come in. Convert to a class when integrating into streamlit

    def process_image(self, img, n_neighbors=5):
        src = get_embedding(self.model, img)
        scores, neighbours = self.index.search(x=src, k=n_neighbors)
        fig = get_fig(neighbours[0], self.images_list)
        # TODO do a streamlit write for this returned subplot

def get_model(model) -> dict:

    model = (
        torch.load(model) if device == "cuda" else torch.load(model, map_location="cpu")
    )
    return model

def get_matrix(model, DATA_PATH, image_size=224, embedding_size=2048) -> np.ndarray:
    def to_tensor(pil):
        return torch.tensor(np.array(pil)).permute(2, 0, 1).float()

    t = transforms.Compose(
        [transforms.Resize((image_size, image_size)), transforms.Lambda(to_tensor)]
    )

    dataset = ImageFolder(DATA_PATH, transform=t)
    model.eval()
    if device == "cuda":
        model.cuda()
    size = embedding_size  # @Ajay TODO Check this please. What should be the embedding size?
    with torch.no_grad():
        data_matrix = (
            torch.empty(size=(0, size)).cuda()
            if device == "cuda"
            else torch.empty(size=(0, size))
        )
        bs = 128
        if len(dataset) < bs:
            bs = 1
        loader = DataLoader(dataset, batch_size=bs, shuffle=False)
        my_bar = st.progress(0)
        for i, batch in enumerate(loader):
            x = batch[0].cuda() if device == "cuda" else batch[0]
            embeddings = model(x)
            data_matrix = torch.cat([data_matrix, embeddings])
            my_bar.progress(int(100 * i / len(loader)))
        my_bar.progress(100)
    return data_matrix.cpu().detach().numpy(), dataset.imgs

def index_gen(embeddings):
    d = embeddings.shape[-1]
    index = faiss.IndexFlatL2(d)
    index.add(embeddings)
    faiss.write_index(index, "index.bin")
    st.write("Index created. Stored as index.bin")
    return index

def get_fig(neighbours, images_list):
    fig, axarr = plt.subplots(1, len(neighbours), figsize=(7, 7))
    plt.axis("off")
    for i in range(len(neighbours)):
        im = Image.open(images_list[neighbours[i]]).convert("RGB")
        axarr[i].imshow(im)
        axarr[i].axis("off")
    return fig  # Returns fig to directly push to streamlit #Returns fig to directly push to streamlit

def get_embedding(model, im):
    def to_tensor(pil):
        return torch.tensor(np.array(pil)).permute(2, 0, 1).float()

    t = transforms.Compose(
        [transforms.Resize((224, 224)), transforms.Lambda(to_tensor)]
    )
    model.eval()
    if device == "cuda":
        model.cuda()
    datapoint = (
        t(im).unsqueeze(0).cuda() if device == "cuda" else t(im).unsqueeze(0)
    )  # only a single datapoint so we unsqueeze to add a dimension
    with torch.no_grad():
        embedding = model(datapoint)  # get_embedding
    return embedding.detach().cpu().numpy()

def read_image(img_byte):
    img = Image.open(img_byte).convert("RGB")
    return img