from typing import Dict, List, Set

from .token import RegularToken

class RegularLexer:
    def __init__(self):
        self.__token_list: List[RegularToken] = []
        self.__line: int = 1
        self.__error_index: int = -1
        self.__no_error_index: int = -1
        self.__skip: bool = False
        self.__compact_move_table: Dict[int, List[list]] = {
            0: [
                [0, {'{'}, [], 1],
                [0, {'|'}, [], 2],
                [0, {'.'}, [], 3],
                [0, {'\\'}, [], 4],
                [2, {'?'}, [('(', '.'), ('0', '9'), ('[', '^'), ('{', '}')], 5],
                [0, set(), [('0', '9')], 6],
                [0, {'['}, [], 7],
                [0, {'^'}, [], 8],
                [0, {']'}, [], 9],
                [0, {'('}, [], 10],
                [0, {')'}, [], 11],
                [0, {'-'}, [], 12],
                [0, {'+'}, [], 13],
                [0, {'*'}, [], 14],
                [0, {'?'}, [], 15],
                [0, {','}, [], 16],
                [0, {'}'}, [], 17]
            ],
            4: [
                [2, {'u'}, [], 18],
                [0, {'u'}, [], 19]
            ],
            19: [
                [0, set(), [('0', '9'), ('A', 'F'), ('a', 'f')], 20]
            ],
            20: [
                [0, set(), [('0', '9'), ('A', 'F'), ('a', 'f')], 21]
            ],
            21: [
                [0, set(), [('0', '9'), ('A', 'F'), ('a', 'f')], 22]
            ],
            22: [
                [0, set(), [('0', '9'), ('A', 'F'), ('a', 'f')], 23]
            ],
            1: [
                [0, {'_'}, [('A', 'Z'), ('a', 'z')], 24]
            ],
            24: [
                [0, {'_'}, [('0', '9'), ('A', 'Z'), ('a', 'z')], 24],
                [0, {'}'}, [], 25]
            ]
        }
        self.__character_set: Set[str] = {'6', '_', '7', 'x', '0', 'O', '9', 'l', '-', '1', 'b', 'P', 'F', 'e', 'Y', 'o', 'k', 'd', '^', 'n', 'W', '.', 'G', ')', '}', 'u', 'N', 'p', ']', 'A', ',', 'q', 't', 'f', 'Z', '8', 'R', '{', 'K', 'L', 'Q', '+', 'T', 'U', 'i', 'y', 'a', 'I', 'j', 'm', 'B', '3', 'C', '|', '*', 'J', 'h', 's', 'D', 'S', 'r', 'H', 'V', 'v', 'X', '[', 'w', 'M', 'z', '2', '?', '4', 'g', 'E', '5', 'c', '(', '\\'}
        self.__start_state: int = 0
        self.__end_state_set: Set[int] = {1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 23, 25}
        self.__lexical_symbol_mapping: Dict[int, str] = {
            1: '!symbol_16',
            2: '!symbol_1',
            3: '!symbol_2',
            5: '!symbol_4',
            6: '!symbol_6',
            7: '!symbol_7',
            8: '!symbol_8',
            9: '!symbol_9',
            10: '!symbol_10',
            11: '!symbol_11',
            12: '!symbol_12',
            13: '!symbol_13',
            14: '!symbol_14',
            15: '!symbol_15',
            16: '!symbol_17',
            17: '!symbol_18',
            18: '!symbol_3',
            19: '!symbol_3',
            23: '!symbol_5',
            25: 'reference'
        }
        self.__non_greedy_state_set: Set[int] = set()
        self.__symbol_function_mapping: Dict[str, List[str]] = {
            'reference': ['reference']
        }
        self.__lexical_function: Dict[str, callable] = {}

    def _invoke_lexical_function(self, symbol: str, token_string: str) -> str:
        self.__skip: bool = False
        if symbol in self.__symbol_function_mapping:
            for function in self.__symbol_function_mapping[symbol]:
                if function in self.__lexical_function:
                    token_string = self.__lexical_function[function](token_string)
                elif function == 'skip':
                    self.skip()
                elif function == 'newline':
                    self.newline()
        return token_string

    def _generate_token(self, state: int, token_string: str) -> None:
        symbol: str = self.__lexical_symbol_mapping.get(state, '!symbol')
        token_string: str = self._invoke_lexical_function(symbol, token_string)
        if not self.__skip:
            self.__token_list.append(RegularToken(token_string, self.__line, symbol))

    def token_list(self) -> List[RegularToken]:
        return self.__token_list

    def line(self) -> int:
        return self.__line

    def skip(self) -> None:
        self.__skip = True

    def newline(self) -> None:
        self.__line += 1

    def error_index(self) -> int:
        return self.__error_index

    def no_error_index(self) -> int:
        return self.__no_error_index

    def tokenize(self, text: str) -> int:
        self.__token_list: List[RegularToken] = []
        self.__error_index: int = self.__no_error_index
        self.__line: int = 1
        state: int = self.__start_state
        token_string: str = ''
        index: int = 0
        while index < len(text):
            character: str = text[index]
            index += 1
            get_token: bool = False
            if state in self.__non_greedy_state_set:
                get_token: bool = True
            if not get_token and state in self.__compact_move_table:
                for attribute, character_set, range_list, next_state in self.__compact_move_table[state]:
                    if attribute == 2:
                        condition: bool = character not in character_set
                        for min_character, max_character in range_list:
                            condition &= character < min_character or character > max_character
                    else:
                        condition: bool = character in character_set
                        if attribute == 1 and character not in self.__character_set:
                            condition: bool = True
                        for min_character, max_character in range_list:
                            if condition or min_character <= character <= max_character:
                                condition: bool = True
                                break
                    if condition:
                        token_string += character
                        state: int = next_state
                        break
                else:
                    if state in self.__end_state_set:
                        get_token: bool = True
                    else:
                        self.__error_index: int = index - 1
                        return self.__error_index
            else:
                if get_token or state in self.__end_state_set:
                    get_token: bool = True
                else:
                    self.__error_index: int = index - 1
                    return self.__error_index
            if get_token:
                self._generate_token(state, token_string)
                token_string: str = ''
                state: int = self.__start_state
                index -= 1
        if state in self.__end_state_set:
            self._generate_token(state, token_string)
        else:
            self.__error_index: int = index - 1
            return self.__error_index
        self.__token_list.append(RegularToken('', self.__line, '$'))
        return self.__error_index

    def register_function(self, function_name: str) -> callable:
        def decorator(f: callable):
            self.__lexical_function[function_name] = f
            return f
        return decorator


def _qformat(self, aline, bline, atags, btags):
        r"""
        Format "?" output and deal with leading tabs.

        Example:

        >>> d = Differ()
        >>> results = d._qformat('\tabcDefghiJkl\n', '\tabcdefGhijkl\n',
        ...                      '  ^ ^  ^      ', '  ^ ^  ^      ')
        >>> for line in results: print repr(line)
        ...
        '- \tabcDefghiJkl\n'
        '? \t ^ ^  ^\n'
        '+ \tabcdefGhijkl\n'
        '? \t ^ ^  ^\n'
        """

        # Can hurt, but will probably help most of the time.
        common = min(_count_leading(aline, "\t"),
                     _count_leading(bline, "\t"))
        common = min(common, _count_leading(atags[:common], " "))
        common = min(common, _count_leading(btags[:common], " "))
        atags = atags[common:].rstrip()
        btags = btags[common:].rstrip()

        yield "- " + aline
        if atags:
            yield "? %s%s\n" % ("\t" * common, atags)

        yield "+ " + bline
        if btags:
            yield "? %s%s\n" % ("\t" * common, btags)


import os
import neat
import numpy as np
import random as rd
import seaborn as sns
import matplotlib.pyplot as plt
from scipy.signal import savgol_filter
from tqdm import tqdm

from anti_spoofing.data_utils import ASVDataset
from anti_spoofing.silence_detection import detect_speech

path = "neat-checkpoint-39"

nb_samples = 1

dev_border = [0, 2548, 6264, 9980, 13696, 17412, 21128, 22296]
index_test = []
for i in range(len(dev_border) - 1):
    index_test += rd.sample([k for k in range(dev_border[i], dev_border[i + 1])], nb_samples)

test_loader = ASVDataset(None, is_train=False, is_eval=False, index_list=index_test)

def whiten(single_input):
    whiten_input = single_input - single_input.mean()
    var = np.sqrt((whiten_input ** 2).mean())
    whiten_input *= 1 / var
    return whiten_input

def gate_activation(recurrent_net, inputs):
    length = inputs.size
    score, select = np.zeros(length), np.zeros(length)
    for i in range(length):
        select[i], score[i] = recurrent_net.activate([inputs[i]])
    mask = (select > 0.5)
    return mask, score

def evaluate(net, data_loader):
    net.reset()
    gates = []
    scores = []
    for data in data_loader:
        inputs, output = data[0], data[1]
        inputs = whiten(inputs)
        mask, score = gate_activation(net, inputs)
        gates.append(mask)
        scores.append(score)

    return np.array(gates), np.array(scores)

def run(config_file, path):
    """
    Launches a run until convergence or max number of generation reached
    :param config_file: path to the config file
    :param n_gen: lax number of generation
    :return: the best genontype (winner), the configs, the stats of the run and the accuracy on the testing set
    """
    # Load configuration.
    config_ = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
                          neat.DefaultSpeciesSet, neat.DefaultStagnation,
                          config_file)

    # load saved population
    p = neat.Checkpointer.restore_checkpoint(path)

    genomes = p.population
    nb_genomes = len(genomes)

    gates = []
    scores = []

    for genome_id in tqdm(genomes):
        net = neat.nn.RecurrentNetwork.create(genomes[genome_id], config_)
        gate, score = evaluate(net, test_loader)
        gates.append(gate)
        scores.append(score)

    return np.array(gates), np.array(scores), nb_genomes

if __name__ == '__main__':
    # Determine path to configuration file. This path manipulation is
    # here so that the script will run successfully regardless of the
    # current working directory.
    local_dir = os.path.dirname(__file__)
    config_path = os.path.join(local_dir, 'neat.cfg')
    gates, scores, nb_genomes = run(config_path, path)

    audio_samples_using = []
    score_audio_samples_using = []
    speech_detection = []
    speech_detection_time = []
    for audio_sample in range(nb_samples * 7):
        audio_samples_using.append(gates[0][audio_sample].astype('int'))
        score_audio_samples_using.append(scores[0][audio_sample])

        # sum gates dans scores over nb_genomes genomes
        for genome in range(1, nb_genomes):
            audio_samples_using[audio_sample] += gates[genome][audio_sample].astype('int')
            score_audio_samples_using[audio_sample] += scores[genome][audio_sample]

        # to smooth gates
        audio_samples_using[audio_sample] = audio_samples_using[audio_sample] / nb_genomes
        audio_samples_using[audio_sample] = savgol_filter(audio_samples_using[audio_sample], 161, 3)

        # to smooth scores
        score_audio_samples_using[audio_sample] = score_audio_samples_using[audio_sample] / nb_genomes
        score_audio_samples_using[audio_sample] = savgol_filter(score_audio_samples_using[audio_sample], 161, 3)

        # to retrieve detection of speech
        raw_audio_sample = test_loader[audio_sample]
        name = str(raw_audio_sample[2]) + ".wav"
        silence, nb_frames, nb_elements = detect_speech(raw_audio_sample[0], name)
        speech_detection.append(silence)
        speech_detection_time.append((nb_frames, nb_elements))

    sns.set(style="darkgrid")
    for audio_sample in range(nb_samples * 7):
        fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1)

        # plot gates
        ax1.plot(audio_samples_using[audio_sample], 'b', label="average gates")

        # plot scores
        score_audio_samples_using[audio_sample][audio_samples_using[audio_sample] < 0.5] = 0
        ax2.plot(score_audio_samples_using[audio_sample], 'r', label="average scores")

        # plot detection of speech
        nb_frames, nb_elements = speech_detection_time[audio_sample]
        t = np.linspace(0, nb_frames * nb_elements, nb_frames)
        ax3.plot(t, speech_detection[audio_sample], 'g', label="speech detection")

        # plot signal
        ax4.plot(test_loader[audio_sample][0], 'm', label='signal')

        fig.legend()
        plt.show()


class ListNode(object):
    def __init__(self, val=None):
        self.val = val
        self.next = None

    def __str__(self):
        return "{val}->{next}".format(val=self.val, next=self.next)

def hasCycle(head: ListNode) -> bool:
    if not head:
        return False
    slow = head
    fast = head.next
    while slow != fast:
        if not fast or not fast.next:
            return False
        slow = slow.next
        fast = fast.next.next
    return True

if __name__ == '__main__':
    head_node = ListNode(1)
    node1 = ListNode(2)
    node2 = ListNode(3)
    head_node.next = node1
    node1.next = node2
    node2.next = head_node
    # print(head_node)
    print(hasCycle(head_node))


import ta
from decimal import Decimal

# Relative Strength Index
def run(params, candles, timeframe):
    rsi_total = ta.momentum.rsi(close = candles["c"], n = 14, fillna = True)

    signals = []
    last_signal = 'hold'
    top = 0
    bottom = 10000000
    top_index = 0
    bottom_index = 0

    for i in range(len(rsi_total)):
        current_rsi = rsi_total.iloc[i]
        curr_price = candles["c"][i]
        #store highest values
        if top < curr_price:
            top = curr_price
            top_index = i
        if bottom > curr_price:
            bottom = curr_price
            bottom_index = i

        if (current_rsi < 100) and (current_rsi > 0):

            base_transaction = {
                'price': float(candles['c'][i]),
                'time': int(candles['t'][i]),
                'tf': timeframe
            }

            if (current_rsi < params['buy']):
                if last_signal != 'buy': #general case we dont want duplicate buy signals
                    last_signal = 'buy'
                    base_transaction["indicator"] = "rsi"
                    base_transaction["sig"] = last_signal
                    base_transaction["str"] = round(Decimal((100 - current_rsi - 10) / 100), 10)
                    signals.append(base_transaction)

                elif last_signal == 'buy':
                    # if our current price is less than the current bottom,
                    # but we have a higher rsi (rsi double bottom signal)
                    if curr_price < bottom and current_rsi > rsi_total.iloc[bottom_index]:
                        last_signal = 'buy'
                        base_transaction["indicator"] = "rsi_div"
                        base_transaction["sig"] = last_signal
                        base_transaction["str"] = round(Decimal((100 - current_rsi - 10) / 100), 10)
                        signals.append(base_transaction)

            elif (current_rsi > params['sell']):
                if last_signal != 'sell':
                    last_signal = 'sell'
                    base_transaction["indicator"] = "rsi"
                    base_transaction["sig"] = last_signal
                    base_transaction["str"] = round(Decimal((current_rsi - 10) / 100), 10)
                    signals.append(base_transaction)

                elif last_signal == 'sell':
                    # if our current price is higher than the current top,
                    # but we have a lower rsi (rsi double top signal)
                    if curr_price > top and current_rsi < rsi_total.iloc[top_index]:
                        last_signal = 'sell'
                        base_transaction["indicator"] = "rsi_div"
                        base_transaction["sig"] = last_signal
                        base_transaction["str"] = round(Decimal((100 - current_rsi - 10) / 100), 10)
                        signals.append(base_transaction)

    print("printing RSI signals")
    pct_change = 0
    bal = 1000
    for i in range(len(signals)):
        if i+1 == len(signals)-1:
            break
        print(signals[i])
        curr_price = signals[i]['price']
        next_price = signals[i+1]['price']
        if signals[i]['sig'] == 'buy' and signals[i+1]['sig'] == 'sell':
            print("curr price: ", curr_price)
            print("next price: ", next_price)
            diff =  ((next_price - curr_price) / curr_price)
            print("diff: ", diff)
            bal = bal + (bal * diff)
            print("bal: ", bal)
            pct_change = pct_change + diff
            print("pct_change: ", pct_change)

    myroi = round((((bal - 1000) / 1000) * 100), 2)
    print("Total roi is: ", myroi, "%")
    gain = round((bal - 1000), 2)
    print("Total gain is: $", gain)
    print("Balance is: $", bal)
    print("Total pct change is: ", round((pct_change), 2))
    return signals


import argparse
import sys

import log_util

logger = log_util.get_logger(__name__)

def _parse_arguments(argv):
    parser = argparse.ArgumentParser(description='TODO')
    parser.add_argument('--debug', action='store_true', help='Enable debug mode.')

    # Insert arguments here

    return parser.parse_args(argv)

def main(argv):
    flags = _parse_arguments(argv)
    if flags.debug:
        log_util.set_log_level(logger, 'DEBUG')

    logger.debug('TODO')

if __name__ == '__main__':
    main(sys.argv[1:])


from django.urls import path, re_path

from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('init/', views.add_data_by_default, name='add_data_by_default'),
    path('files/', views.parsing_file, name='parsing_file'),
    path('participants/', views.participants_list, name='participants_list'),
    re_path(r'^aggregate/(?P<from_date>\d{2}-\d{2}-\d{4})/(?P<to_date>\d{2}-\d{2}-\d{4})/$', views.my_date_view, name='my_date')
]


class DoubleNode:
    def __init__(self, key,value):
        self.value = value
        self.key = key
        self.next = None
        self.previous = None
class DoublyLinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

    def get_head(self):
        return self.head

    def get_tail(self):
        return self.tail

    def add_last(self, key, value):
        if self.tail is None:
            self.tail = DoubleNode(key,value)
            self.head = self.tail
            return
        self.tail.next = DoubleNode(key,value)
        self.tail.next.previous = self.tail
        self.tail = self.tail.next

    def add_first(self,key, value):
        if self.head is None:
            self.head = DoubleNode(key,value)
            self.tail = self.head
            return
        self.head.previous = DoubleNode(key,value)
        self.head.previous.next = self.head
        self.head = self.head.previous

    def remove_using_node(self,node):                  #Removes node using reference to the node
        if node.previous and node.next:
            node.previous.next = node.next
            node.next.previous = node.previous
            node.next = None
            node.previous = None
        elif node.previous:
             self.tail = node.previous
             node.previous.next = None
             node.previous = None
        elif node.next:
             self.head = node.next
             node.next.previous = None
             node.next = None
        else:
            if self.head:
                self.head = None
                self.tail = None

    def print_list(self):             #helper to print Doubly LinkedList
        current = self.head
        while current:
            print(current.key,current.value)
            current = current.next

class LRU_Cache(object):

    def __init__(self, capacity):
        self.capacity = capacity
        self.num_items = 0
        self.lookup_table = dict()                              #Lookup table for storing references to nodes
        self.doubly_list = DoublyLinkedList()
        pass

    def get(self, key):
        if key in self.lookup_table:

           #removing the node from it's original position
           node_to_be_removed = self.lookup_table[key]
           self.doubly_list.remove_using_node(node_to_be_removed)

           #adding it to the front of doublyLinked List
           self.doubly_list.add_first(key,self.lookup_table[key].value)
           self.lookup_table[key] = self.doubly_list.get_head()

           return self.lookup_table[key].value

        else:
           return -1
        pass
    def print_doublylist(self):
        self.doubly_list.print_list()

    def set(self, key, value):

        if self.capacity == 0:
            print("Can't perform operations on 0 capacity cache")
            return

        if key in self.lookup_table:                           #Updating the value of duplicate key by removing the present key
             node_to_be_removed = self.lookup_table[key]
             self.doubly_list.remove_using_node(node_to_be_removed)

             #updating the value
             self.doubly_list.add_first(key,value)
             self.lookup_table[key] = self.doubly_list.get_head()

        elif self.num_items < self.capacity:

            self.doubly_list.add_first(key,value)
            self.lookup_table[key] = self.doubly_list.get_head()
            self.num_items+=1

        else:

            #removing LRU node
            node_to_be_removed = self.doubly_list.get_tail()
            remove_key = node_to_be_removed.key

            self.doubly_list.remove_using_node(node_to_be_removed)
            del self.lookup_table[remove_key]

            #adding the given node
            self.doubly_list.add_first(key,value)
            self.lookup_table[key] = self.doubly_list.get_head()

# Normal Case:
our_cache = LRU_Cache(5)
our_cache.set(1, 1)
our_cache.set(2, 2)
our_cache.set(3, 3)
our_cache.set(4, 4)

print(our_cache.get(1))
# returns 1
print(our_cache.get(2))
# returns 2
print(our_cache.get(9))
# returns -1 because 9 is not present in the cache

our_cache.set(5, 5)
our_cache.set(6, 6)

print(our_cache.get(3))
# returns -1 because the cache reached it's capacity and 3 was the least recently used entry

# Edge Case:
our_cache = LRU_Cache(2)
our_cache.set(1, 1)
our_cache.set(2, 2)
our_cache.set(1, 10)
print(our_cache.get(1))
# should return 10
print(our_cache.get(2))
# should return 2

our_cache = LRU_Cache(0)
our_cache.set(1, 1)
# should print some message like "Can't perform operations on 0 capacity cache"
print(our_cache.get(1))
# should return -1


#!/usr/bin/env python
# coding: utf-8

# In[1]:

from PyQt5 import QtCore, QtGui, QtWidgets
from pynput import mouse
import pyautogui
import os
import tensorflow.keras as k
from tensorflow.keras.models import Model
import cv2
import numpy as np
import shutil

class MyModel():
    """
    Ideally we'll have our preprocessing steps in our Model class instead of stuffing it inside our
    """
    def __init__(self, model_name):
        self.estimator = k.models.load_model("./models/" + model_name)
        self.INPUT_DIMS = self.estimator.layers[0].input_shape
        self.PREVIEW_DIMS = (299, 299)

    def preview_candidate(self, path):
        """
        Resize the candidate into a 299x299 square.
        """
        img = cv2.imread(path)
        resize = cv2.resize(img, self.PREVIEW_DIMS)
        cv2.imwrite("last_candidate.png", resize)

    def transform_candidate(self):
        """
        Preprocess a single image into a readable array
        """
        img = cv2.imread("last_screenshot.png")
        resize = cv2.resize(img, (self.INPUT_DIMS[1], self.INPUT_DIMS[2]))
        gray = cv2.cvtColor(resize, cv2.COLOR_BGR2GRAY)
        arr = gray/255
        return np.expand_dims(arr, 2)

    def predict(self):
        inputs = np.expand_dims(self.transform_candidate(), 0)
        preds = self.estimator(inputs)
        return preds

class PredictionApp(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        # Initial Calibration
        self.num_clicks = 0
        self.MAX_CLICKS = 2

        if "config.txt" in os.listdir():
            with open("config.txt", "r") as f:
                self.X1, self.Y1, self.X2, self.Y2 = tuple(map(int, f.read().splitlines()))
        else:
            self.X1 = 0
            self.Y1 = 0
            self.X2 = 1
            self.Y2 = 1

        # For selection of model
        self.model_list = [model for model in os.listdir("./models/") if "model" in model]
        if self.model_list:
            self.model_name = self.model_list[0]
        else:
            self.model_name = None
        self.model = MyModel(self.model_name)

        # For saving/loading/deleting model
        self.saved_studies = [file for file in os.listdir("./saved_studies/")]
        self.prediction = ""
        self.probability = 0.0

        self.setupUi()

        # Connect to calibration
        self.SelectSnapArea.clicked.connect(self.select_snap_area)
        self.SnapArea.clicked.connect(self.snap_candidate)
        self.SelectModel.currentIndexChanged.connect(self.model_selection_change)
        self.Evaluate.clicked.connect(self.evaluate_func)
        self.SaveStudy.clicked.connect(self.save_study)
        self.LoadStudy.currentIndexChanged.connect(self.load_study)

    def setupUi(self):

        # Main Window
        self.setObjectName("MainWindow")
        self.resize(627, 700)
        self.centralwidget = QtWidgets.QWidget(self)
        self.centralwidget.setObjectName("centralwidget")

        # Candidate Preview Window
        self.Candidate = QtWidgets.QLabel(self.centralwidget)
        self.Candidate.setGeometry(QtCore.QRect(20, 20, 299, 299))
        self.Candidate.setFrameShape(QtWidgets.QFrame.Panel)
        self.Candidate.setText("")
        if "last_candidate.png" in os.listdir():
            self.Candidate.setPixmap(QtGui.QPixmap("last_candidate.png"))
        else:
            self.Candidate.setPixmap(QtGui.QPixmap("placeholder.png"))
        self.Candidate.setObjectName("Candidate")

        # Candidate Heatmap Window
        self.CandidateHeatmap = QtWidgets.QLabel(self.centralwidget)
        self.CandidateHeatmap.setGeometry(QtCore.QRect(20, 350, 299, 299))
        self.CandidateHeatmap.setFrameShape(QtWidgets.QFrame.Panel)
        self.CandidateHeatmap.setText("")
        self.CandidateHeatmap.setPixmap(QtGui.QPixmap("placeholder.png"))
        self.CandidateHeatmap.setObjectName("CandidateHeatmap")

        # Select Snap Area Button
        self.SelectSnapArea = QtWidgets.QPushButton(self.centralwidget)
        self.SelectSnapArea.setGeometry(QtCore.QRect(350, 20, 240, 50))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.SelectSnapArea.setFont(font)
        self.SelectSnapArea.setObjectName("SelectSnapArea")

        # Select Model ComboBox
        self.SelectModel = QtWidgets.QComboBox(self.centralwidget)
        self.SelectModel.setGeometry(QtCore.QRect(350, 140, 240, 50))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.SelectModel.setFont(font)
        self.SelectModel.setObjectName("SelectModel")
        self.SelectModel.addItems(self.model_list)

        # Snap Area Button
        self.SnapArea = QtWidgets.QPushButton(self.centralwidget)
        self.SnapArea.setGeometry(QtCore.QRect(350, 220, 240, 99))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.SnapArea.setFont(font)
        self.SnapArea.setStyleSheet("background-color: rgb(0, 255, 0);")
        self.SnapArea.setObjectName("SnapArea")

        # Save Study button
        self.SaveStudy = QtWidgets.QPushButton(self.centralwidget)
        self.SaveStudy.setGeometry(QtCore.QRect(350, 410, 240, 40))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.SaveStudy.setFont(font)
        self.SaveStudy.setObjectName("SaveStudy")

        # Display Model Name Label
        self.SaveStudyConfirmation = QtWidgets.QLabel(self.centralwidget)
        self.SaveStudyConfirmation.setGeometry(QtCore.QRect(350, 450, 240, 20))
        self.SaveStudyConfirmation.setObjectName("SaveStudyConfirmation")

        # Load Study Button
        self.LoadStudy = QtWidgets.QComboBox(self.centralwidget)
        self.LoadStudy.setGeometry(QtCore.QRect(350, 490, 240, 40))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.LoadStudy.setFont(font)
        self.LoadStudy.setObjectName("LoadStudy")
        self.LoadStudy.addItems(self.saved_studies)

        # Evaluate Button
        self.SelectSnapArea.setObjectName("SelectSnapArea")
        self.Evaluate = QtWidgets.QPushButton(self.centralwidget)
        self.Evaluate.setGeometry(QtCore.QRect(350, 350, 240, 50))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.Evaluate.setFont(font)
        self.Evaluate.setStyleSheet("background-color: rgb(0, 255, 0);")
        self.Evaluate.setObjectName("Evaluate")

        # Display Model Name Label
        self.DisplayModelName = QtWidgets.QLabel(self.centralwidget)
        self.DisplayModelName.setGeometry(QtCore.QRect(350, 190, 241, 16))
        self.DisplayModelName.setObjectName("DisplayModelName")

        # Probability Output Label
        self.ProbabilityOutput = QtWidgets.QLabel(self.centralwidget)
        self.ProbabilityOutput.setGeometry(QtCore.QRect(350, 540, 240, 50))
        self.ProbabilityOutput.setStyleSheet("background-color: rgb(255, 255, 255);")
        self.ProbabilityOutput.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
        self.ProbabilityOutput.setObjectName("ProbabilityOutput")

        # Config Output Label
        self.ConfigOutput = QtWidgets.QLabel(self.centralwidget)
        self.ConfigOutput.setGeometry(QtCore.QRect(350, 80, 241, 41))
        self.ConfigOutput.setStyleSheet("background-color: rgb(255, 255, 255);")
        self.ConfigOutput.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
        self.ConfigOutput.setObjectName("ConfigOutput")
        self.setCentralWidget(self.centralwidget)

        # MenuBar
        self.menubar = QtWidgets.QMenuBar(self)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 627, 21))
        self.menubar.setObjectName("menubar")
        self.menu = QtWidgets.QMenu(self.menubar)
        self.menu.setObjectName("menu")
        self.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(self)
        self.statusbar.setObjectName("statusbar")
        self.setStatusBar(self.statusbar)
        self.actionAbout = QtWidgets.QAction(self)
        self.actionAbout.setObjectName("actionAbout")
        self.menu.addAction(self.actionAbout)
        self.menubar.addAction(self.menu.menuAction())

        self.retranslateUi(self)

        QtCore.QMetaObject.connectSlotsByName(self)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        self.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.SelectSnapArea.setText(_translate("MainWindow", "Configure Snap Area"))
        self.SnapArea.setText(_translate("MainWindow", "Snap Area"))
        self.Evaluate.setText(_translate("MainWindow", "Evaluate"))
        self.SaveStudy.setText(_translate("MainWindow", "Save Study"))
        self.SaveStudyConfirmation.setText(_translate("MainWindow", ""))
        self.DisplayModelName.setText(_translate("MainWindow", self.adjust_model_label()))
        self.ProbabilityOutput.setText(_translate("MainWindow", self.adjust_probability_label()))
        self.ConfigOutput.setText(_translate("MainWindow", self.adjust_config_label()))
        self.menu.setTitle(_translate("MainWindow", "..."))
        self.actionAbout.setText(_translate("MainWindow", "About"))

    """
    ************************************************************************************
    THESE ARE FOR ADJUSTING DIALOGUE BOXES
    ************************************************************************************
    """

    def adjust_config_label(self, override=None):
        BASE_TEXT = "X1,Y1: ({},{})\nX2,Y2: ({},{})"
        if override:
            return override
        return BASE_TEXT.format(self.X1, self.Y1, self.X2, self.Y2)

    def adjust_probability_label(self, override=None):
        BASE_TEXT = "Prediction: {}\nProbability: {}"
        if override:
            return override
        return BASE_TEXT.format(self.prediction, self.probability)

    def adjust_model_label(self, override=None):
        if override:
            return override
        BASE_TEXT = "Current Model: {}"
        return BASE_TEXT.format(self.model_name)
    def save_study_confirmation(self, filename, override=None):
        if override:
            return overrride
        BASE_TEXT = "Saved study: "
        return BASE_TEXT + filename

    """
    ************************************************************************************
    THESE ARE FOR CONFIGURING THE READER
    ************************************************************************************
    """
    def get_coords(self):
        '''
        Use to get coordinates upon mouse click and mouse release.
        For use in "Select Snap Area"
        '''
        def on_click(x, y, button, pressed, self=self):
            if self.num_clicks == 0:
                self.X1 = x
                self.Y1 = y
                self.num_clicks += 1
            else:
                self.X2 = x
                self.Y2 = y
                self.num_clicks = 0
                return False

        with mouse.Listener(on_click=on_click) as listener:
            try:
                listener.join()
            except:
                print(self.X1, self.Y1, self.X2, self.Y2)
                listener.stop

    def select_snap_area(self):
        self.ConfigOutput.setText(self.adjust_config_label(override="****CALIBRATING****"))
        self.get_coords()
        self.ConfigOutput.setText(self.adjust_config_label())
        with open("config.txt", "w") as f:
            f.write(f"{self.X1}\n{self.Y1}\n{self.X2}\n{self.Y2}")

    """
    ************************************************************************************
    THESE ARE FOR CHANGING THE MODEL
    ************************************************************************************
    """

    def model_selection_change(self):
        self.model_name = self.SelectModel.currentText()
        self.model = MyModel(self.model_name)
        self.DisplayModelName.setText(self.adjust_model_label())

    """
    ************************************************************************************
    THESE ARE FOR SNAPPING SHOTS
    ************************************************************************************
    """

    def snap_candidate(self):
        width = abs(self.X2 - self.X1)
        height = abs(self.Y2 - self.Y1)
        screenshot = pyautogui.screenshot(region=(self.X1, self.Y1, width, height))
        screenshot.save("last_screenshot.png")
        self.model.preview_candidate("last_screenshot.png")
        self.Candidate.setPixmap(QtGui.QPixmap("last_candidate.png"))

    """
    ************************************************************************************
    THESE ARE FOR EVALUATING
    ************************************************************************************
    """

    def evaluate_func(self):
        mapping = {0: "COVID NEGATIVE", 1: "COVID POSITIVE"}
        model_output = self.model.predict()[0]
        self.prediction = mapping[np.argmax(model_output)]
        self.probability = np.max(model_output)
        self.ProbabilityOutput.setText(self.adjust_probability_label())

    def gradcam(self):
        pass
    """
    ************************************************************************************
    THESE ARE FOR SAVING, LOADING, AND DELETING STUDIES
    ************************************************************************************
    """

    def save_study(self):
        custom_studies = [file for file in self.saved_studies if "study_" in file]
        if custom_studies:
            last_file = self.saved_studies[-1]
            last_int = last_file.split('_')[-1].replace('.png', '').strip('0')
            last_int = int(last_int) + 1
        else:
            last_int = 1
        filename = f"study_{last_int:05}.png"
        shutil.copy("last_screenshot.png", "./saved_studies/" + filename)
        self.saved_studies = os.listdir("./saved_studies/")
        self.LoadStudy.addItem(filename)
        self.SaveStudyConfirmation.setText(self.save_study_confirmation(filename))

    def load_study(self):
        filename = self.LoadStudy.currentText()
        shutil.copy(f"./saved_studies/{filename}", "last_screenshot.png")
        self.model.preview_candidate("last_screenshot.png")
        self.Candidate.setPixmap(QtGui.QPixmap("last_candidate.png"))
        self.evaluate_func()

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    ui = PredictionApp()
    ui.show()
    sys.exit(app.exec_())

# In[ ]:


import pygame

import pygame_widgets
from pygame_widgets.widget import WidgetBase
from pygame_widgets.mouse import Mouse, MouseState

class Button(WidgetBase):
    def __init__(self, win, x, y, width, height, isSubWidget=False, **kwargs):
        """ A customisable button for Pygame

        :param win: Surface on which to draw
        :type win: pygame.Surface
        :param x: X-coordinate of top left
        :type x: int
        :param y: Y-coordinate of top left
        :type y: int
        :param width: Width of button
        :type width: int
        :param height: Height of button
        :type height: int
        :param kwargs: Optional parameters
        """
        super().__init__(win, x, y, width, height, isSubWidget)

        # Colour
        self.inactiveColour = kwargs.get('inactiveColour', (150, 150, 150))
        self.hoverColour = kwargs.get('hoverColour', (125, 125, 125))
        self.pressedColour = kwargs.get('pressedColour', (100, 100, 100))
        self.colour = kwargs.get('colour', self.inactiveColour)  # Allows colour to override inactiveColour
        self.inactiveColour = self.colour
        self.shadowDistance = kwargs.get('shadowDistance', 0)
        self.shadowColour = kwargs.get('shadowColour', (210, 210, 180))

        # Function
        self.onClick = kwargs.get('onClick', lambda *args: None)
        self.onRelease = kwargs.get('onRelease', lambda *args: None)
        self.onClickParams = kwargs.get('onClickParams', ())
        self.onReleaseParams = kwargs.get('onReleaseParams', ())
        self.clicked = False

        # Text (Remove if using PyInstaller)
        self.textColour = kwargs.get('textColour', (0, 0, 0))
        self.fontSize = kwargs.get('fontSize', 20)
        self.string = kwargs.get('text', '')
        self.font = kwargs.get('font', pygame.font.SysFont('sans-serif', self.fontSize))
        self.text = self.font.render(self.string, True, self.textColour)
        self.textHAlign = kwargs.get('textHAlign', 'centre')
        self.textVAlign = kwargs.get('textVAlign', 'centre')
        self.margin = kwargs.get('margin', 20)

        self.textRect = self.text.get_rect()
        self.alignTextRect()

        # Image
        self.image = kwargs.get('image', None)
        self.imageHAlign = kwargs.get('imageHAlign', 'centre')
        self.imageVAlign = kwargs.get('imageVAlign', 'centre')

        if self.image:
            self.imageRect = self.image.get_rect()
            self.alignImageRect()

        # Border
        self.borderThickness = kwargs.get('borderThickness', 0)
        self.inactiveBorderColour = kwargs.get('inactiveBorderColour', (0, 0, 0))
        self.hoverBorderColour = kwargs.get('hoverBorderColour', (80, 80, 80))
        self.pressedBorderColour = kwargs.get('pressedBorderColour', (100, 100, 100))
        self.borderColour = kwargs.get('borderColour', self.inactiveBorderColour)
        self.inactiveBorderColour = self.borderColour
        self.radius = kwargs.get('radius', 0)

    def alignImageRect(self):
        self.imageRect.center = (self._x + self._width // 2, self._y + self._height // 2)

        if self.imageHAlign == 'left':
            self.imageRect.left = self._x + self.margin
        elif self.imageHAlign == 'right':
            self.imageRect.right = self._x + self._width - self.margin

        if self.imageVAlign == 'top':
            self.imageRect.top = self._y + self.margin
        elif self.imageVAlign == 'bottom':
            self.imageRect.bottom = self._y + self._height - self.margin

    def alignTextRect(self):
        self.textRect.center = (self._x + self._width // 2, self._y + self._height // 2)

        if self.textHAlign == 'left':
            self.textRect.left = self._x + self.margin
        elif self.textHAlign == 'right':
            self.textRect.right = self._x + self._width - self.margin

        if self.textVAlign == 'top':
            self.textRect.top = self._y + self.margin
        elif self.textVAlign == 'bottom':
            self.textRect.bottom = self._y + self._height - self.margin

    def listen(self, events):
        """ Wait for inputs

        :param events: Use pygame.event.get()
        :type events: list of pygame.event.Event
        """
        if not self._hidden and not self._disabled:
            mouseState = Mouse.getMouseState()
            x, y = Mouse.getMousePos()

            if self.contains(x, y):
                if mouseState == MouseState.RELEASE and self.clicked:
                    self.clicked = False
                    self.onRelease(*self.onReleaseParams)

                elif mouseState == MouseState.CLICK:
                    self.clicked = True
                    self.onClick(*self.onClickParams)
                    self.colour = self.pressedColour
                    self.borderColour = self.pressedBorderColour

                elif mouseState == MouseState.DRAG and self.clicked:
                    self.colour = self.pressedColour
                    self.borderColour = self.pressedBorderColour

                elif mouseState == MouseState.HOVER or mouseState == MouseState.DRAG:
                    self.colour = self.hoverColour
                    self.borderColour = self.hoverBorderColour

            else:
                self.clicked = False
                self.colour = self.inactiveColour
                self.borderColour = self.inactiveBorderColour

    def draw(self):
        """ Display to surface """
        if not self._hidden:
            if pygame.version.vernum[0] < 2:
                borderRects = [
                    (self._x + self.radius, self._y, self._width - self.radius * 2, self._height),
                    (self._x, self._y + self.radius, self._width, self._height - self.radius * 2),
                ]

                borderCircles = [
                    (self._x + self.radius, self._y + self.radius),
                    (self._x + self.radius, self._y + self._height - self.radius),
                    (self._x + self._width - self.radius, self._y + self.radius),
                    (self._x + self._width - self.radius, self._y + self._height - self.radius)
                ]

                backgroundRects = [
                    (
                        self._x + self.borderThickness + self.radius,
                        self._y + self.borderThickness,
                        self._width - 2 * (self.borderThickness + self.radius),
                        self._height - 2 * self.borderThickness
                    ),
                    (
                        self._x + self.borderThickness,
                        self._y + self.borderThickness + self.radius,
                        self._width - 2 * self.borderThickness,
                        self._height - 2 * (self.borderThickness + self.radius)
                    )
                ]

                backgroundCircles = [
                    (self._x + self.radius + self.borderThickness,
                     self._y + self.radius + self.borderThickness),
                    (self._x + self.radius + self.borderThickness,
                     self._y + self._height - self.radius - self.borderThickness),
                    (self._x + self._width - self.radius - self.borderThickness,
                     self._y + self.radius + self.borderThickness),
                    (self._x + self._width - self.radius - self.borderThickness,
                     self._y + self._height - self.radius - self.borderThickness)
                ]

                for rect in borderRects:
                    pygame.draw.rect(self.win, self.borderColour, rect)

                for circle in borderCircles:
                    pygame.draw.circle(self.win, self.borderColour, circle, self.radius)

                for rect in backgroundRects:
                    pygame.draw.rect(self.win, self.colour, rect)

                for circle in backgroundCircles:
                    pygame.draw.circle(self.win, self.colour, circle, self.radius)
            else:
                pygame.draw.rect(
                    self.win, self.shadowColour,
                    (self._x + self.shadowDistance, self._y + self.shadowDistance, self._width, self._height),
                    border_radius=self.radius
                )

                pygame.draw.rect(
                    self.win, self.borderColour, (self._x, self._y, self._width, self._height),
                    border_radius=self.radius
                )

                pygame.draw.rect(
                    self.win, self.colour, (self._x + self.borderThickness, self._y + self.borderThickness,
                                            self._width - self.borderThickness * 2,
                                            self._height - self.borderThickness * 2),
                    border_radius=self.radius
                )

            if self.image:
                self.imageRect = self.image.get_rect()
                self.alignImageRect()
                self.win.blit(self.image, self.imageRect)

            self.textRect = self.text.get_rect()
            self.alignTextRect()
            self.win.blit(self.text, self.textRect)

    def setText(self, text):
        self.string = text
        self.text = self.font.render(self.string, True, self.textColour)
        self.textRect = self.text.get_rect()
        self.alignTextRect()

    def setImage(self, image):
        self.image = image
        self.imageRect = self.image.get_rect()
        self.alignImageRect()

    def setOnClick(self, onClick, params=()):
        self.onClick = onClick
        self.onClickParams = params

    def setOnRelease(self, onRelease, params=()):
        self.onRelease = onRelease
        self.onReleaseParams = params

    def setInactiveColour(self, colour):
        self.inactiveColour = colour

    def setPressedColour(self, colour):
        self.pressedColour = colour

    def setHoverColour(self, colour):
        self.hoverColour = colour

    def get(self, attr):
        parent = super().get(attr)
        if parent is not None:
            return parent

        if attr == 'colour':
            return self.colour

    def set(self, attr, value):
        super().set(attr, value)

        if attr == 'colour':
            self.inactiveColour = value

class ButtonArray(WidgetBase):
    def __init__(self, win, x, y, width, height, shape, **kwargs):
        """ A collection of buttons

        :param win: Surface on which to draw
        :type win: pygame.Surface
        :param x: X-coordinate of top left
        :type x: int
        :param y: Y-coordinate of top left
        :type y: int
        :param width: Width of button
        :type width: int
        :param height: Height of button
        :type height: int
        :param shape: The 2d shape of the array (columns, rows)
        :type shape: tuple of int
        :param kwargs: Optional parameters
        """
        super().__init__(win, x, y, width, height)

        self.shape = shape
        self.numButtons = shape[0] * shape[1]

        # Array
        self.colour = kwargs.get('colour', (210, 210, 180))
        self.border = kwargs.get('border', 10)
        self.topBorder = kwargs.get('topBorder', self.border)
        self.bottomBorder = kwargs.get('bottomBorder', self.border)
        self.leftBorder = kwargs.get('leftBorder', self.border)
        self.rightBorder = kwargs.get('rightBorder', self.border)
        self.borderRadius = kwargs.get('borderRadius', 0)
        self.separationThickness = kwargs.get('separationThickness', self.border)

        self.buttonAttributes = {
            # Colour
            'inactiveColour': kwargs.get('inactiveColours', None),
            'hoverColour': kwargs.get('hoverColours', None),
            'pressedColour': kwargs.get('pressedColours', None),
            'shadowDistance': kwargs.get('shadowDistances', None),
            'shadowColour': kwargs.get('shadowColours', None),

            # Function
            'onClick': kwargs.get('onClicks', None),
            'onRelease': kwargs.get('onReleases', None),
            'onClickParams': kwargs.get('onClickParams', None),
            'onReleaseParams': kwargs.get('onReleaseParams', None),

            # Text
            'textColour': kwargs.get('textColours', None),
            'fontSize': kwargs.get('fontSizes', None),
            'text': kwargs.get('texts', None),
            'font': kwargs.get('fonts', None),
            'textHAlign': kwargs.get('textHAligns', None),
            'textVAlign': kwargs.get('textVAligns', None),
            'margin': kwargs.get('margins', None),

            # Image
            'image': kwargs.get('images', None),
            'imageHAlign': kwargs.get('imageHAligns', None),
            'imageVAlign': kwargs.get('imageVAligns', None),
            'imageRotation': kwargs.get('imageRotations', None),
            'imageFill': kwargs.get('imageFills', None),
            'imageZoom': kwargs.get('imageZooms', None),
            'radius': kwargs.get('radii', None)
        }

        self.buttons = []
        self.createButtons()

    def createButtons(self):
        across, down = self.shape
        width = (self._width - self.separationThickness * (across - 1) - self.leftBorder - self.rightBorder) // across
        height = (self._height - self.separationThickness * (down - 1) - self.topBorder - self.bottomBorder) // down

        count = 0
        for i in range(across):
            for j in range(down):
                x = self._x + i * (width + self.separationThickness) + self.leftBorder
                y = self._y + j * (height + self.separationThickness) + self.topBorder
                self.buttons.append(Button(self.win, x, y, width, height, isSubWidget=True,
                                           **{k: v[count] for k, v in self.buttonAttributes.items() if v is not None})
                                    )
                count += 1

    def listen(self, events):
        """ Wait for inputs

        :param events: Use pygame.event.get()
        :type events: list of pygame.event.Event
        """
        if not self._hidden and not self._disabled:
            for button in self.buttons:
                button.listen(events)

    def draw(self):
        """ Display to surface """
        if not self._hidden:
            rects = [
                (self._x + self.borderRadius, self._y, self._width - self.borderRadius * 2, self._height),
                (self._x, self._y + self.borderRadius, self._width, self._height - self.borderRadius * 2)
            ]

            circles = [
                (self._x + self.borderRadius, self._y + self.borderRadius),
                (self._x + self.borderRadius, self._y + self._height - self.borderRadius),
                (self._x + self._width - self.borderRadius, self._y + self.borderRadius),
                (self._x + self._width - self.borderRadius, self._y + self._height - self.borderRadius)
            ]

            for rect in rects:
                pygame.draw.rect(self.win, self.colour, rect)

            for circle in circles:
                pygame.draw.circle(self.win, self.colour, circle, self.borderRadius)

            for button in self.buttons:
                button.draw()

    def getButtons(self):
        return self.buttons

if __name__ == '__main__':
    pygame.init()
    win = pygame.display.set_mode((600, 600))

    button = Button(win, 100, 100, 300, 150, text='Hello', fontSize=50, margin=20,
                    inactiveColour=(255, 0, 0), pressedColour=(0, 255, 0), radius=20,
                    onClick=lambda: print('Click'), font=pygame.font.SysFont('calibri', 10),
                    textVAlign='bottom', imageHAlign='centre', imageVAlign='centre', borderThickness=3,
                    onRelease=lambda: print('Release'), shadowDistance=5, borderColour=(0, 0, 0))

    buttonArray = ButtonArray(win, 50, 50, 500, 500, (2, 2), border=100, texts=('1', '2', '3', '4'),
                              onClicks=(lambda: print(1), lambda: print(2), lambda: print(3), lambda: print(4)))

    buttonArray.hide()

    run = True
    while run:
        events = pygame.event.get()
        for event in events:
            if event.type == pygame.QUIT:
                pygame.quit()
                run = False
                quit()

        win.fill((255, 255, 255))

        pygame_widgets.update(events)
        pygame.display.update()


# https://leetcode.cn/problems/count-the-number-of-consistent-strings/

class Solution:
    def countConsistentStrings(self, allowed, words):
        """
        Inputs:
            allowed: str
            words: list[str]
        Outputs:
            res: int
        """
        res = len(words)
        table = [0 for _ in range(26)]
        for ch in allowed:
            table[ord(ch) - ord("a")] = 1
        for word in words:
            for ch in word:
                if table[ord(ch) - ord("a")] != 1:
                    res -= 1
                    break
        return res


import requests

from secretsanta.celery import app
from secretsanta.settings import TELEGRAM_URL, TELEGRAM_BOT_TOKEN

def rate_limit(task, task_group):
    # берем соединение с брокером из пула
    with task.app.connection_for_read() as conn:
        # забираем токен
        msg = conn.default_channel.basic_get(task_group+'_tokens', no_ack=True)
        # получили None - очередь пуста, токенов нет
        if msg is None:
            # повторить таску через 1 сек
            task.retry(countdown=1)

@app.task(bind=True, queue='messages', max_retries=None)
def send_message(self, text, chat_id, reply_markup=None):
    rate_limit(self, 'messages')
    data = {
        "chat_id": chat_id,
        "text": text,
        "reply_markup": reply_markup,
        "parse_mode": "Markdown"
    }
    response = requests.post(f"{TELEGRAM_URL}/bot{TELEGRAM_BOT_TOKEN}/sendMessage", data=data)
    print(response.content)

@app.task(bind=True, queue='messages', max_retries=None)
def edit_message(self, text, chat_id, message_id, reply_markup=None):
    rate_limit(self, 'messages')
    data = {
        "chat_id": chat_id,
        "text": text,
        "message_id": message_id,
        "reply_markup": reply_markup,
        "parse_mode": "Markdown"
    }
    response = requests.post(f"{TELEGRAM_URL}/bot{TELEGRAM_BOT_TOKEN}/editMessageText", data=data)
    print(response.content)

@app.task(bind=True, queue='messages', max_retries=None)
def mailing(self, answer, recipients):
    for recipient in recipients:
        send_message.delay(answer, recipient)
    print(f"Рассылка произведена по {len(recipients)} чатам")


import random as rd
import Klassen
import Funktionen as fk

def spieler_erstellen():
    name = input("Wie meochtest du heißen?")

    geschlecht = ""
    moegl_geschlechter = ("maennlich","weiblich","leo","zwitter")
    while geschlecht not in moegl_geschlechter:
        geschlecht = input("Welches Geschlecht bist du?(maennlich/weiblich)")
        geschlecht = fk.text_anpassen(geschlecht)

    geschicklichkeit = rd.randint(2,7)
    staerke = rd.randint(2,7)
    alter = rd.randint(17,42)
    spieler = Klassen.Spieler(name,geschlecht,alter,geschicklichkeit,staerke)
    print ("Hallo,",spieler.name," du bist",spieler.alter,"Jahre alt und",spieler.geschlecht)
    return spieler

def zombie_erstellen(position):
    geschicklichkeit = rd.randint(2,4)
    staerke = rd.randint(2,12)
    leben = 15
    name = "Zombie"+str(position)
    zombie = Klassen.Zombie(leben,geschicklichkeit,staerke,name)
    return zombie


import torch
import torch.nn as nn
import torch.nn.functional as F

from .jitbase import config
from .unet_blocks import ResnetBlock, AttentionBlock, AttentionBlock_conv

def callRA(mdlist,x,emb,cond_k,cond_v):
    m0=config.ckp(mdlist[0].forward,x,emb)
    return config.ckp(mdlist[1].forward,m0, cond_k,cond_v)

def callRC(mdlist,x,emb):
    m0 = config.ckp(mdlist[0].forward,x,emb)
    return config.ckp(mdlist[1].forward,m0)

def callRAC(mdlist,x,emb,cond_k,cond_v):
    m0 = config.ckp(mdlist[0].forward,x,emb)
    m1 = config.ckp(mdlist[1].forward,m0, cond_k,cond_v)
    return config.ckp(mdlist[2].forward,m1)

class UNetModel(nn.Module):

    def __init__(
        self,
        in_channels = 4
    ):
        super().__init__()

        # input
        self.input_blocks_0_0 = nn.Conv2d(bias=True, dilation=(1,1), groups=1, in_channels=in_channels, kernel_size=(3,3), out_channels=320, padding=(1,1), padding_mode=config.pad, stride=(1,1))
        # time
        self.freqs=nn.Parameter(torch.ones(1), requires_grad=False)
        self.time_embed_0 = nn.Linear(bias=True, in_features=320, out_features=1280)
        self.time_embed_2 = nn.Linear(bias=True, in_features=1280, out_features=1280)

        self.input_blocks = nn.ModuleList([None]*12)
        self.input_blocks[1]=nn.ModuleList([ResnetBlock(320,320),AttentionBlock(320,1280)])
        self.input_blocks[2]=nn.ModuleList([ResnetBlock(320,320),AttentionBlock(320,1280)])

        self.input_blocks[3]=nn.ModuleList([AttentionBlock_conv(320,2)])
        self.input_blocks[4]=nn.ModuleList([ResnetBlock(320,640,prv_skip=True),AttentionBlock(640,2560)])
        self.input_blocks[5]=nn.ModuleList([ResnetBlock(640,640),AttentionBlock(640,2560)])

        self.input_blocks[6]=nn.ModuleList([AttentionBlock_conv(640,2)])
        self.input_blocks[7]=nn.ModuleList([ResnetBlock(640,1280,prv_skip=True),AttentionBlock(1280,5120)])	#9
        self.input_blocks[8]=nn.ModuleList([ResnetBlock(1280,1280),AttentionBlock(1280,5120)])		#11

        self.input_blocks[9]=nn.ModuleList([AttentionBlock_conv(1280,2)])
        self.input_blocks[10]=nn.ModuleList([ResnetBlock(1280,1280)])
        self.input_blocks[11]=nn.ModuleList([ResnetBlock(1280,1280)])

        self.middle_block = nn.ModuleList([ResnetBlock(1280,1280),AttentionBlock(1280,5120),ResnetBlock(1280,1280)])	#13

        self.output_blocks = nn.ModuleList([None]*12)
        self.output_blocks[0]=nn.ModuleList([ResnetBlock(2560,1280,prv_skip=True)])
        self.output_blocks[1]=nn.ModuleList([ResnetBlock(2560,1280,prv_skip=True)])
        self.output_blocks[2]=nn.ModuleList([ResnetBlock(2560,1280,prv_skip=True),		AttentionBlock_conv(1280,1)])

        self.output_blocks[3]=nn.ModuleList([ResnetBlock(2560,1280,prv_skip=True),AttentionBlock(1280,5120)])	#15
        self.output_blocks[4]=nn.ModuleList([ResnetBlock(2560,1280,prv_skip=True),AttentionBlock(1280,5120)])	#17
        self.output_blocks[5]=nn.ModuleList([ResnetBlock(1920,1280,prv_skip=True),AttentionBlock(1280,5120),AttentionBlock_conv(1280,1)])	#19

        self.output_blocks[6]=nn.ModuleList([ResnetBlock(1920,640,prv_skip=True),AttentionBlock(640,2560)])
        self.output_blocks[7]=nn.ModuleList([ResnetBlock(1280,640,prv_skip=True),AttentionBlock(640,2560)])
        self.output_blocks[8]=nn.ModuleList([ResnetBlock(960, 640,prv_skip=True),AttentionBlock(640,2560),AttentionBlock_conv(640,1)])

        self.output_blocks[9]=nn.ModuleList([ResnetBlock(960,320,prv_skip=True),AttentionBlock(320,1280)])
        self.output_blocks[10]=nn.ModuleList([ResnetBlock(640,320,prv_skip=True),AttentionBlock(320,1280)])
        self.output_blocks[11]=nn.ModuleList([ResnetBlock(640,320,prv_skip=True),AttentionBlock(320,1280)])

        # out
        self.out_0 = nn.GroupNorm(num_groups=32,num_channels=320,eps=0.000010)
        self.out_2 = nn.Conv2d(bias=True, dilation=(1,1), groups=1, in_channels=320, kernel_size=(3,3), out_channels=4, padding=(1,1), padding_mode=config.pad, stride=(1,1))

    def time_embedding(
        self,
        t
    ):
        v_5 = t.unsqueeze(1) * self.freqs
        return F.silu(self.time_embed_2(F.silu(self.time_embed_0(torch.cat((torch.cos(v_5), torch.sin(v_5)), dim=-1)))))

    def forward_crossattn(
        self,
        x,
        t,
        context_k,
        context_v=None
    ):
        if context_v is None:
          context_v=context_k
        # 1. time
        emb = self.time_embedding(t)
        hs=[None]*12

        h0 = self.input_blocks_0_0(x)
        hs[0]=h0
        h1=callRA(self.input_blocks[1],h0,emb,context_k,context_v)
        hs[1]=h1
        h2=callRA(self.input_blocks[2],h1,emb,context_k,context_v)
        hs[2]=h2
        h3=config.ckp(self.input_blocks[3][0].forward,h2)
        hs[3]=h3
        h4=callRA(self.input_blocks[4],h3,emb,context_k,context_v)
        hs[4]=h4
        h5=callRA(self.input_blocks[5],h4,emb,context_k,context_v)
        hs[5]=h5
        h6=config.ckp(self.input_blocks[6][0].forward,h5)
        hs[6]=h6
        h7=callRA(self.input_blocks[7],h6,emb,context_k,context_v)
        hs[7]=h7
        h8=callRA(self.input_blocks[8],h7,emb,context_k,context_v)
        hs[8]=h8
        h9=config.ckp(self.input_blocks[9][0].forward,h8)
        hs[9]=h9
        h10=config.ckp(self.input_blocks[10][0].forward,h9,emb)
        hs[10]=h10
        h11=config.ckp(self.input_blocks[11][0].forward,h10,emb)
        hs[11]=h11

        h=config.ckp(self.middle_block[0].forward,h11,emb)
        h=config.ckp(self.middle_block[1].forward,h,context_k,context_v)
        h=config.ckp(self.middle_block[2].forward,h,emb)
        return h,emb,hs

    def forward2(self, h, emb, context_k, h6, h7, h8, h9, h10, h11,context_v=None):
        if context_v is None:
          context_v=context_k
        h0 = torch.cat((h, h11), dim=1)
        h0 = torch.cat((config.ckp(self.output_blocks[0][0].forward,h0,emb), h10), dim=1)
        h0 = torch.cat((config.ckp(self.output_blocks[1][0].forward,h0,emb), h9), dim=1)
        h0 = torch.cat((callRC(self.output_blocks[2],h0,emb), h8), dim=1)
        h0 = torch.cat((callRA(self.output_blocks[3],h0,emb,context_k,context_v), h7), dim=1)
        h0 = torch.cat((callRA(self.output_blocks[4],h0,emb,context_k,context_v), h6), dim=1)
        h0 = callRAC(self.output_blocks[5],h0,emb,context_k,context_v)
        return h0

    def forward3(self, h, emb, context_k, h0, h1, h2, h3, h4, h5,context_v=None):
        if context_v is None:
          context_v=context_k
        hv = torch.cat((h, h5), dim=1)
        hv = torch.cat((callRA(self.output_blocks[6],hv,emb,context_k,context_v), h4), dim=1)
        hv = torch.cat((callRA(self.output_blocks[7],hv,emb,context_k,context_v), h3), dim=1)
        hv = torch.cat((callRAC(self.output_blocks[8],hv,emb,context_k,context_v), h2), dim=1)
        hv = torch.cat((callRA(self.output_blocks[9],hv,emb,context_k,context_v), h1), dim=1)
        hv = torch.cat((callRA(self.output_blocks[10],hv,emb,context_k,context_v), h0), dim=1)
        hv = callRA(self.output_blocks[11],hv,emb,context_k,context_v)
        return self.out_2(F.silu(self.out_0(hv)))


import random
from typing import TypeVar, List, Tuple

X = TypeVar('X')

def split_data(data: List[X], prob: float) -> Tuple[List[X], List[X]]:
    """split data into fractions [prob, 1-prob]

    Args:
        data (List[X]): _description_
        prob (float): _description_

    Returns:
        Tuple[List[X], List[X]]: _description_
    """
    data = data[:]
    random.shuffle(data)
    cut = int(len(data) * prob)
    return data[:cut], data[cut:]

data = [n for n in range(1000)]
train, test = split_data(data, .75)

assert len(train) == 750
assert len(test) == 250

assert sorted(train + test) == data

Y = TypeVar('Y')

def train_test_split(xs: List[X],
                     ys: List[Y],
                     test_pct: float = .25) -> Tuple[List[X], List[X],
                                                     List[Y], List[Y]]:
    """_summary_

    Args:
        xs (List[X]): _description_
        ys (List[Y]): _description_
        test_pct (float, optional): _description_. Defaults to .25.

    Returns:
        Tuple[List[X], List[X], List[Y], List[Y]]: _description_
    """
    idxs = [i for i in range(len(xs))]
    train_idxs, test_idxs = split_data(idxs, 1 - test_pct)
    return ([xs[i] for i in train_idxs],
            [xs[i] for i in test_idxs],
            [ys[i] for i in train_idxs],
            [ys[i] for i in test_idxs])

xs = [x for x in range(1000)]
ys = [2 * x for x in xs]

x_train, x_test, y_train, y_test = train_test_split(xs, ys, .25)

# Check that the proportions are correct
assert len(x_train) == len(y_train) == 750
assert len(x_test) == len(y_test) == 250

# Check that the corresponding data points are paired correctly
assert all(y == 2 * x for x, y in zip(x_train, y_train))
assert all(y == 2 * x for x, y in zip(x_test, y_test))

def accuracy(tp: int, fp: int, fn: int, tn: int) -> float:
    """_summary_

    Args:
        tp (int): _description_
        fp (int): _description_
        fn (_type_): _description_

    Returns:
        float: _description_
    """
    correct = tp + tn
    total = tp + fp + fn + tn
    return correct / total

assert accuracy(70, 4930, 13930, 981070) == .98114

def recall(tp: int, fp: int, fn: int, tn: int) -> float:
    return tp / (tp + fn)

assert recall(70, 4930, 13930, 981070) == 0.005

def precision(tp: int, fp: int, fn: int, tn: int) -> float:
    return tp / (tp + fp)

assert precision(70, 4930, 13930, 981070) == 0.014


"""
Analyze activation-based clustering and compare to weight-based
"""

import sys
import os
import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds
from sklearn.metrics.cluster import normalized_mutual_info_score
from scipy.stats import spearmanr, kendalltau, entropy
from sklearn.cross_decomposition import CCA
from pathos.multiprocessing import ProcessPool
import copy
import pickle
import time
from pathlib import Path
from classification_models.keras import Classifiers
from sacred import Experiment
from sacred.observers import FileStorageObserver
from src.utils import load_weights
from src.cnn.extractor import extract_cnn_weights_filters_as_units
from src.cnn import CNN_MODEL_PARAMS, CNN_VGG_MODEL_PARAMS
from src.generate_datasets import prep_imagenet_validation_data
from src.utils import (load_model2, suppress, all_logging_disabled, splitter, combine_ps,
                       chi2_categorical_test, compute_pvalue, imagenet_downsampled_dataset)
from src.spectral_cluster_model import (weights_array_to_cluster_quality, weights_to_graph,
    delete_isolated_ccs_refactored, compute_ncut, get_inv_avg_commute_time)
from src.pointers import DATA_PATHS

# set up some sacred stuff
activations_experiment = Experiment('activations_model')
activations_experiment.observers.append((FileStorageObserver.create('activations_runs')))

activations_cluster = Experiment('activations_clust')
activations_cluster.observers.append((FileStorageObserver.create('activations_runs2')))

RANDOM_STATE = 42

@activations_cluster.config
def my_config2():
    eigen_solver = 'arpack'
    assign_labels = 'kmeans'
    epsilon = 1e-8
    n_workers = 10
    n_outputs = 10
    corr_type = 'spearman'  # must be in ['kendall', 'pearson', 'spearman']
    n_samples = 0
    with_shuffle = False
    exclude_inputs = True
    local = False
    local_layerwise = True
    lucid = False

def load_train_data(model_path, dataset_name='', max_size=5000):

    if 'vgg' in model_path.lower():
        width, height = 32, 32
        depth = 3
        if not dataset_name:
            dataset_name = 'cifar10_full'
    else:
        if not dataset_name:
            dataset_name = 'mnist'
        width, height = 28, 28
        depth = 1
    data_path = DATA_PATHS[dataset_name]
    size = width * height

    try:
        with open(data_path, 'rb') as f:
            dataset = pickle.load(f)
    except:
        with open('.'+data_path, 'rb') as f:
            dataset = pickle.load(f)
    X_train = dataset['X_train']
    y_train = tf.keras.utils.to_categorical(dataset['y_train'])
    assert y_train.shape[-1] == 10

    if X_train.shape[0] > max_size:
        rdm_idxs = np.random.choice(X_train.shape[0], size=(max_size,), replace=False)
        X_train = X_train[rdm_idxs]
        y_train = y_train[rdm_idxs]

    if (X_train.min() == 0 and X_train.min() == 0 and X_train.max() <= 255 and X_train.max() >= 250):
        X_train = X_train / 255
    else:
        raise ValueError('X_train and X_test should be in the range [0, 255] or [0, 1].')
    assert X_train.min() == 0
    assert X_train.max() <= 1
    assert X_train.max() >= 0.95

    if 'cnn' in model_path:
        if 'stacked' in dataset_name:
            X_train = np.transpose(X_train, (0, 2, 3, 1))
        else:
            X_train = X_train.reshape([-1, height, width, depth])

        assert X_train.shape[-3:] == (height, width, depth)

    elif 'mlp' in model_path:
        X_train = X_train.reshape([-1, size])

    return X_train, y_train

def get_corr_adj(activations_mat, corr_type):

    # kendall has less gross error sensitivity and slightly smaller empirical variance
    # https://www.tse-fr.eu/sites/default/files/medias/stories/SEMIN_09_10/STATISTIQUE/croux.pdf
    # but spearman is much faster to compute

    # get the pearson, kendall, and spearman r^2 values from the activations matrix where rows=units, cols=examples
    n_units = activations_mat.shape[0]
    if corr_type == 'pearson':
        corr_mat = np.corrcoef(activations_mat, rowvar=True)
    elif corr_type == 'spearman':
        corr_mat, _ = spearmanr(activations_mat, axis=1)  # pearson r of ranks
    elif corr_type == 'kendall':
        corr_mat = np.diag(np.ones(n_units))  # n_concordant_pair - n_discordant_pair / n_choose_2
        for i in range(n_units):
            for j in range(i):
                kendall_tau, _ = kendalltau(activations_mat[i], activations_mat[j])
                corr_mat[i, j] = kendall_tau
                corr_mat[j, i] = kendall_tau
    else:
        raise ValueError("corr_type must be in ['kendall', 'pearson', 'spearman']")
    assert corr_mat.shape == (n_units, n_units)

    corr_adj = corr_mat**2
    np.fill_diagonal(corr_adj, 0)

    corr_adj = np.nan_to_num(corr_adj)
    corr_adj[corr_adj < 0] = 0
    corr_adj[corr_adj > 1] = 1

    return corr_adj

def shuffle_and_cluster_activations(n_samples, corr_adj, n_clusters,
                                    eigen_solver, assign_labels, epsilon):

    n_units = corr_adj.shape[0]
    shuff_ncuts = []

    time_str = str(time.time())
    dcml_place = time_str.index('.')
    time_seed = int(time_str[dcml_place + 1:])
    np.random.seed(time_seed)

    for _ in range(n_samples):
        # shuffle all edges
        corr_adj_shuff = np.zeros((n_units, n_units))
        upper_tri = np.triu_indices(n_units, 1)
        edges = corr_adj[upper_tri]
        np.random.shuffle(edges)
        corr_adj_shuff[upper_tri] = edges
        corr_adj_shuff = np.maximum(corr_adj_shuff, corr_adj_shuff.T)

        # cluster
        shuffled_ncut, _ = weights_array_to_cluster_quality(None, corr_adj_shuff, n_clusters,
                                                            eigen_solver, assign_labels, epsilon,
                                                            is_testing=False)
        shuff_ncuts.append(shuffled_ncut)

    return np.array(shuff_ncuts)

def do_clustering_activations(network_type, activations_path, activations_mask_path, local, local_layerwise,
                              corr_type, n_clusters, n_inputs, n_outputs, exclude_inputs, eigen_solver,
                              assign_labels, epsilon, n_samples, with_shuffle, n_workers):

    with open(activations_path, 'rb') as f:
        activations = pickle.load(f)
    with open(activations_mask_path, 'rb') as f:
        activations_mask = pickle.load(f)

    if 'cnn' in network_type:  # for the cnns, only look at conv layers
        # if 'stacked' in str(activations_path).lower():
        #     n_in = n_inputs * 2
        # else:
        #     n_in = n_inputs
        # cnn_params = CNN_VGG_MODEL_PARAMS if 'vgg' in network_type else CNN_MODEL_PARAMS
        # n_conv_filters = sum([cl['filters'] for cl in cnn_params['conv']])
        # n_start = np.sum(activations_mask[:n_in])
        # n_stop = n_start + np.sum(activations_mask[n_in: n_in+n_conv_filters])
        # activations = activations[n_start:n_stop, :]
        # activations_mask = activations_mask[n_in: n_in+n_conv_filters]
        pass
    elif exclude_inputs:
        n_in = n_inputs
        n_start = np.sum(activations_mask[:n_in])
        activations = activations[n_start: -n_outputs, :]
        activations_mask = activations_mask[n_in: -n_outputs]

    if local:

        assert exclude_inputs

        if 'cnn' in str(activations_path).lower():
            cnn_params = CNN_VGG_MODEL_PARAMS if 'vgg' in str(activations_path).lower() else CNN_MODEL_PARAMS
            layer_sizes = [cl['filters'] for cl in cnn_params['conv']]
        else:  # it's an mlp
            layer_sizes = [256, 256, 256, 256]
        mask_layerwise = list(splitter(activations_mask, layer_sizes))
        masked_sizes = [np.sum(ml) for ml in mask_layerwise]
        acts_layerwise = list(splitter(activations, masked_sizes))

        if local_layerwise:
            corr_adj = get_corr_adj(activations, corr_type)
            _, pre_labels = weights_array_to_cluster_quality(None, corr_adj, n_clusters, eigen_solver,
                                                             assign_labels, epsilon, is_testing=False)
            labels = -1 * np.ones(activations_mask.shape)
            labels[activations_mask] = pre_labels
            labels_in_layers = [np.array(lyr_labels) for lyr_labels in list(splitter(labels, layer_sizes))]
            n_clusters_per_layer = []
            for ll in labels_in_layers:
                ll = ll[ll != -1]
                n_clusters_per_layer.append(len(np.unique(ll)))
        else:
            n_clusters_per_layer = [n_clusters] * len(layer_sizes)

        unshuffled_ncut, pre_labels = [], []
        for layer_i, al in enumerate(acts_layerwise):
            corr_adj = get_corr_adj(np.array(al), corr_type)
            un, cl = weights_array_to_cluster_quality(None, corr_adj, n_clusters_per_layer[layer_i], eigen_solver,
                                                      assign_labels, epsilon, is_testing=False)
            unshuffled_ncut.append(un)
            pre_labels.append(cl)

        unshuffled_ncut = sum(unshuffled_ncut) / len(unshuffled_ncut)
        pre_labels = np.concatenate(pre_labels)

    else:
        corr_adj = get_corr_adj(activations, corr_type)
        unshuffled_ncut, pre_labels = weights_array_to_cluster_quality(None, corr_adj, n_clusters, eigen_solver,
                                                                       assign_labels, epsilon, is_testing=False)

    clustering_labels = -1 * np.ones(activations_mask.shape)
    clustering_labels[activations_mask] = pre_labels

    ave_in_out = (1 - unshuffled_ncut / n_clusters) / (2 * unshuffled_ncut / n_clusters)
    ent = entropy(clustering_labels)
    true_labels = clustering_labels[clustering_labels >= 0]
    label_proportions = np.bincount(true_labels.astype(int)) / len(true_labels)
    result = {'activations': activations, 'mask': activations_mask,
              'ncut': unshuffled_ncut, 'ave_in_out': ave_in_out, 'labels': clustering_labels,
              'label_proportions': label_proportions, 'entropy': ent}

    if with_shuffle and not local:  # don't do this if local
        n_samples_per_worker = n_samples // n_workers
        function_argument = (n_samples_per_worker, corr_adj,
                             n_clusters, eigen_solver,
                             assign_labels, epsilon)
        if n_workers == 1:
            print('No Pool! Single Worker!')
            shuff_ncuts = shuffle_and_cluster_activations(*function_argument)

        else:
            print(f'Using Pool! Multiple Workers! {n_workers}')

            workers_arguments = [[copy.deepcopy(arg) for _ in range(n_workers)]
                                 for arg in function_argument]

            with ProcessPool(nodes=n_workers) as p:
                shuff_ncuts_results = p.map(shuffle_and_cluster_activations,
                                            *workers_arguments)

            shuff_ncuts = np.concatenate(shuff_ncuts_results)

        shuffled_n_samples = len(shuff_ncuts)
        shuffled_mean = np.mean(shuff_ncuts, dtype=np.float64)
        shuffled_stdev = np.std(shuff_ncuts, dtype=np.float64)
        print('BEFORE', np.std(shuff_ncuts))
        percentile = compute_pvalue(unshuffled_ncut, shuff_ncuts)
        print('AFTER', np.std(shuff_ncuts))
        z_score = (unshuffled_ncut - shuffled_mean) / shuffled_stdev

        result.update({'n_samples': shuffled_n_samples,
                       'mean': shuffled_mean,
                       'stdev': shuffled_stdev,
                       'z_score': z_score,
                       'percentile': percentile})
    return result

@activations_cluster.automain
def activations_clustering(activations_path, activations_mask_path, local, local_layerwise, n_clusters,
                           corr_type, exclude_inputs, n_outputs, n_samples, with_shuffle, eigen_solver,
                           assign_labels, epsilon, n_workers, lucid):

    lower_path = str(activations_path).lower()
    if 'cnn_vgg' in lower_path:
        network_type = 'cnn'
        n_inputs = 32**2 * 3
    elif 'cnn' in lower_path:
        network_type = 'cnn_vgg'
        n_inputs = 28**2
    else:
        network_type = 'mlp'
        n_inputs = 28**2
    if lucid and not 'cnn_vgg' in lower_path:
        n_inputs *= 3

    act_cluster_results = do_clustering_activations(network_type, activations_path, activations_mask_path,
                                                    local, local_layerwise, corr_type, n_clusters, n_inputs,
                                                    n_outputs, exclude_inputs, eigen_solver, assign_labels,
                                                    epsilon, n_samples, with_shuffle, n_workers)
    labels = act_cluster_results['labels']
    mask = act_cluster_results['mask']
    n_total = len(mask)
    prop_dead = np.sum(mask) / n_total
    # labels = np.zeros(n_total)
    # labels[mask] = masked_labels
    # labels[np.logical_not(mask)] = -1

    metrics = {'ncut': act_cluster_results['ncut'], 'prop_dead': prop_dead}

    return {'labels': labels, 'metrics': metrics}

def get_max_act_images(model_path, savepath, labels, batch_size=256, n_top=10,
                       min_size=5, max_prop=0.75, n_random=19):

    with suppress(), all_logging_disabled():
        model = load_model2(model_path)
    model_path = str(model_path).lower()
    dset_X, dset_y = load_train_data(model_path)

    if 'mlp' in model_path:
        layer_sizes = [256, 256, 256, 256]
    else:
        cnn_params = CNN_VGG_MODEL_PARAMS if 'vgg' in model_path else CNN_MODEL_PARAMS
        layer_sizes = [cl['filters'] for cl in cnn_params['conv']]
    if 'vgg' in model_path:
        width = 32
        height = 32
    else:
        width = 28
        height = 28
    labels_in_layers = [np.array(lyr_labels) for lyr_labels in list(splitter(labels, layer_sizes))]

    in_dims = width * height
    if len(dset_X.shape) == 4:
        in_dims *= dset_X.shape[-1]
    n_data = dset_X.shape[0]
    n_data -= n_data % batch_size

    inp = model.input  # input placeholder
    if 'cnn' in model_path:
        # outputs = [layer.input for layer in model.layers if 'conv2d' in layer._name]
        pre_relus = [layer.output.op.inputs[0] for layer in model.layers if 'conv2d' in layer._name]
    else:
        # outputs = [layer.input for layer in model.layers if 'dense' in layer._name]
        pre_relus = [layer.output.op.inputs[0] for layer in model.layers if 'dense' in layer._name]

    functor = tf.keras.backend.function([inp, tf.keras.backend.learning_phase()], pre_relus)

    activations_single_batch = functor([dset_X[:batch_size], 0])
    n_layers = len(activations_single_batch)
    activations_dims = [] if 'cnn' in model_path else [(in_dims,)]
    for lyr_single in activations_single_batch:
        shp = np.squeeze(lyr_single).shape
        activations_dims.append((shp[-1],))  # each filter is a unit if a cnn

    activations = [np.zeros(((n_data,) + lyr_dims)) for lyr_dims in activations_dims]

    if 'cnn' in model_path:
        for test_i in range(0, n_data, batch_size):  # iterate through test set
            acts_batch = functor([dset_X[test_i: test_i + batch_size], 0])  # False for eval
            for lyr in range(n_layers):
                activations[lyr][test_i: test_i + batch_size] = np.linalg.norm(acts_batch[lyr], ord=1, axis=(1, 2))
    else:
        for test_i in range(0, n_data, batch_size):  # iterate through test set
            batch_in = dset_X[test_i: test_i + batch_size]
            batch_in = np.reshape(batch_in, (batch_size, -1))
            activations[0][test_i: test_i + batch_size] = batch_in
            acts_batch = functor([dset_X[test_i: test_i + batch_size], 0])  # False for eval
            for lyr in range(n_layers):
                activations[lyr + 1][test_i: test_i + batch_size] = acts_batch[lyr]

    all_act_mat = np.abs(np.hstack(activations).T)  # after taking .T, each row is a unit and each col an example
    all_act_split = [np.array(lyr_acts) for lyr_acts in splitter(all_act_mat, layer_sizes)]

    results = {}
    percentiles = []
    effect_sizes = []
    for layer_i in range(len(labels_in_layers)):
        layer_labels = labels_in_layers[layer_i]
        layer_size = len(layer_labels)
        for label_i in np.sort(np.unique(layer_labels)):
            sm_size = np.sum(layer_labels == label_i)
            if sm_size < min_size or sm_size > max_prop * len(layer_labels):
                continue
            sm_sums = np.sum(all_act_split[layer_i][layer_labels == label_i], axis=0)
            sm_max_i = np.argsort(sm_sums)[-(n_top+1):]
            max_ims = [np.reshape(dset_X[maxi], (width, height, -1)) for maxi in sm_max_i]

            max_labels = np.argmax(dset_y[sm_max_i], axis=1)
            max_labels[-1] = n_top
            rdm_max_labels = []
            rdm_ims = None
            for rdm_i in range(n_random):  # random max results
                rdm_idxs = np.random.choice(np.array(range(layer_size)), size=sm_size, replace=False)
                rdm_sums = np.sum(all_act_split[layer_i][rdm_idxs], axis=0)
                rdm_max_i = np.argsort(rdm_sums)[-(n_top+1):]
                rdm_max_sample = np.argmax(dset_y[rdm_max_i], axis=1)
                rdm_max_sample[-1] = n_top
                rdm_max_labels.append(rdm_max_sample)
                if rdm_i == 0:
                    rdm_ims = [np.reshape(dset_X[maxi], (width, height, -1)) for maxi in rdm_max_i]
            true_props = np.bincount(max_labels)[:-1] / n_top
            rdm_props = [np.bincount(rdm_max)[:-1] / n_top for rdm_max in rdm_max_labels]
            true_entropy = entropy(true_props)
            random_entropies = np.array([entropy(rdm_prop) for rdm_prop in rdm_props])
            percentiles.append(compute_pvalue(true_entropy, random_entropies))
            effect_sizes.append(np.mean(random_entropies)/true_entropy)

            results[f'layer_{layer_i}_label_{int(label_i)}'] = {'size': sm_size,
                                                                'ims': max_ims,
                                                                'rdm_ims': rdm_ims}
    percentiles = np.array(percentiles)
    effect_sizes = np.array(effect_sizes)
    results['fisher_fisher_p'] = combine_ps(percentiles, n_random)
    results['chi2_fisher_p'] = chi2_categorical_test(percentiles, n_random)
    results['mean_effect_size'] = np.mean(effect_sizes)

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

    return results

def get_labels_imagenet_activations(network, n_clusters, local=False, norm=1,
                                    eigen_solver='arpack', assign_labels='kmeans', n_samples=2000,
                                    batch_size=32, corr_type='spearman', epsilon=1e-8,
                                    data_dir='/project/nn_clustering/datasets/imagenet2012',
                                    val_tar='ILSVRC2012_img_val.tar'):

    net, preprocess = Classifiers.get(network)
    model = net((224, 224, 3), weights='imagenet')
    inp = model.input
    # outputs = [layer.output for layer in model.layers if 'conv' in layer.name]
    if 'resnet' in network:
        conv_idxs = [model.layers.index(cl) for cl in model.layers
                     if '.conv2d' in str(type(cl)).lower()]
        pre_relus = [model.layers[ci].output.op.inputs[0] for ci in conv_idxs[1:]]
        pre_relus.append(model.layers[conv_idxs[-1]].output)
    elif 'vgg' in network:
        pre_relus = [layer.output.op.inputs[0] for layer in model.layers if 'conv' in layer.name]
    else:
        raise ValueError

    functor = tf.keras.backend.function([inp, tf.keras.backend.learning_phase()], pre_relus)

    data_path = Path(data_dir)
    tfrecords = list(data_path.glob('*validation.tfrecord*'))
    if not tfrecords:
        prep_imagenet_validation_data(data_dir, val_tar)  # this'll take a sec
    imagenet = tfds.image.Imagenet2012()  # dataset builder object
    imagenet._data_dir = data_dir
    val_dataset_object = imagenet.as_dataset(split='validation')  # datast object
    dataset, _ = imagenet_downsampled_dataset(val_dataset_object, preprocess, n_images=n_samples)

    activations_single_batch = functor([dataset[:batch_size], 0])  # 0 for eval
    n_layers = len(activations_single_batch)
    activations_dims = []
    for lyr_single in activations_single_batch:
        shp = np.squeeze(lyr_single).shape
        activations_dims.append((shp[-1],))  # each filter is a unit if a cnn

    activations = [np.zeros(((n_samples,) + lyr_dims)) for lyr_dims in activations_dims]

    for test_i in range(0, n_samples, batch_size):  # iter through test set
        acts_batch = functor([dataset[test_i: test_i+batch_size], 0])
        for lyr in range(n_layers):
            activations[lyr][test_i: test_i + batch_size] = np.linalg.norm(acts_batch[lyr], ord=norm, axis=(1, 2))
    del model
    del dataset

    if local:
        masks_layerwise, unshuffled_ncut, clustering_labels = [], [], []
        for am in activations:
            col_stds = np.std(am, axis=0)
            act_mask = col_stds != 0
            masks_layerwise.append(act_mask)
            acts = am.T[act_mask]
            # activations_layerwise.append(acts)

            corr_adj = get_corr_adj(acts, corr_type)
            un, cl = weights_array_to_cluster_quality(None, corr_adj, n_clusters, eigen_solver,
                                                      assign_labels, epsilon, is_testing=False)
            unshuffled_ncut.append(un)
            clustering_labels.append(cl)
        del activations
        unshuffled_ncut = sum(unshuffled_ncut) / len(unshuffled_ncut)
        clustering_labels = np.concatenate(clustering_labels)
        activations_mask = np.concatenate(masks_layerwise)

    else:
        all_act_mat = np.hstack(activations).T  # after taking .T, each row is a unit and each col an example
        row_stds = np.std(all_act_mat, axis=1)
        activations_mask = row_stds != 0
        activations = all_act_mat[activations_mask]
        del all_act_mat
        corr_adj = get_corr_adj(activations, corr_type)
        del activations
        unshuffled_ncut, clustering_labels = weights_array_to_cluster_quality(None, corr_adj, n_clusters,
                                                                              eigen_solver, assign_labels,
                                                                              epsilon, is_testing=False)

    n_total = len(activations_mask)
    prop_dead = np.sum(activations_mask) / n_total
    labels = np.zeros(n_total)
    labels[activations_mask] = clustering_labels
    labels[np.logical_not(activations_mask)] = -1

    print(f'{network}: ncut: {unshuffled_ncut}, prop_dead: {prop_dead}')
    sys.stdout.flush()

    with open(data_dir + f'/{network}_activations_local={local}_k={n_clusters}.pkl', 'wb') as f:
        pickle.dump(labels, f)

    return labels

def get_max_act_images_imagenet(model_tag, savepath, use_activations, n_top=10, norm=1, n_samples=4000,
                                batch_size=32, min_size=5, max_prop=0.75,
                                infodir='/project/nn_clustering/results/',
                                data_dir='/project/nn_clustering/datasets/imagenet2012',
                                val_tar='ILSVRC2012_img_val.tar'):

    # with suppress(), all_logging_disabled():

    if use_activations:
        with open(infodir + model_tag + '_act_clustering_info.pkl', 'rb') as f:
            clustering_info = pickle.load(f)
    else:
        with open(infodir + model_tag + '_clustering_info.pkl', 'rb') as f:
            clustering_info = pickle.load(f)
    labels_in_layers = [np.array(lyr_labels) for lyr_labels in clustering_info['labels']]
    layer_sizes = [len(labels) for labels in labels_in_layers]

    data_path = Path(data_dir)
    tfrecords = list(data_path.glob('*validation.tfrecord*'))
    if not tfrecords:
        prep_imagenet_validation_data(data_dir, val_tar)  # this'll take a sec

    net, preprocess = Classifiers.get(model_tag)
    model = net((224, 224, 3), weights='imagenet')
    inp = model.input
    # outputs = [layer.output for layer in model.layers if 'conv' in layer.name]
    pre_relus = [layer.output.op.inputs[0] for layer in model.layers if 'conv' in layer.name]
    functor = tf.keras.backend.function([inp, tf.keras.backend.learning_phase()], pre_relus)

    data_path = Path(data_dir)
    tfrecords = list(data_path.glob('*validation.tfrecord*'))
    if not tfrecords:
        prep_imagenet_validation_data(data_dir, val_tar)  # this'll take a sec
    imagenet = tfds.image.Imagenet2012()  # dataset builder object
    imagenet._data_dir = data_dir
    val_dataset_object = imagenet.as_dataset(split='validation', shuffle_files=True)  # datast object
    dataset, dataset_y = imagenet_downsampled_dataset(val_dataset_object, preprocess,
                                                      n_images=n_samples)

    activations_single_batch = functor([dataset[:batch_size], 0])  # 0 for eval
    n_layers = len(activations_single_batch)
    activations_dims = []
    for lyr_single in activations_single_batch:
        shp = np.squeeze(lyr_single).shape
        activations_dims.append((shp[-1],))  # each filter is a unit if a cnn

    activations = [np.zeros(((n_samples,) + lyr_dims)) for lyr_dims in activations_dims]

    for test_i in range(0, n_samples, batch_size):  # iter through test set
        acts_batch = functor([dataset[test_i: test_i + batch_size], 0])
        for lyr in range(n_layers):
            activations[lyr][test_i: test_i + batch_size] = np.linalg.norm(acts_batch[lyr], ord=norm, axis=(1, 2))
    del model

    all_act_mat = np.abs(np.hstack(activations).T)  # after taking .T, each row is a unit and each col an example
    assert len(all_act_mat) == sum([len(layer_labels) for layer_labels in labels_in_layers]), \
        f'all_act_mat len {len(all_act_mat)} not compatible with layer_label lens' \
        f'{[len(layer_labels) for layer_labels in labels_in_layers]} with layer_sizes {layer_sizes}'
    all_act_split = [np.array(lyr_acts) for lyr_acts in list(splitter(all_act_mat, layer_sizes))]

    print(f'labels_in_layers: {[len(layer) for layer in labels_in_layers]}')
    print(f'all_act_split: {[len(layer) for layer in all_act_split]}')

    results = {}
    for layer_i in range(len(labels_in_layers)):
        assert len(labels_in_layers[layer_i]) == len(all_act_split[layer_i])
        layer_labels = labels_in_layers[layer_i]
        layer_size = len(layer_labels)
        for label_i in np.sort(np.unique(layer_labels)):
            sm_size = np.sum(layer_labels == label_i)
            if sm_size < min_size or sm_size > max_prop * len(layer_labels):
                continue
            sm_sums = np.sum(all_act_split[layer_i][layer_labels == label_i], axis=0)
            sm_max_i = np.argsort(sm_sums)[-n_top:]
            rdm_idxs = np.random.choice(np.array(range(layer_size)), size=sm_size, replace=False)
            rdm_sums = np.sum(all_act_split[layer_i][rdm_idxs], axis=0)
            rdm_max_i = np.argsort(rdm_sums)[-n_top:]

            # max_labels = dataset_y[sm_max_i]
            # rdm_max_labels = []
            # for _ in range(n_random):  # random max results
            #     rdm_idxs = np.random.choice(np.array(range(layer_size)), size=sm_size, replace=False)
            #     rdm_sums = np.sum(all_act_split[layer_i][rdm_idxs], axis=0)
            #     rdm_max_i = np.argsort(rdm_sums)[-n_top:]
            #     rdm_max_labels.append(dataset_y[rdm_max_i])

            results[f'layer_{layer_i}_label_{int(label_i)}'] = {'size': sm_size,
                                                                'ims': dataset[sm_max_i],
                                                                'rdm_ims': dataset[rdm_max_i]}
    del dataset

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

    return results


import heapq
def solution(n, works):
    heap = [-el for el in works]
    heapq.heapify(heap)

    for i in range(n):
        work = heapq.heappop(heap)
        if work == 0: break
        work += 1
        heapq.heappush(heap, work)

    answer = 0

    for el in heap:
        answer += el ** 2

    return answer

프로그래머스/lv3/12927. 야근 지수/야근 지수.py