#! /usr/bin/python3
#-*-coding: utf-8-*-

def add_rxn(name, D_mets, model, rev=True): # this function would be defined to add reactions to the model
    r_name = name
    r_obj = cobra.Reaction(rname)
    r_obj.name = r_name
    r_obj.id = r_name
    model.add_reaction(r_obj)
    r_obj.add_metabolites(D_mets)
    r_obj.objective_coefficient = 0
    if rev:
        r_obj.bounds = (-inf,inf)
    else:
        r_obj.bounds = (0,inf)

def set_fixed_flux(r_id, val, model): # function probably defined to define fixed flux => consider reactions and bounds
    r_obj = model.reactions.get_by_id(r_id) # search the reaction by id in the model (get_by_id)
    r_obj.bounds = (val,val) # fix the limits of the reaction, which are the same for both the inferior and the superior limits
    # => Fixed limits

def set_bounds(r_id, val_tuple, model): # function defined to define bounds for reactions ; retrieve the reaction from its id
    r_obj = model.reactions.get_by_id(r_id) # creation of a summary
    r_obj.bounds = val_tuple # assign bounds (= superior and inferior limits of the reaction)

def set_fixed_flux_ratio(r_dict, model): # function defined to fix flux ratio
    if len(r_dict) == 2:
        r_id1 = list(r_dict.keys())[0]
        r_obj1 = model.reactions.get_by_id(r_id1)
        r_v1 = list(r_dict.values())[0]
        r_id2 = list(r_dict.keys())[1]
        r_obj2 = model.reactions.get_by_id(r_id2)
        r_v2 = list(r_dict.values())[1]
        const = model.problem.Constraint(r_v1 * r_obj2.flux_expression - r_v2 * r_obj1.flux_expression, lb = 0, ub = 0)
        model.add_cons_vars(const)
        return const


import json
import os

from account import Account

home_path = os.getenv("HOME")
config = json.load(open(os.path.join(home_path, ".config", "revChatGPT", "config.json")))
cache = json.load(open(os.path.join(home_path, ".cache", "revChatGPT", "config.json")))

# 从配置读取 token
session_token = config['accounts'][0]['session_token']
access_token = cache['access_token']

account = Account("fkxxyz", "fkxxyz@xxxx.com", "xxxxxxxx", session_token, "/tmp", config['proxy'])

# 尝试用 access_token 访问
is_logged_in = account.login_with_session_info()

# 用 session_token 登录得到 access_token
if not is_logged_in:
    is_logged_in = account.login()


import pandas as pd
from absplice.utils import get_abs_max_rows

def variant_to_string(df):
    chrom = str(df['#Chrom'])
    if 'chr' not in chrom:
        chrom = 'chr' + chrom
    return chrom + ':' + str(df['Pos']) + ':' + df['Ref'] + '>' + df['Alt']

df_pred = pd.read_csv(snakemake.input['model'], sep='\t', skiprows=1)
df_pred = df_pred.rename(columns={'GeneID': 'gene_id', 'GeneName': 'gene_name'})
df_pred = df_pred[~df_pred['gene_id'].isna()]

df_pred['variant'] = df_pred.apply(lambda x: variant_to_string(x), axis=1)
df_pred = df_pred[['variant', 'gene_id', 'gene_name', 'RawScore', 'PHRED']]

# join sample info
if 'var_samples_df' in snakemake.input.keys():
    df_vcf_annotation = pd.read_csv(snakemake.input['var_samples_df'])

    df_pred = df_pred.set_index('variant').join(
        df_vcf_annotation.set_index('variant'),
        how='inner')\
        .reset_index()

df_pred.to_parquet(
    snakemake.output['model_postprocess'],
    index=False)


from random import random

import toga
from colosseum import CSS

def build(app):
    data = []
    for x in range(5):
        data.append([str(x) for x in range(5)])

    label = toga.Label('No row selected.')

    def selection_handler(widget, row):
        label.text = 'You selected row: {}'.format(row) if row is not None else 'No row selected'

    table = toga.Table(headings=['heading_{}'.format(x) for x in range(5)],
                       data=data,
                       style=CSS(flex=1),
                       on_select=selection_handler)

    def insert_handler(widget):
        table.data.insert(0, [str(round(random() * 100)) for _ in range(5)])
        table._impl.refresh()
        print('Rows', len(table.data.data))

    def delete_handler(widget):
        if len(table.data.data) > 0:
            table.data.remove(table.data.data[0])
            table._impl.refresh()
        else:
            print('Table is empty!')

    btn_style = CSS(flex=1)
    btn_insert = toga.Button('Insert Row', on_press=insert_handler, style=btn_style)
    btn_delete = toga.Button('Delete Row', on_press=delete_handler, style=btn_style)
    btn_box = toga.Box(children=[btn_insert, btn_delete], style=CSS(flex_direction='row'))

    box = toga.Box(children=[table, btn_box, label], style=CSS(flex=1, flex_direction='column', padding=10))
    return box

def main():
    return toga.App('Test Table', 'org.pybee.helloworld', startup=build)

if __name__ == '__main__':
    app = main()
    app.main_loop()


import torch
from torch import nn

__all__ = [
    '_CONV_DICT',
    '_CONV_TRANS_DICT',
    '_AVG_POOL_DICT',
    '_MAX_POOL_DICT',
    '_NORM_DICT',
    '_REFLECTION_PAD_DICT',
    '_CENTER_CROP_DICT',
    '_ACTIVATION_DICT',
    'activation_from_str'
]

def center_crop_1d(layer: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
    _, _, layer_width = layer.size()
    _, _, target_width = target.size()
    assert layer_width >= target_width
    diff_x = (layer_width - target_width) // 2
    return layer[:, :,
        diff_x:(diff_x + target_width)]

def center_crop_2d(layer: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
    _, _, layer_height, layer_width = layer.size()
    _, _, target_height, target_width = target.size()
    assert layer_height >= target_height
    assert layer_width >= target_width
    diff_x = (layer_width - target_width) // 2
    diff_y = (layer_height - target_height) // 2
    return layer[:, :,
        diff_y:(diff_y + target_height),
        diff_x:(diff_x + target_width)]

def center_crop_3d(layer: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
    _, _, layer_depth, layer_height, layer_width = layer.size()
    _, _, target_depth, target_height, target_width = layer.size()
    assert layer_depth >= target_depth
    assert layer_height >= target_height
    assert layer_width >= target_width
    diff_x = (layer_width - target_width) // 2
    diff_y = (layer_height - target_height) // 2
    diff_z = (layer_depth - target_depth) // 2
    return layer[:, :,
        diff_z:(diff_z + target_depth),
        diff_y:(diff_y + target_height),
        diff_x:(diff_x + target_width)]

_CONV_DICT = {
    1: nn.Conv1d,
    2: nn.Conv2d,
    3: nn.Conv3d
}

_CONV_TRANS_DICT = {
    1: nn.ConvTranspose1d,
    2: nn.ConvTranspose2d,
    3: nn.ConvTranspose3d
}

_AVG_POOL_DICT = {
    1: nn.AvgPool1d,
    2: nn.AvgPool2d,
    3: nn.AvgPool3d
}

_MAX_POOL_DICT = {
    1: nn.MaxPool1d,
    2: nn.MaxPool2d,
    3: nn.MaxPool3d
}

_NORM_DICT = {
    'batch': {
        1: nn.BatchNorm1d,
        2: nn.BatchNorm2d,
        3: nn.BatchNorm3d
    }
}

_REFLECTION_PAD_DICT = {
    1: nn.ReflectionPad1d,
    2: nn.ReflectionPad2d
}

_CENTER_CROP_DICT = {
    1: center_crop_1d,
    2: center_crop_2d,
    3: center_crop_3d
}

_ACTIVATION_DICT = {
    'relu': nn.ReLU(),
    'elu': nn.ELU(),
    'selu': nn.SELU(),
    'sigmoid': nn.Sigmoid(),
    'leaky_relu': nn.LeakyReLU(),
    'softplus': nn.Softplus()
}

def activation_from_str(activation_str: str):
    return _ACTIVATION_DICT[activation_str]


from django.utils import timezone
from rest_framework import status
from rest_framework.generics import CreateAPIView, RetrieveUpdateDestroyAPIView
from rest_framework.response import Response
from rest_framework.views import APIView

from logistics.models import Logistic, LogisticRate
from receivers.models import Receiver
from senders.models import Sender
from users.authentications import CustomTokenAuthentication
from users.permissions import CustomPermission

from . import models, serializers

class OrderCreateView(APIView):
    authentication_classes = [CustomTokenAuthentication]
    permission_classes = [CustomPermission]

    def post(self, request, *args, **kwargs):
        user, _ = self.authentication_classes[0]().authenticate(request)

        serializer = serializers.OrderSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        order_data = serializer.create(serializer.validated_data)

        order_db = models.Order()
        updated_order_data = order_db.update_order(order_data["id"], {"user_id": user["id"]})

        logistics_db = Logistic()
        logistic = logistics_db.get_logistic("id", updated_order_data["logistic_id"])
        if not logistic:
            return Response({"error": "Logistics Not Available"}, status=status.HTTP_400_BAD_REQUEST)

        receiver_id = updated_order_data["receiver_id"]
        sender_id = updated_order_data["sender_id"]

        receiver_db = Receiver()
        receiver = receiver_db.get_receiver("id", receiver_id)

        to_region = receiver["region"]

        sender_db = Sender()
        sender = sender_db.get_sender("id", sender_id)

        from_region = sender["region"]

        logistic_id = logistic["id"]

        logistic_rate_db = LogisticRate()
        logistic_rate = logistic_rate_db.get_logistics_rate_price(
            from_region=from_region, to_region=to_region, logistic_id=logistic_id
        )

        price = logistic_rate[0]["price"]
        print(price)

        order_db = models.Order()
        updated_order_data = order_db.update_order(order_data["id"], {"price": price})

        serializer = serializers.OrderSerializer(data=updated_order_data)
        if serializer.is_valid():
            response_data = {"data": serializer.data, "message": "Order Created Successfully"}
            return Response(response_data, status=status.HTTP_200_OK)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class OrderDetailView(RetrieveUpdateDestroyAPIView):
    authentication_classes = [CustomTokenAuthentication]
    permission_classes = [CustomPermission]

    def get(self, request, id, *args, **kwargs):
        user, _ = self.authentication_classes[0]().authenticate(request)
        order_db = models.Order()
        data = order_db.get_order("id", id)
        if data["user_id"] != user["id"]:
            return Response({"error": "You do not have the permission"}, status=status.HTTP_401_UNAUTHORIZED)
        serializer = serializers.OrderSerializer(data=data)
        if serializer.is_valid(raise_exception=True):
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_200_OK)

    def update(self, request, id, *args, **kwargs):
        user, _ = self.authentication_classes[0]().authenticate(request)

        data = request.data
        serializer = serializers.OrderSerializer(data=data)

        if serializer.is_valid(raise_exception=True):
            order_db = models.Order()
            order = order_db.get_order("id", id)

            if order["user_id"] != user["id"]:
                return Response({"error": "You do not have the permission"}, status=status.HTTP_401_UNAUTHORIZED)

            order = order_db.update_order(id, serializer.validated_data)
            order = order_db.update_order(id, {"updated_at": str(timezone.now())})
            serializer = serializers.OrderSerializer(data=order)
            if serializer.is_valid():
                data = {"data": serializer.data, "message": "Order Updated Successfully"}
                return Response(data, status=status.HTTP_200_OK)

            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def destroy(self, request, id, *args, **kwargs):
        user, _ = self.authentication_classes[0]().authenticate(request)

        data = request.data

        order_db = models.Order()
        order = order_db.get_order("id", id)

        if order["user_id"] != user["id"]:
            return Response({"error": "You do not have the permission"}, status=status.HTTP_401_UNAUTHORIZED)

        order = order_db.update_order(id, {"is_active": False, "updated_at": str(timezone.now())})
        data = {"data": order, "message": "Order set as inactive"}
        return Response(data, status=status.HTTP_200_OK)


def fix_quotes(text: str) -> str:
    """
    将文本中的全角引号左右互换的问题修正.
    e.g.
        ”...“ -> “...”
    """
    lst = 0
    out = []

    while True:
        lq = min(text.find("“", lst), text.find("”", lst))
        if lq == -1:
            break
        rq = min(text.find("“", lq + 1), text.find("”", lq + 1))
        if rq == -1:
            break

        out.append(text[lst:lq])
        out.append("“")
        out.append(text[lq + 1:rq])
        out.append("”")

        lst = rq + 1

    out.append(text[lst:])

    return "".join(out)

def fix_quotes_file_inplace(filename: str):
    """
    在文件中就地进行 `fix_quotes`.
    """
    with open(filename) as f:
        text = f.read()
    text = fix_quotes(text)
    with open(filename, "w") as f:
        f.write(text)


from __future__ import print_function
import numpy as np
from skimage import io
from tqdm import tqdm
import argparse
import os
from config import palette, invert_palette

def convert_to_color(arr_2d, palette=palette):
    """ grayscale labels to RGB-color encoding """
    arr_3d = np.zeros((arr_2d.shape[0], arr_2d.shape[1], 3), dtype=np.uint8)

    for c, i in palette.items():
        m = arr_2d == c
        arr_3d[m] = i

    return arr_3d

def convert_from_color(arr_3d, palette=invert_palette):
    """ RGB-color encoding to grayscale labels """
    arr_2d = np.zeros((arr_3d.shape[0], arr_3d.shape[1]), dtype=np.uint8)

    for c, i in palette.items():
        m = np.all(arr_3d == np.array(c).reshape(1, 1, 3), axis=2)
        arr_2d[m] = i

    return arr_2d

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("images", help="Images to process (at least one)",
                        nargs='+')
    parser.add_argument("--to-color",
                        help="Convert from grayscale labels"
                             "to RGB encoded labels",
                        action="store_true")
    parser.add_argument("--from-color",
                        help="Convert from RGB encoded labels"
                             "to grayscale labels",
                        action="store_true")
    parser.add_argument("--out",
                        help="Folder where to save the modified images",
                        type=str)
    args = parser.parse_args()

    files = args.images

    if args.to_color and args.from_color:
        raise ValueError("Cannot specify both --from-color"
                         "and --to-color at the same time")
    elif args.to_color:
        convert_fun = convert_to_color
    elif args.from_color:
        convert_fun = convert_from_color
    else:
        raise ValueError("You need to specify whether to convert"
                         "from or to the RGB color labels")

    if args.out is None:
        OUTPUT_FOLDER = './out'
    else:
        OUTPUT_FOLDER = args.out

    if os.path.isdir(OUTPUT_FOLDER):
        print("WARNING : output folder {} exists !".format(OUTPUT_FOLDER))
    else:
        os.mkdir(OUTPUT_FOLDER)

    for f in tqdm(files):
        filename = f.split('/')[-1]
        img = io.imread(f)
        new_img = convert_fun(img)
        io.imsave(OUTPUT_FOLDER + '/' + filename, new_img)


import socket

class Topic:

    def __init__(self, host="byond.oraclestation.com", port=5000, key="default_pwd"):
        self.host = host
        self.port = port
        self.key = key

        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, proto=socket.IPPROTO_TCP)
        sock.connect((self.host, self.port))
        sock.settimeout(5)

        self.sock = sock

    def __del__(self):
        if hasattr(self, 'sock'):
            self.sock.close()

    def send_topic(self, query):
        query = "?" + query + "&key=" + self.key

        packet = bytearray([0, 0x83, 0, 0, 0, 0, 0, 0, 0]) + query.encode('ascii') + bytearray([0])
        length = (len(packet) - 4).to_bytes(2, byteorder='little', signed=False)

        packet[2] = length[1]
        packet[3] = length[0]

        sent = self.sock.send(packet)

        if sent != len(packet):
            raise Exception("Could not send data!")

        receive = self.sock.recv(512)

        if len(receive) > 5:
            response = receive[5:-1].decode('ascii')
            return response

        return ""


import nltk
from Model.Model import Model
from View.View import View
from docx import Document
from datetime import datetime
from classes.Document import MyDocument
import os
import string
import pymorphy2
from tkinter import filedialog
from tkinter import messagebox
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import math
import numpy as np
import re
import heapq

class Controller:
    def __init__(self, root):
        self.model = Model()
        self.view = View(root,self)

    def __punctuation(self, str):
        punctuation = string.punctuation
        translator = str.maketrans('', '', punctuation)
        result = str.translate(translator)
        characters_to_remove = ['"', '“', '”', '«', '»']

        for char in characters_to_remove:
            result = result.replace(char, '')
        return  result

    def __get_synonyms(self,word):
        morph = pymorphy2.MorphAnalyzer()
        normal_form = morph.parse(word)[0].normal_form
        synonyms = []
        for synset in morph.parse(normal_form)[0].lexeme:
            synonyms.append(synset.word)
        return synonyms

    def create_dictionary_by_documents(self):
        dictionary = []
        documents = self.model.get_documents()
        for doc in documents:
            dictionary+=self.__punctuation(doc.text.lower()).split()
        dictionary = list(set(dictionary))
        self.model.set_dictionary(dictionary)

    def create_binary_vector_documents(self):
        dictionary = self.model.get_dictionary()
        docs = self.model.get_documents()
        matrix_of_docs = []

        for doc in docs:
            vector_doc = []
            for word in dictionary:
                vector_doc.append(1 if word in doc.text else 0)
            matrix_of_docs.append(vector_doc)

        self.model.set_docs_vectors(matrix_of_docs)

    def create_binary_vector_query(self, query):
        query = self.__punctuation(query).lower()
        query = query.split()
        query_termins_synonyms = []

        for word in query:
            query_termins_synonyms+= list(set(self.__get_synonyms(word)))

        dictionary = self.model.get_dictionary()
        vector_binary_query = []

        for word in dictionary:
                vector_binary_query.append(1 if word in query_termins_synonyms else 0)

        self.model.set_query_vector(vector_binary_query)

    def calculate_similar(self):

        matrix_docs = self.model.get_docs_vectors()
        query_vector = np.array(self.model.get_query_vector())
        e_query_vector = np.linalg.norm(query_vector)
        similar = {}
        id = 0

        for vector in matrix_docs:
            vec = np.array(vector)
            e_vec = np.linalg.norm(vec)

            if (e_vec * e_query_vector) != 0:
                query_equals_doc = (np.dot(vec, query_vector))/(e_vec * e_query_vector)
                similar[id]=query_equals_doc
                id+=1

            else:
                query_equals_doc = "Nan"
                similar[id] = query_equals_doc
                id += 1

        sorted_similar = {k: v for k, v in sorted(similar.items(),reverse=True, key=lambda item: item[1])}

        self.model.set_result_similar(sorted_similar)

    def open_word_file(self):

        documents = []
        file_path = filedialog.askopenfilenames(filetypes=[("Word Files", "*.docx")])

        if file_path:
            for path in file_path:

                doc = Document(path)
                doc_name = os.path.basename(path)
                doc_content = "\n".join([paragraph.text for paragraph in doc.paragraphs])
                doc_created_date = datetime.fromtimestamp(os.path.getctime(path)).strftime('%H:%M - %d.%m.%Y').split(
                    "-")
                document = MyDocument(doc_name, path, doc_content, doc_created_date[1], doc_created_date[0])
                documents.append(document)

            self.model.set_documents(documents)
            self.update_log("Files uploaded")

    def update_log(self, message):
        self.view.log_text.config(state=tk.NORMAL)  # Делаем текстовое поле активным
        self.view.log_text.insert(tk.END, message + "\n")  # Добавляем запись
        self.view.log_text.config(state=tk.DISABLED)  # Делаем текстовое поле неактивным
        self.view.log_text.see(tk.END)

    def check_is_nan(self, similar):
        for key, value in similar.items():
            if value == "Nan":
                self.update_log("Совпадения не найдены.")
                return False
            else: return True

    def start(self):
        if not self.model.get_documents():
            messagebox.showinfo("Ошибка", "Вы не загрузили документы")
            return 0

        if not self.view.query_entry.get():
            messagebox.showinfo("Ошибка", "Введите языковой запрос")
            return 0

        self.create_dictionary_by_documents()
        self.create_binary_vector_documents()
        self.create_binary_vector_query(self.view.query_entry.get())
        self.calculate_similar()

        if not self.check_is_nan(self.model.get_result_similar()):
            return 0

        docs_id = list(self.model.get_result_similar().keys())

        self.update_log("Наиболее подходящие документы:")
        for id in range(len(docs_id)):
            self.update_log(f"{id+1}. "+self.model.get_document_by_id(docs_id[id]).title + f": {self.model.get_result_similar()[docs_id[id]]}")

        self.view.show_open_files_button()

    def generate_annotation(self):
        path = f"../docs/"
        article_text = ""
        selected_index = self.view.listbox.curselection()

        if selected_index:
            selected_file = self.view.listbox.get(selected_index[0])
            file_path = os.path.join(path, selected_file)
            print(file_path)
            try:
                if file_path.endswith('.docx'):
                    doc = Document(file_path)
                    for paragraph in doc.paragraphs:
                        article_text += paragraph.text + '\n'
                elif file_path.endswith('.txt'):
                    with open(file_path, 'r', encoding='utf-8') as file:
                        article_text = file.read()
                else:
                    print("Неподдерживаемый формат файла.")
            except Exception as e:
                print(f"Произошла ошибка при чтении файла: {e}")

        print(article_text)

        article_text = re.sub(r'\[[0-9]*\]', ' ', article_text)
        article_text = re.sub(r'\s+', ' ', article_text)

        formatted_article_text = re.sub('[^а-яА-Я]', ' ', article_text)
        formatted_article_text = re.sub(r'\s+', ' ', formatted_article_text)

        sentence_list = nltk.sent_tokenize(article_text)

        stopwords = nltk.corpus.stopwords.words('russian')

        word_frequencies = {}
        for word in nltk.word_tokenize(formatted_article_text):
            if word not in stopwords:
                if word not in word_frequencies.keys():
                    word_frequencies[word] = 1
                else:
                    word_frequencies[word] += 1
        print(word_frequencies.values())
        maximum_frequency = max(word_frequencies.values())

        for word in word_frequencies.keys():
            word_frequencies[word] = (word_frequencies[word] / maximum_frequency)

        sentence_scores = {}
        for sent in sentence_list:
            for word in nltk.word_tokenize(sent.lower()):
                if word in word_frequencies.keys():
                    if len(sent.split(' ')) < 30:
                        if sent not in sentence_scores.keys():
                            sentence_scores[sent] = word_frequencies[word]
                        else:
                            sentence_scores[sent] += word_frequencies[word]

        summary_sentences = heapq.nlargest(3, sentence_scores, key=sentence_scores.get)

        summary = ' '.join(summary_sentences)

        self.update_log(f"\n{selected_file}: {summary}")

    def update_file_list(self):
        docs_id = list(self.model.get_result_similar().keys())
        self.view.listbox.delete(0, tk.END)
        for id in range(len(docs_id)):
            self.view.listbox.insert(tk.END, self.model.get_document_by_id(docs_id[id]).title)

    def open_new_files(self):
        path = f"../docs/"
        selected_index = self.view.listbox.curselection()
        if selected_index:
            selected_file = self.view.listbox.get(selected_index[0])
            os.startfile(path+selected_file)

    def recall_metric(self, a, c): # and average precision
        return a/(a+c) #r

    def precision_metric(self, a, b):
        return a/(a+b) # p

    def accuracy_metric(self, a, b, c, d):
        return (a+d)/(a+b+c+d)

    def error_metric(self, a, b, c, d):
        return (b+c)/(a+b+c+d)

    def f_measure_metric(self,r, p):
        return 2/((1/p)+(1/r))

    def precision_n_metric(self,a):
        return a/3

    def r_precision_metric(self, a):
        return 2/a

    def grafik(self):
        recall = np.array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.85, 0.9, 1.0])
        p = []
        for el in recall:
            if el > 0.5:
             p.append(0)
            else: p.append(1)
        p = np.array(p)
        # Сортируем оценки уверенности в порядке убывания
        sorted_indices = np.argsort(recall)[::-1]
        p_sorted = p[sorted_indices]

        # Инициализируем списки для хранения точности и полноты на 11 уровнях
        precision_at_recall = []
        recall_levels = np.linspace(0, 1, 11)  # 11 равномерно распределенных уровней полноты от 0 до 1

        # Вычисляем точность на каждом уровне полноты
        for recall_level in recall_levels:
            cutoff = int(recall_level * len(p_sorted))
            y_true_cutoff = p_sorted[:cutoff]
            precision = np.sum(y_true_cutoff) / (
                        cutoff + 1e-9)  # Добавляем маленькое значение для избегания деления на ноль
            precision_at_recall.append(precision)

        # Интерполируем значения
        interpolated_precision = np.maximum.accumulate(precision_at_recall[::-1])[::-1]

        # Создаем фигуру matplotlib
        fig = Figure(figsize=(8, 6))
        ax = fig.add_subplot(111)

        # Построение кривой полноты/точности с точками и интерполированными значениями
        ax.step(recall_levels, precision_at_recall, marker='o', label='Точки')
        ax.plot(recall_levels, interpolated_precision, linestyle='--', label='Интерполированная линия')
        ax.set_xlabel('Полнота (Recall)')
        ax.set_ylabel('Точность (Precision)')
        ax.set_title('Кривая полноты/точности с интерполированными значениями')
        ax.grid(True)
        ax.legend()

        canvas = FigureCanvasTkAgg(fig, master=self.view.metrics_window)
        canvas_widget = canvas.get_tk_widget()
        canvas_widget.pack()

    def calculate_metrics(self):
        amount_relevant_docs = len(self.model.get_relevant_documents()) # a
        amount_irrelevant_docs = len(self.model.get_irrelevant_documents()) # b
        amount_bad_relevant_docs = len(self.model.get_bad_relevant_documents()) # d
        not_finded_docs = 0 # c

        reccal = self.recall_metric(amount_relevant_docs,not_finded_docs)
        precision = self.precision_metric(amount_relevant_docs, amount_irrelevant_docs)

        accuracy = self.accuracy_metric(amount_relevant_docs,amount_irrelevant_docs, not_finded_docs, amount_bad_relevant_docs)
        error = self.error_metric(amount_relevant_docs,amount_irrelevant_docs, not_finded_docs, amount_bad_relevant_docs)

        f_measure = self.f_measure_metric(reccal, precision)
        precision_n = self.precision_n_metric(amount_relevant_docs)

        r_precision = self.r_precision_metric(amount_relevant_docs)

        txt = f"Recall: {reccal} \n" \
              f"Precision: {precision}\n" \
              f"Average precision: {reccal}\n" \
              f"Accuracy: {accuracy}\n" \
              f"F-measure: {f_measure}\n" \
              f"Precision by n: {[precision_n]}\n" \
              f"R-precision: {r_precision}\n"
        self.view.label_metrics.config(text=txt)

    def calculate_idfs(self):
        # Создайте словарь для хранения числа документов, содержащих каждый термин
        term_document_count = {}
        documents = self.model.get_documents

        total_documents = len(documents)
        termins = []

        for doc in documents:

            unique_terms = set(doc.text.split())

            for term in unique_terms:
                termins.append(term)

        for doc in documents:
            for term in termins:
                if term in set(doc.text.split()):
                    term_document_count[term] = term_document_count.get(term, 0) + 1

        idf_values = {}
        for term, doc_count in term_document_count.items():
            idf = math.log(total_documents / (doc_count + 1))  # Добавляем 1, чтобы избежать деления на 0
            idf_values[term] = idf

        self.model.set_IDFS(idf_values)

    def calculated_weight_termins_and_L_vector_in_documents(self):
        documents = self.model.get_documents()
        IDFS = self.model.get_IDFS()
        WTDS = []
        L_vector = []

        if not IDFS:
            return False

        for doc in documents:
            term_document_count = {}
            Li = []

            for key in IDFS:
                term_document_count[key] = doc.text.count(key) * IDFS[key]

                if key in doc.text:
                    Li.append(1)
                else:
                    Li.append(0)

            L_vector.append(Li)
            WTDS.append(term_document_count)

        self.model.set_L_vector(L_vector)
        self.model.set_WTDS(WTDS)

    def search_query_transformation(self, user_query):
        user_termins = set(user_query.split())
        IDFS = self.model.get_IDFS()
        query_vector = []

        for termin in user_termins:
            if termin in IDFS:
                value = IDFS[termin] * user_query.count(termin)
                query_vector.append(value)

        self.model.set_query_vector(query_vector)


# -*- coding: utf-8 -*-
# @Time : 2022/6/17 15:05
# @Author : renyumeng
# @Email : 2035328756@qq.com
# @File : Solve.py
# @Project : ProbabilityTheoryAndMathematicalStatisticsExperiments
import numpy as np
import scipy.stats as sts

class Solve:
    def __init__(self, N) -> None:
        self.n: int = N
        self._random_num: np.ndarray = self.get_normal_num
        self._describe_num: tuple = self.get_describe
        self.mean: float = self.get_mean
        self._describe_variance: float = self._describe_num[-3]
        self.func_variance: float = self.get_variance

    def __str__(self) -> str:
        return f"""使用describe函数得到的方差：{self._describe_variance}\n使用公式计算出的方差：{self.func_variance}"""

    @property
    def get_normal_num(self) -> np.ndarray:
        _normal_num: np.array = sts.norm.rvs(loc=0, scale=1, size=self.n)
        return _normal_num

    @property
    def get_describe(self) -> tuple:
        _describe_ans: tuple = sts.describe(self._random_num)
        return _describe_ans

    @property
    def get_mean(self) -> float:
        _mean: float = self._random_num.mean()
        return _mean

    @property
    def get_variance(self) -> float:
        temp_array: np.ndarray = self._random_num.copy()
        _mean: float = self.mean
        ans: float = 0
        for i in range(len(temp_array)):
            ans += (temp_array[i] - _mean) ** 2
        ans /= (self.n - 1)
        return ans

if __name__ == "__main__":
    newSolve: Solve = Solve(10)
    print(newSolve)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Tue Nov 13 13:15:49 2018

@author: michal
"""
import networkx as nx
from copy import deepcopy
#import numpy as np
from solution import Solution
from random import sample

class PublicationMatcher:

    def primitiveMaxPointsOfRest(self, publications):
        allPointsOfRest = self.countMaxPublicationPoints(publications)

        result = []
        for p in publications:
            allPointsOfRest -= self.publicationDict[p].points
            result.append(allPointsOfRest)

        return result

    def maxPointsOfRestFromFlowTheory(self, publications, maxW):
        result = []
        for i in range(len(publications)):
            result.append( self.maxPointsFromFlowTheory( publications[i:], maxW ) )

        return result

    def buildFlowGraph(self, publications):
        flowG = nx.DiGraph()
        flowG.add_node("s")
        flowG.add_node("t")

        pubs = publications
        allAuthors = []
        for p in pubs:
            publication = self.publicationDict[p]
            flowG.add_edge("s", p , capacity = publication.size, weight = - int(publication.points /publication.size)  )

            authors = list(self.pubGraph.neighbors(p))
            allAuthors += authors

            for a in authors:
                flowG.add_edge(p, a)

        allAuthors = list(set(allAuthors))

        for a in allAuthors:
            flowG.add_edge(a, "t", capacity = self.authorsDict[a].slots )

        return flowG

    def maxPointsFromFlowTheory(self, publications, maxW, returnDict =False):
        W = int(100*maxW)

        flowG = self.buildFlowGraph(publications)

        maxFlow, flowDict = nx.maximum_flow(flowG, "s", "t")
        if maxFlow < W:
            W = maxFlow

        flowG.nodes["s"]["demand"] = -W
        flowG.nodes["t"]["demand"] = W

        flowCost, flowDict = nx.network_simplex(flowG)
        if returnDict:
            data = { "maxPoints" : -flowCost/100, "maxSlots" : W/100, "flowGraph" : flowG, "flowDict" : flowDict}
            return data

        return -flowCost

    def maxPointsIncludingSolution(self, solution, publications, maxW):
#        W = int(100*maxW)
        flowG = self.buildFlowGraph(publications)

        p2a = solution.publication2authors
        i = 0
        for p in p2a:
            flowG.remove_edge(p, p2a[p])
            newSink = "s" + str(i)
            newVent = "t" + str(i)
            flowG.add_node( newVent, demand = self.publicationDict[p].size )
            flowG.add_edge(p, newVent)

            flowG.add_node( newSink, demand = -self.publicationDict[p].size )
            flowG.add_edge( newSink, p2a[p])

            i+=1

        maxFlow, flowDict = nx.maximum_flow(flowG, "s", "t")
        if maxFlow < maxW:
            maxW = maxFlow

        flowG.nodes["s"]["demand"] = -maxW
        flowG.nodes["t"]["demand"] = maxW

        flowCost, flowDict = nx.network_simplex(flowG)
        return -flowCost

    def getSortedPublicationByAuthor(self):
        author2allPublications, author2pubNo = self.generateAuthor2Publications()

        author2publications = self.generateSingleAuthor2PubDict()

        publications = self.getAllPublicationsFromMainGraph()
        pubOut = []
        pubUsed = set()
        for a in author2publications:
            uniquePubs = author2publications[a]
            pubOut += uniquePubs
            pubUsed |= set(uniquePubs)

            restPubs = author2allPublications[a]
            restPubs = list( set(restPubs) - pubUsed)

            pubOut += restPubs
            pubUsed |= set(restPubs)

        rest = list( set(publications) - pubUsed)
        pubOut += rest

        return pubOut

    def getSortedPublicationByPoints(self):
        publications = self.getAllPublicationsFromMainGraph()
        sortedPubObjects = sorted( self.publicationList , key=lambda x: x.points, reverse=True)

        outList = []

        for p in sortedPubObjects:
#            print( p.points)
            if p.id in publications:
                outList.append(p.id)

        return outList

    def branchAndBoundHeuristic(self, maxWeight, minimalPoints = 0, maxSolutionsNo = 20000, publications = [], maxPoints = []):
        minimalPoints = int(round(minimalPoints*100))

        if not publications:
            publications = self.getAllPublicationsFromMainGraph()

        maxPointsOfRest = maxPoints

        if not maxPoints :
            maxPoints = self.maxPointsOfRestFromFlowTheory(publications, maxWeight)
#        print(maxPoints)
        print("Maksymalne punkty z teori przeplywu - obliczone")
        print(maxPoints)
        maxWeight = int(round(maxWeight*100))

        minSizePerWeight = int( maxSolutionsNo/maxWeight )

        queue = [ Solution() ]
        pubLen = len(publications)

        progressFile = open("progress.log", 'w' )
        progressFile.close()

        inpossibleBranches = 0
        toHeavyBranches = 0
        toCheapBranches = 0

        bestPointsForWeight = {}

        for n, publication in enumerate(publications):

            authors = list(self.pubGraph.neighbors(publication))
            maxPointsOfRest = maxPoints[n]
            newQueue = []
            for solution in queue:
                for author in authors:
                    newSolution = deepcopy(solution)
                    solutionPossible = newSolution.addConnection(self.authorsDict[ author], self.publicationDict[publication] )

                    if not solutionPossible:
                        inpossibleBranches += 1
                        continue
##
                    if newSolution.actualWeight > maxWeight:
                        toHeavyBranches += 1
                        continue
    #
                    if newSolution.actualPoints + maxPointsOfRest < minimalPoints:
                        toCheapBranches += 1
                        continue

                    weight = newSolution.actualWeight
                    if weight in bestPointsForWeight:
                        if newSolution.actualPoints > bestPointsForWeight[weight]:
                            bestPointsForWeight[weight] = newSolution.actualPoints
                    else:
                        bestPointsForWeight[weight] = newSolution.actualPoints

                    points = newSolution.actualPoints
                    if len(queue) > 0.5*maxSolutionsNo:
                        if bestPointsForWeight[weight] * 0.9 >  points:
                            continue

                    newQueue.append(deepcopy(newSolution))

                if solution.actualPoints + maxPointsOfRest >= minimalPoints:
                    newQueue.append(deepcopy(solution))
                else:
                    toCheapBranches += 1

                queue = newQueue

                if len(queue) > maxSolutionsNo:
                    newQueue = []
                    for solution in queue:
                        weight = solution.actualWeight
                        points = solution.actualPoints
                        if bestPointsForWeight[weight] * 0.9 <  points:
                            newQueue.append(solution)

                    queue = newQueue

                if len(newQueue) > maxSolutionsNo:
                    mass2solutions = {}
                    for solution in newQueue:
                        weight2dict = solution.actualWeight
                        if not weight2dict in mass2solutions:
                            mass2solutions[weight2dict] = [ solution ]
                        else:
                            mass2solutions[weight2dict].append(solution)

                    newQueue = []
                    for mass in mass2solutions:
                        if len(mass2solutions[mass]) <= minSizePerWeight:
                            newQueue += mass2solutions[mass]
                        else:
                            newQueue += sample( mass2solutions[mass], minSizePerWeight )

                    queue = newQueue

            progressFile = open("progress.log", 'a' )
            progressFile.write("#########################\n")
            progressFile.write(str(float(n/pubLen)*100) +  " %  "+str(n)+"\n")
            progressFile.write("in queue: " + str(len(queue))+"\n")
            progressFile.write("impossible branches: "+ str(inpossibleBranches)+"\n")
            progressFile.write("to heavy branches: "+ str(toHeavyBranches)+"\n")
            progressFile.write("to cheap branches: "+ str(toCheapBranches)+"\n")
            progressFile.close()

        if not queue:
            print("nic nie znaleziono!")
            return

        bestSolution = None
        bestPoints = 0
        lowestPoints = 10000
#        print("wszystkie rozwiazania: ", len(queue))
        for solution in queue:
            if solution.actualPoints > bestPoints:
                bestPoints = solution.actualPoints
                bestSolution = solution
            if solution.actualPoints < lowestPoints:
                lowestPoints = solution.actualPoints

        return bestSolution

    def branchAndBound(self, maxWeight, minimalPoints = 0, publications = [], maxPoints = []):
        minimalPoints = int(round(minimalPoints*100))

        if not publications:
            publications = self.getAllPublicationsFromMainGraph()

        maxPointsOfRest = maxPoints

        if not maxPoints :
            maxPoints = self.maxPointsOfRestFromFlowTheory(publications, maxWeight)
#        print(maxPoints)
        print("Maksymalne punkty z teori przeplywu - obliczone")
        print(maxPoints)
        maxWeight = int(round(maxWeight*100))

        queue = [ Solution() ]
        pubLen = len(publications)

        progressFile = open("progress.log", 'w' )
        progressFile.close()

        inpossibleBranches = 0
        toHeavyBranches = 0
        toCheapBranches = 0

        for n, publication in enumerate(publications):

            authors = list(self.pubGraph.neighbors(publication))
            maxPointsOfRest = maxPoints[n]
            newQueue = []
            for solution in queue:
                for author in authors:
                    newSolution = deepcopy(solution)
                    solutionPossible = newSolution.addConnection(self.authorsDict[ author], self.publicationDict[publication] )

                    if not solutionPossible:
                        inpossibleBranches += 1
                        continue
##
                    if newSolution.actualWeight > maxWeight:
                        toHeavyBranches += 1
                        continue
    #
                    if newSolution.actualPoints + maxPointsOfRest < minimalPoints:
                        toCheapBranches += 1
                        continue

                    newQueue.append(deepcopy(newSolution))

                if solution.actualPoints + maxPointsOfRest >= minimalPoints:
                    newQueue.append(deepcopy(solution))
                else:
                    toCheapBranches += 1

            queue = newQueue

            progressFile = open("progress.log", 'a' )
            progressFile.write("#########################\n")
            progressFile.write(str(float(n/pubLen)*100) +  " %  "+str(n)+"\n")
            progressFile.write("in queue: " + str(len(queue))+"\n")
            progressFile.write("impossible branches: "+ str(inpossibleBranches)+"\n")
            progressFile.write("to heavy branches: "+ str(toHeavyBranches)+"\n")
            progressFile.write("to cheap branches: "+ str(toCheapBranches)+"\n")
            progressFile.close()

        if not queue:
            print("nic nie znaleziono!")
            return

        bestSolution = None
        bestPoints = 0
        lowestPoints = 10000
#        print("wszystkie rozwiazania: ", len(queue))
        for solution in queue:
            if solution.actualPoints > bestPoints:
                bestPoints = solution.actualPoints
                bestSolution = solution
            if solution.actualPoints < lowestPoints:
                lowestPoints = solution.actualPoints

        return bestSolution

def countIdenticalElements( vector2test, vectorKnown):
    count = 0
    for el in vectorKnown:
        if el in vector2test:
            count +=1

    return count


import pandas as pd
import datetime
import pickle
import numpy as np
from sklearn.linear_model import SGDRegressor
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import StandardScaler
from sklearn.externals import joblib
import random

# Codes des compagnies aériennes et l'équivalent du code interne après numérisation de la feature.
carrier_dict = {'AA':0, 'AS':1, 'B6':2, 'DL':3, 'EV':4, 'F9':5,  'HA':6, 'NK':7, 'OO':8, 'UA':9,  'VX':10,'WN':11}

# Distance entre les destinations
tripDistances=pd.DataFrame()

# codes numérique et codes textuelles des aéroports
airport_codes=pd.DataFrame()

# New Year day, Martin Luther King Jr. Day, Presidents' Day, Memorial Day
# Independence Day, Labor Day, Columbus Day, Veterans Day,
# Thanksgiving, Christmas Day
holidays = [datetime.date(2018, 1, 1),datetime.date(2019, 1, 1), datetime.date(2020, 1, 1),
 datetime.date(2018, 1, 15),datetime.date(2019, 1, 21), datetime.date(2020, 1, 20),
 datetime.date(2018, 2, 19), datetime.date(2019, 2, 18), datetime.date(2020, 2, 17),
 datetime.date(2018, 5, 28), datetime.date(2019, 5, 27), datetime.date(2020, 5, 25),
 datetime.date(2018, 7, 4), datetime.date(2019, 7, 4), datetime.date(2020, 7, 4),
 datetime.date(2018, 9, 3), datetime.date(2019, 9, 2), datetime.date(2020, 9, 7),
 datetime.date(2018,10, 8), datetime.date(2019,10, 14), datetime.date(2020,10, 12),
 datetime.date(2018, 11, 11), datetime.date(2019, 11, 11), datetime.date(2020, 11, 11),
 datetime.date(2018, 11, 22), datetime.date(2019, 11, 28), datetime.date(2020, 11, 26),
 datetime.date(2018, 12, 25), datetime.date(2019, 12, 25), datetime.date(2020, 12, 25)]

# Notre modèle de prédiction sauvegardé dans un fichier
predictionModel = SGDRegressor()

encoder = OneHotEncoder()
scaler = StandardScaler()

error_info = ''

def init(model_file='data/flights_delays_model.pkl', trip_distance_file='data/tripDistance.pkl', airport_code_file='data/airportCodesDF.pkl', encoder_file='data/categ_featuresEncoder.pkl', scaler_file='data/numfeaturesScaler.pkl') :
    global predictionModel, tripDistances, airport_codes,encoder, scaler
    predictionModel = joblib.load(model_file)
    pkl_file = open(trip_distance_file, 'rb')
    tripDistances = pickle.load(pkl_file)
    pkl_file = open(airport_code_file, 'rb')
    airport_codes =  pickle.load(pkl_file)
    encoder = joblib.load(encoder_file)
    scaler = joblib.load(scaler_file)

# Retourne le numéro de semaine correspondant à la date
def getWeekNum(day, month,year) :
    global error_info
    try :
        fl_date =  datetime.date(year, month, day)
        return fl_date.isocalendar()[1]
    except Exception as err:
        error_info += 'Invalid date entered (' + str(day) + '/' + str(month) + '/' + str(year) + ') :' + str(err) + '. '
        raise(err)

# Retourne le jour de la semaine (1 = lundi, ...)
def getWeekDay(day, month,year) :
    global error_info
    try :
        return datetime.date(year, month, day).weekday() + 1
    except Exception as err:
        error_info += 'Invalid date entered (' + str(day) + '/' + str(month) + '/' + str(year) + ') :' + str(err) + '. '
        raise(err)

# retourne le code numérique correspondant au code de la compagnies
def getCarrierCodeNum(unique_carrier_code):
    global error_info
    if  unique_carrier_code in carrier_dict :
        return carrier_dict[unique_carrier_code]
    else :
        error_info += 'Cannot find carrier code (' + unique_carrier_code + '). '
        raise ValueError('Bad carrier code')

# retourne la distance de vols entre 2 aéroports
def getTripDistance(origin_code, destination_code):
    global error_info
    try:
        distance = np.array(float(tripDistances[(tripDistances.ORIGIN == origin_code) &
            (tripDistances.DEST == destination_code)].DISTANCE.drop_duplicates()))
        return distance
    except Exception as err:
        error_info += 'Route was not found in the data. Please try a different nearby city or a new route.'
        raise(err)

# Retourne le code numérique de l'aéoport d'origine (si true) ou destination si false.
def getAirportCodeNum(airport_code, origin=True):
    global error_info
    try :
        if origin :
            return int(airport_codes[airport_codes.AIRPORT_CODE == airport_code].ORIGIN_CODE)
        else :
            return int(airport_codes[airport_codes.AIRPORT_CODE == airport_code].DEST_CODE)
    except Exception as err:
        error_info += 'No airport found with code ' + str(airport_code) + '. '
        raise(err)

# Retourne le nombre de jour à proximité d'un jour férié
def getNumDaysToHoliday(day, month, year):
    if year not in [2018, 2019, 2020] :
        error_info += 'No data found for the year ' + str(year) + '. '
        raise ValueError('Bad year')
    c_date = datetime.date(year, month, day)
    return np.min(np.abs(np.array(c_date) - np.array(holidays))).days

# Utilisation de notre modèle pour prédire le retard éventuel.
def delay_prediction(originCode, destCode,  carrier, day, month, year, dep_hour) :
    global error_info
    error_info=''
    try :
        origin_code_num = getAirportCodeNum(originCode, True)
        dest_code_num = getAirportCodeNum(destCode, False)
        carrier_code_num = carrier_dict[carrier]
        weekday = getWeekDay(day, month, year)
        week_num = getWeekNum(day, month, year)
        hdays = getNumDaysToHoliday(day, month, year)
        distance = getTripDistance(originCode, destCode)

        numerical_values = np.c_[distance, hdays]
        # Scale the features
        numerical_values_scaled = scaler.transform(numerical_values)

        categorical_values = np.zeros(8)
        categorical_values[0] = int(month)
        categorical_values[1] = int(day)
        categorical_values[2] = int(weekday)
        categorical_values[3] = int(week_num)
        categorical_values[4] = int(dep_hour)
        categorical_values[5] = int(carrier_code_num)
        categorical_values[6] = int(origin_code_num)
        categorical_values[7] = int(dest_code_num)

        categorical_values_encoded = encoder.transform([categorical_values]).toarray()
        travel = np.c_[numerical_values_scaled, categorical_values_encoded]

        pred_delay = predictionModel.predict(travel)
        return int(pred_delay[0]),error_info
    except Exception as err:
        print(error_info)
        print ('Prediction error.', err)
        return None, error_info

def test() :
    tcarrier = ['AA', 'AS', 'DL', 'HA', 'UA']
    tday = [1,10, 6, 9, 23, 30, 26, 12, 6, 9]
    tmonth = [1,2, 3, 4, 5, 6, 7, 8, 9, 10,11,12]
    tcode = ['BOS', 'JFK', 'SEA', 'SAN', 'DCA']
    tdep_hour = [1, 2, 4, 7, 9, 12, 10, 15, 14, 17, 19, 20, 21, 22, 23]
    for i in range(1000) :
        origcode = random.choice(tcode)
        destcode = random.choice(tcode)
        carrier = random.choice(tcarrier)
        day = random.choice(tday)
        month = random.choice(tmonth)
        dep_hour = random.choice(tdep_hour)
        d = delay_prediction(origcode, destcode, carrier, day, month, 2018, dep_hour)
        if d is not None :
            if d > 5 :
                print(origcode, destcode,carrier,day, month, dep_hour)
                print("delay", d)
                print("----------")


from fastapi import APIRouter, Depends, FastAPI

from src.dependencies.auth import firebase_authentication
from src.routes.audios import views as audios_views
from src.routes.auth import views as auth_views
from src.routes.users import views as users_views

api_router = APIRouter()
api_router.include_router(auth_views.router, tags=['Authentication'])
api_router.include_router(users_views.router,
                          tags=['Reciter'],
                          prefix='/reciters',
                          dependencies=[Depends(firebase_authentication)])
api_router.include_router(audios_views.router,
                          tags=['Audios'],
                          prefix='/audios',
                          dependencies=[Depends(firebase_authentication)])

def init_api(app: FastAPI) -> None:
    app.include_router(api_router)


# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def helper(self, l, r):
        if l > r: return None

        max_idx = l
        for i in range(l, r+1):
            if self.nums[i] > self.nums[max_idx]:
                max_idx = i

        return TreeNode(self.nums[max_idx], self.helper(l, max_idx-1), self.helper(max_idx+1, r))

    def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode:
        self.nums = nums

        return self.helper(0, len(nums)-1)


from collections.abc import MutableMapping
from collections.abc import MutableSequence
from dpath import options
from dpath.exceptions import InvalidKeyName
import dpath.segments

_DEFAULT_SENTINAL = object()
MERGE_REPLACE = (1 << 1)
MERGE_ADDITIVE = (1 << 2)
MERGE_TYPESAFE = (1 << 3)

def __safe_path__(path, separator):
    '''
    Given a path and separator, return a tuple of segments. If path is
    already a non-leaf thing, return it.

    Note that a string path with the separator at index[0] will have the
    separator stripped off. If you pass a list path, the separator is
    ignored, and is assumed to be part of each key glob. It will not be
    stripped.
    '''
    if not dpath.segments.leaf(path):
        segments = path
    else:
        segments = path.lstrip(separator).split(separator)

        # FIXME: This check was in the old internal library, but I can't
        # see a way it could fail...
        for i, segment in enumerate(segments):
            if (separator and (separator in segment)):
                raise InvalidKeyName("{} at {}[{}] contains the separator '{}'"
                                     "".format(segment, segments, i, separator))

        if options.CONVERT_INT_LIKE_SEGMENTS:
            # Attempt to convert integer segments into actual integers.
            final = []
            for segment in segments:
                try:
                    final.append(int(segment))
                except:
                    final.append(segment)
            segments = final

    return segments

def new(obj, path, value, separator='/', creator=None):
    '''
    Set the element at the terminus of path to value, and create
    it if it does not exist (as opposed to 'set' that can only
    change existing keys).

    path will NOT be treated like a glob. If it has globbing
    characters in it, they will become part of the resulting
    keys

    creator allows you to pass in a creator method that is
    responsible for creating missing keys at arbitrary levels of
    the path (see the help for dpath.path.set)
    '''
    segments = __safe_path__(path, separator)
    if creator:
        return dpath.segments.set(obj, segments, value, creator=creator)
    return dpath.segments.set(obj, segments, value)

def delete(obj, glob, separator='/', afilter=None):
    '''
    Given a obj, delete all elements that match the glob.

    Returns the number of deleted objects. Raises PathNotFound if no paths are
    found to delete.
    '''
    globlist = __safe_path__(glob, separator)

    def f(obj, pair, counter):
        (segments, value) = pair

        # Skip segments if they no longer exist in obj.
        if not dpath.segments.has(obj, segments):
            return

        matched = dpath.segments.match(segments, globlist)
        selected = afilter and dpath.segments.leaf(value) and afilter(value)

        if (matched and not afilter) or selected:
            key = segments[-1]
            parent = dpath.segments.get(obj, segments[:-1])

            try:
                # Attempt to treat parent like a sequence.
                parent[0]

                if len(parent) - 1 == key:
                    # Removing the last element of a sequence. It can be
                    # truly removed without affecting the ordering of
                    # remaining items.
                    #
                    # Note: In order to achieve proper behavior we are
                    # relying on the reverse iteration of
                    # non-dictionaries from dpath.segments.kvs().
                    # Otherwise we'd be unable to delete all the tails
                    # of a list and end up with None values when we
                    # don't need them.
                    del parent[key]
                else:
                    # This key can't be removed completely because it
                    # would affect the order of items that remain in our
                    # result.
                    parent[key] = None
            except:
                # Attempt to treat parent like a dictionary instead.
                del parent[key]

            counter[0] += 1

    [deleted] = dpath.segments.foldm(obj, f, [0])
    if not deleted:
        raise dpath.exceptions.PathNotFound("Could not find {0} to delete it".format(glob))

    return deleted

def set(obj, glob, value, separator='/', afilter=None):
    '''
    Given a path glob, set all existing elements in the document
    to the given value. Returns the number of elements changed.
    '''
    globlist = __safe_path__(glob, separator)

    def f(obj, pair, counter):
        (segments, found) = pair

        # Skip segments if they no longer exist in obj.
        if not dpath.segments.has(obj, segments):
            return

        matched = dpath.segments.match(segments, globlist)
        selected = afilter and dpath.segments.leaf(found) and afilter(found)

        if (matched and not afilter) or (matched and selected):
            dpath.segments.set(obj, segments, value, creator=None)
            counter[0] += 1

    [changed] = dpath.segments.foldm(obj, f, [0])
    return changed

def get(obj, glob, separator='/', default=_DEFAULT_SENTINAL):
    '''
    Given an object which contains only one possible match for the given glob,
    return the value for the leaf matching the given glob.
    If the glob is not found and a default is provided,
    the default is returned.

    If more than one leaf matches the glob, ValueError is raised. If the glob is
    not found and a default is not provided, KeyError is raised.
    '''
    if glob == '/':
        return obj

    globlist = __safe_path__(glob, separator)

    def f(obj, pair, results):
        (segments, found) = pair

        if dpath.segments.match(segments, globlist):
            results.append(found)
        if len(results) > 1:
            return False

    results = dpath.segments.fold(obj, f, [])

    if len(results) == 0:
        if default is not _DEFAULT_SENTINAL:
            return default

        raise KeyError(glob)
    elif len(results) > 1:
        raise ValueError("dpath.util.get() globs must match only one leaf : %s" % glob)

    return results[0]

def values(obj, glob, separator='/', afilter=None, dirs=True):
    '''
    Given an object and a path glob, return an array of all values which match
    the glob. The arguments to this function are identical to those of search().
    '''
    yielded = True

    return [v for p, v in search(obj, glob, yielded, separator, afilter, dirs)]

def search(obj, glob, yielded=False, separator='/', afilter=None, dirs=True):
    '''
    Given a path glob, return a dictionary containing all keys
    that matched the given glob.

    If 'yielded' is true, then a dictionary will not be returned.
    Instead tuples will be yielded in the form of (path, value) for
    every element in the document that matched the glob.
    '''

    globlist = __safe_path__(glob, separator)

    def keeper(segments, found):
        '''
        Generalized test for use in both yielded and folded cases.
        Returns True if we want this result. Otherwise returns False.
        '''
        if not dirs and not dpath.segments.leaf(found):
            return False

        matched = dpath.segments.match(segments, globlist)
        selected = afilter and afilter(found)

        return (matched and not afilter) or (matched and selected)

    if yielded:
        def yielder():
            for segments, found in dpath.segments.walk(obj):
                if keeper(segments, found):
                    yield (separator.join(map(dpath.segments.int_str, segments)), found)
        return yielder()
    else:
        def f(obj, pair, result):
            (segments, found) = pair

            if keeper(segments, found):
                dpath.segments.set(result, segments, found, hints=dpath.segments.types(obj, segments))

        return dpath.segments.fold(obj, f, {})

def merge(dst, src, separator='/', afilter=None, flags=MERGE_ADDITIVE):
    '''
    Merge source into destination. Like dict.update() but performs deep
    merging.

    NOTE: This does not do a deep copy of the source object. Applying merge
    will result in references to src being present in the dst tree. If you do
    not want src to potentially be modified by other changes in dst (e.g. more
    merge calls), then use a deep copy of src.

    NOTE that merge() does NOT copy objects - it REFERENCES. If you merge
    take these two dictionaries:

    >>> a = {'a': [0] }
    >>> b = {'a': [1] }

    ... and you merge them into an empty dictionary, like so:

    >>> d = {}
    >>> dpath.util.merge(d, a)
    >>> dpath.util.merge(d, b)

    ... you might be surprised to find that a['a'] now contains [0, 1].
    This is because merge() says (d['a'] = a['a']), and thus creates a reference.
    This reference is then modified when b is merged, causing both d and
    a to have ['a'][0, 1]. To avoid this, make your own deep copies of source
    objects that you intend to merge. For further notes see
    https://github.com/akesterson/dpath-python/issues/58

    flags is an OR'ed combination of MERGE_ADDITIVE, MERGE_REPLACE,
    MERGE_TYPESAFE.
        * MERGE_ADDITIVE : List objects are combined onto one long
          list (NOT a set). This is the default flag.
        * MERGE_REPLACE : Instead of combining list objects, when
          2 list objects are at an equal depth of merge, replace
          the destination with the source.
        * MERGE_TYPESAFE : When 2 keys at equal levels are of different
          types, raise a TypeError exception. By default, the source
          replaces the destination in this situation.
    '''
    filtered_src = search(src, '**', afilter=afilter, separator='/')

    def are_both_mutable(o1, o2):
        mapP = isinstance(o1, MutableMapping) and isinstance(o2, MutableMapping)
        seqP = isinstance(o1, MutableSequence) and isinstance(o2, MutableSequence)

        if mapP or seqP:
            return True

        return False

    def merger(dst, src, _segments=()):
        for key, found in dpath.segments.kvs(src):
            # Our current path in the source.
            segments = _segments + (key,)

            if len(key) == 0 and not options.ALLOW_EMPTY_STRING_KEYS:
                raise InvalidKeyName("Empty string keys not allowed without "
                                     "dpath.options.ALLOW_EMPTY_STRING_KEYS=True: "
                                     "{}".format(segments))

            # Validate src and dst types match.
            if flags & MERGE_TYPESAFE:
                if dpath.segments.has(dst, segments):
                    target = dpath.segments.get(dst, segments)
                    tt = type(target)
                    ft = type(found)
                    if tt != ft:
                        path = separator.join(segments)
                        raise TypeError("Cannot merge objects of type"
                                        "{0} and {1} at {2}"
                                        "".format(tt, ft, path))

            # Path not present in destination, create it.
            if not dpath.segments.has(dst, segments):
                dpath.segments.set(dst, segments, found)
                continue

            # Retrieve the value in the destination.
            target = dpath.segments.get(dst, segments)

            # If the types don't match, replace it.
            if ((type(found) != type(target)) and (not are_both_mutable(found, target))):
                dpath.segments.set(dst, segments, found)
                continue

            # If target is a leaf, the replace it.
            if dpath.segments.leaf(target):
                dpath.segments.set(dst, segments, found)
                continue

            # At this point we know:
            #
            # * The target exists.
            # * The types match.
            # * The target isn't a leaf.
            #
            # Pretend we have a sequence and account for the flags.
            try:
                if flags & MERGE_ADDITIVE:
                    target += found
                    continue

                if flags & MERGE_REPLACE:
                    try:
                        target['']
                    except TypeError:
                        dpath.segments.set(dst, segments, found)
                        continue
                    except:
                        raise
            except:
                # We have a dictionary like thing and we need to attempt to
                # recursively merge it.
                merger(dst, found, segments)

    merger(dst, filtered_src)

    return dst


from bs4 import BeautifulSoup
import requests
import json

HEADING_ORDER = [
    "defensePhysical",
    "defensePhysicalStrike",
    "defensePhysicalSlash",
    "defensePhysicalPierce",
    "defenseMagic",
    "defenseFire",
    "defenseLightning",
    "defenseHoly",
    "immunity",
    "robustness",
    "focus",
    "vitality",
    "poise",
    "weight",
]

def extract_from_html(content, slot):
    soup = BeautifulSoup(content, features="html.parser")
    for table_row in soup.find_all("tr"):
        name_cell = table_row.find("td")
        if name_cell is None:
            continue
        name = name_cell.find_all("a")[-1].get_text().strip()

        armor = {
            "name": name,
            "slot": slot,
            "weight": 0,
            "poise": 0,
            "immunity": 0,
            "robustness": 0,
            "focus": 0,
            "vitality": 0,
            "defensePhysical": 0,
            "defensePhysicalStrike": 0,
            "defensePhysicalSlash": 0,
            "defensePhysicalPierce": 0,
            "defenseMagic": 0,
            "defenseFire": 0,
            "defenseLightning": 0,
            "defenseHoly": 0,
        }

        for attribute, cell in zip(HEADING_ORDER, [x for x in table_row.children if x != "\n"][1:len(HEADING_ORDER) + 1]):
            cell_text = cell.get_text()
            if "defense" in attribute or attribute == "weight":
                armor[attribute] = float(cell_text)
            else:
                armor[attribute] = int(cell_text)

        yield armor

if __name__ == "__main__":
    armor_data = []
    armor_data.extend(extract_from_html(requests.get(
        "https://eldenring.wiki.fextralife.com/Helms").text, "head"))
    armor_data.extend(extract_from_html(requests.get(
        "https://eldenring.wiki.fextralife.com/Chest+Armor").text, "body"))
    armor_data.extend(extract_from_html(requests.get(
        "https://eldenring.wiki.fextralife.com/Gauntlets").text, "arms"))
    armor_data.extend(extract_from_html(requests.get(
        "https://eldenring.wiki.fextralife.com/Leg+Armor").text, "legs"))
    armor_data.sort(key=lambda x: x["name"])
    with open("armor_data.json", "w") as f:
        json.dump(armor_data, f, indent=2)


#!/usr/bin/env python3

import argparse
import boutvecma
import easyvvuq as uq
import chaospy
import os
import numpy as np
import time
import matplotlib.pyplot as plt

CAMPAIGN_NAME = "Conduction."

def refine_sampling_plan(campaign, analysis, number_of_refinements):
    """
    Refine the sampling plan.

    Parameters
    ----------
    number_of_refinements (int)
       The number of refinement iterations that must be performed.

    Returns
    -------
    None. The new accepted indices are stored in analysis.l_norm and the admissible indices
    in sampler.admissible_idx.
    """

    sampler = campaign.get_active_sampler()

    for _ in range(number_of_refinements):
        # compute the admissible indices
        sampler.look_ahead(analysis.l_norm)

        print(f"Code will be evaluated {sampler.n_new_points[-1]} times")
        # run the ensemble
        campaign.execute().collate(progress_bar=True)

        # accept one of the multi indices of the new admissible set
        data_frame = campaign.get_collation_result()
        analysis.adapt_dimension("T", data_frame)
        analysis.save_state(f"{campaign.campaign_dir}/analysis.state")

def plot_grid_2D(campaign, analysis, i, filename="out.pdf"):
    fig = plt.figure(figsize=[12, 4])
    ax1 = fig.add_subplot(121)
    ax2 = fig.add_subplot(122)
    accepted_grid = campaign.get_active_sampler().generate_grid(analysis.l_norm)
    ax1.plot(accepted_grid[:, 0], accepted_grid[:, 1], "o")
    ax2.plot(accepted_grid[:, 2], accepted_grid[:, 3], "o")
    ax1.set_title(f"iteration {i}")

    fig.tight_layout()
    fig.savefig(filename)

def custom_moments_plot(results, filename, i):
    fig, ax = plt.subplots()
    xvalues = np.arange(len(results.describe("T", "mean")))
    ax.fill_between(
        xvalues,
        results.describe("T", "mean") - results.describe("T", "std"),
        results.describe("T", "mean") + results.describe("T", "std"),
        label="std",
        alpha=0.2,
    )
    ax.plot(xvalues, results.describe("T", "mean"), label="mean")
    try:
        ax.plot(xvalues, results.describe("T", "1%"), "--", label="1%", color="black")
        ax.plot(xvalues, results.describe("T", "99%"), "--", label="99%", color="black")
    except RuntimeError:
        pass
    ax.grid(True)
    ax.set_ylabel("T")
    ax.set_xlabel(r"$\rho$")
    ax.set_title("iteration " + str(i))
    ax.legend()
    fig.savefig(filename)

def first_time_setup():
    encoder = boutvecma.BOUTEncoder(
        template_input="../../models/conduction/data/BOUT.inp"
    )
    # decoder = boutvecma.LogDataBOUTDecoder(variables=["T"])
    decoder = boutvecma.SimpleBOUTDecoder(variables=["T"])
    params = {
        "conduction:chi": {"type": "float", "min": 0.0, "max": 1e3, "default": 1.0},
        "T:scale": {"type": "float", "min": 0.0, "max": 1e3, "default": 1.0},
        "T:gauss_width": {"type": "float", "min": 0.0, "max": 1e3, "default": 0.2},
        "T:gauss_centre": {
            "type": "float",
            "min": 0.0,
            "max": 2 * np.pi,
            "default": np.pi,
        },
    }
    actions = uq.actions.local_execute(
        encoder,
        os.path.abspath(
            "../../build/models/conduction/conduction -q -q -q -q  -d . |& tee run.log"
        ),
        decoder,
        root=".",
    )
    campaign = uq.Campaign(name=CAMPAIGN_NAME, actions=actions, params=params)

    vary = {
        "conduction:chi": chaospy.Uniform(0.2, 4.0),
        "T:scale": chaospy.Uniform(0.5, 1.5),
        "T:gauss_width": chaospy.Uniform(0.5, 1.5),
        "T:gauss_centre": chaospy.Uniform(0.5 * np.pi, 1.5 * np.pi),
    }

    sampler = uq.sampling.SCSampler(
        vary=vary,
        polynomial_order=1,
        quadrature_rule="C",
        sparse=True,
        growth=True,
        midpoint_level1=True,
        dimension_adaptive=True,
    )
    campaign.set_sampler(sampler)

    print(f"Output will be in {campaign.campaign_dir}")

    sampler = campaign.get_active_sampler()

    print(f"Computing {sampler.n_samples} samples")

    time_start = time.time()
    campaign.execute().collate(progress_bar=True)

    # Create an analysis class and run the analysis.
    analysis = create_analysis(campaign)
    campaign.apply_analysis(analysis)
    analysis.save_state(f"{campaign.campaign_dir}/analysis.state")
    plot_grid_2D(campaign, analysis, 0, f"{campaign.campaign_dir}/grid0.png")

    for i in np.arange(1, 10):
        refine_once(campaign, analysis, i)
    time_end = time.time()

    print(f"Finished, took {time_end - time_start}")

    return campaign

def create_analysis(campaign):
    return uq.analysis.SCAnalysis(sampler=campaign.get_active_sampler(), qoi_cols=["T"])

def refine_once(campaign, analysis, iteration):
    refine_sampling_plan(campaign, analysis, 1)
    campaign.apply_analysis(analysis)
    analysis.save_state(f"{campaign.campaign_dir}/analysis.state")

    results = campaign.last_analysis
    plot_grid_2D(
        campaign,
        analysis,
        iteration,
        f"{campaign.campaign_dir}/grid{iteration:02}.png",
    )
    moment_plot_filename = os.path.join(
        f"{campaign.campaign_dir}", f"moments{iteration:02}.png"
    )
    sobols_plot_filename = os.path.join(
        f"{campaign.campaign_dir}", f"sobols_first{iteration:02}.png"
    )
    results.plot_sobols_first(
        "T",
        ylabel=f"iteration{iteration}",
        xlabel=r"$\rho$",
        filename=sobols_plot_filename,
    )
    plt.ylim(0, 1)
    plt.savefig(f"{campaign.campaign_dir}/sobols{iteration:02}.png")

    custom_moments_plot(results, moment_plot_filename, iteration)

    with open(f"{campaign.campaign_dir}/last_iteration", "w") as f:
        f.write(f"{iteration}")

def plot_results(campaign, moment_plot_filename, sobols_plot_filename):
    results = campaign.get_last_analysis()

    results.plot_sobols_first("T", xlabel=r"$\rho$", filename=sobols_plot_filename)

    fig, ax = plt.subplots()
    xvalues = np.arange(len(results.describe("T", "mean")))
    ax.fill_between(
        xvalues,
        results.describe("T", "mean") - results.describe("T", "std"),
        results.describe("T", "mean") + results.describe("T", "std"),
        label="std",
        alpha=0.2,
    )
    ax.plot(xvalues, results.describe("T", "mean"), label="mean")
    try:
        ax.plot(xvalues, results.describe("T", "1%"), "--", label="1%", color="black")
        ax.plot(xvalues, results.describe("T", "99%"), "--", label="99%", color="black")
    except RuntimeError:
        pass
    ax.grid(True)
    ax.set_ylabel("T")
    ax.set_xlabel(r"$\rho$")
    ax.legend()
    fig.savefig(moment_plot_filename)

    print(f"Results are in:\n\t{moment_plot_filename}\n\t{sobols_plot_filename}")

def reload_campaign(directory):
    """Reload a campaign from a directory

    Returns the campaign, analysis, and last iteration number
    """

    campaign = uq.Campaign(
        name=CAMPAIGN_NAME,
        db_location=f"sqlite:///{os.path.abspath(directory)}/campaign.db",
    )
    analysis = create_analysis(campaign)
    analysis.load_state(f"{campaign.campaign_dir}/analysis.state")

    with open(f"{campaign.campaign_dir}/last_iteration", "r") as f:
        iteration = int(f.read())

    return campaign, analysis, iteration

if __name__ == "__main__":

    parser = argparse.ArgumentParser(
        "conduction_sc",
        description="Adaptive dimension refinement for 1D conduction model",
    )
    parser.add_argument(
        "--restart", type=str, help="Restart previous campaign", default=None
    )
    parser.add_argument(
        "-n", "--refinement-num", type=int, default=1, help="Number of refinements"
    )

    args = parser.parse_args()

    if args.restart is None:
        first_time_setup()
    else:
        campaign, analysis, last_iteration = reload_campaign(args.restart)
        for iteration in range(
            last_iteration + 1, last_iteration + args.refinement_num + 1
        ):
            refine_once(campaign, analysis, iteration)


# 적록색약

import sys

sys.setrecursionlimit(1000000)

input = sys.stdin.readline

N = int(input())
area1 = []
area2 = []

for _ in range(N):
    lst1 = []
    lst2 = []
    for s in list(input().strip()):
        lst1.append(s)
        if s == "G":
            lst2.append("R")
        else:
            lst2.append(s)
    area1.append(lst1)
    area2.append(lst2)

direction = [(1, 0), (0, -1), (-1, 0), (0, 1)]

visit = [[False for _ in range(N)] for _ in range(N)]
res1 = 0

def dfs1(color, i, j):
    for d in direction:
        next_i = i + d[0]
        next_j = j + d[1]
        if (
            0 <= next_i < N
            and 0 <= next_j < N
            and not visit[next_i][next_j]
            and area1[next_i][next_j] == color
        ):
            visit[next_i][next_j] = True
            dfs1(color, next_i, next_j)

for i in range(N):
    for j in range(N):
        if not visit[i][j]:
            dfs1(area1[i][j], i, j)
            res1 += 1

########################################################

visit = [[False for _ in range(N)] for _ in range(N)]
res2 = 0

def dfs2(color, i, j):
    for d in direction:
        next_i = i + d[0]
        next_j = j + d[1]
        if (
            0 <= next_i < N
            and 0 <= next_j < N
            and not visit[next_i][next_j]
            and area2[next_i][next_j] == color
        ):
            visit[next_i][next_j] = True
            dfs2(color, next_i, next_j)

for i in range(N):
    for j in range(N):
        if not visit[i][j]:
            dfs2(area2[i][j], i, j)
            res2 += 1

print(res1, res2)


# takes an input file and output file
# input file is a Gcal in csv format
# output file: add 2 commas beggining of line if there are no commas in the line

import os, sys

inputFile = open(sys.argv[1], "rt")
outFile = open(sys.argv[2], "wt")

for line in inputFile:
    if line.count(",") == 0:
        strName = line.split("@")[0]
        line = strName + "," + strName + "," + line
    outFile.write(line)

inputFile.close()
outFile.close()


# coding=utf-8

import logging
from datetime import datetime

import markupsafe
from playhouse.shortcuts import dict_to_model, model_to_dict

from app import components
from app.notes.model import Note, TaggedNote
from app.tags import tagService
from app.categories import categoryService

class NoteService(components.Service):
    name = "notes"
    model_class = Note

    def __init__(self):
        super().__init__()

    def fetch_all_items(self, category_filter, milestone_filter):
        user_id = components.current_user_id()

        category_select = categoryService.category_filter_helper(Note, user_id, category_filter)
        milestone_select = []
        # milestone_filter == "all"
        # milestone_filter == "unassigned"
        # else ...
        return Note.select(Note).where(
            Note.is_deleted == False,
            *category_select,
            *milestone_select,
            Note.owner_id == user_id
        ).order_by(Note.edited.desc()).objects()

    def create_item(self, item_json):
        (item_json, tags) = self._select_and_sanitize_tags(item_json)

        # Check if user has ownership over the category given
        if ("category" in item_json and item_json["category"] and not categoryService.read_item(item_json["category"])):
            raise components.BadRequestError()

        item = dict_to_model(Note, item_json)
        item.content = markupsafe.escape(markupsafe.Markup(item.content))
        item.owner = components.current_user()
        item.save(force_insert=True)
        item.tags.add(tags)
        return item

    def update_item(self, item_id, item_json):
        myItem = self.read_item(item_id)
        (item_json, tags) = self._select_and_sanitize_tags(item_json)
        item = dict_to_model(Note, item_json)
        with components.DB.atomic():
            item.id = int(myItem.id)
            item.changed()
            item.save()
            item.tags.clear()
            item.tags.add(tags)
            return item
        raise RuntimeError("Could not update note")

    def serialize_item(self, item):
        item_json = model_to_dict(item, exclude=(
            Note.is_deleted,
            Note.owner,
            Note.tags
        ), recurse=False)
        tags = [tag for tag in item.tags]
        item_json["tags"] = [tag.tag for tag in tags]
        return item_json

    def sanitize_fields(self, item_json):
        if "due_date" in item_json:
            due_date = datetime.fromtimestamp(int(item_json["due_date"])).date() if item_json["due_date"] else None
            item_json["due_date"] = due_date
        return super().sanitize_fields(item_json)

    def _select_and_sanitize_tags(self, item_json):
        tags = []
        item_json = self.sanitize_fields(item_json)
        if "tags" in item_json:
            tags = tagService.bulk_search_or_insert(item_json["tags"])
            del item_json["tags"]
        logging.debug("Selected tags:" + ",".join([tag.tag for tag in tags]))
        return (item_json, tags)

noteService = NoteService()

# ----------------------------------------

class Module(components.Module):
    from app.notes.controller import NoteListController, NoteController
    name = "notes"
    services = [noteService]
    models = [Note, TaggedNote]
    controllers = [NoteListController, NoteController]

module = Module()


# normal libraries
from inspect import signature  # used in the method eval of the class

import numpy as np
import scipy.stats  # functions of statistics
# other files
from corai_error import Error_type_setter
from scipy.integrate import simps

# my libraries

np.random.seed(124)

# section ######################################################################
#  #############################################################################
# some information

# -------------------------------------------------------------------------------------------------------
# list of the possible kernels:
# fct_top_hat
# fct_plain
# fct_truncnorm
# fct_biweight
#
#

# the functions are correct, they scale and shift the way it is supposed.
# However they are written in the following way : f_t(t_i) = K( t_i - t )

# example of kernels:
# list_of_kernels =
#           [Kernel(fct_top_hat, name="wide top hat", a=-450, b=450),
#            Kernel(fct_top_hat, name="normal top hat", a=-200, b=200),
#            Kernel(fct_truncnorm, name="wide truncnorm", a=-500, b=500, sigma=350),
#            Kernel(fct_truncnorm, name="normal truncnorm", a=-350, b=350, sigma=250)]
# -------------------------------------------------------------------------------------------------------
# the functions only work for positive time. If one input negative times, it messes up the orientation.

# section ######################################################################
#  #############################################################################
# class
class Kernel:
    # kernel is a functor, used for weighting some computations.

    # the evaluation gives back a list of np.array

    # the function should hand in the list of np.arrays non scaled.

    # the parameters of the function (to be called) are gathered before:
    #   the weights do not change inside the estimation process.

    # the name is for identification in plots
    def __init__(self, fct_kernel, name=' no name ', **kwargs):
        self.fct_kernel = fct_kernel
        self.name = name
        self.__dict__.update(kwargs)

    def __repr__(self):
        return f"Function is {repr(self._fct_kernel)} and name {self.name}."

    def __call__(self, T_t, eval_point, T_max, debug=False):
        # getting the length over each dimensions for the kernel.
        shape_T_t = [len(T_t[i]) for i in range(len(T_t))]  # recall each dim has different nb of jumps
        # ans is the kernel evaluated on the jumps
        ans = self._fct_kernel(T_t=T_t, eval_point=eval_point, shape_T_t=shape_T_t,
                               **{k: self.__dict__[k] for k in self.__dict__ if
                                  k in signature(self._fct_kernel).parameters})
        # ans is a list of np arrays. It is normalized such that it is a kernel.
        # then I want to scale every vector.
        # The total integral should be T_max, so I multiply by T_max

        # If it isn't fct plain, then I have to scale.
        if self._fct_kernel.__name__ != 'fct_plain':

            # I want to rescale the results for the kernels that are not covering seen part. For that reason,
            # I compute the integral of the kernel, and scale accordingly.
            tt_integral = [np.linspace(0, T_max, int(5E5))] # in a list to respect the format list of list of T_t.
            yy = self._fct_kernel(T_t=tt_integral, eval_point=eval_point, shape_T_t=[1],
                                  **{k: self.__dict__[k] for k in self.__dict__ if
                                     k in signature(self._fct_kernel).parameters})
            integral = simps(yy[0], tt_integral[0])
            # yy[0] bc function gives back a list of arrays.

            for i in range(len(shape_T_t)):
                ans[i] = ans[i] / integral * T_max
                # *= do not work correctly since the vectors are not the same type (int/float).
                # I also divide by the sum, the vector is normalized, however,
                # possibly we're on the edge and we need to take that into account.

        if debug:
            print(f"inside kernel debug, "
                  f"that's my integral : "
                  f"{np.sum(ans[0][:-1]) * T_max / (len(ans[0]) - 1)}. "
                  f"Name : {self.fct_kernel.__name__}.")
        return ans

    # section ######################################################################
    #  #############################################################################
    # getters setters
    @property
    def fct_kernel(self):
        return self._fct_kernel

    @fct_kernel.setter
    def fct_kernel(self, new_fct_kernel):
        self._fct_kernel = new_fct_kernel

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, new_name):
        if isinstance(new_name, str):
            self._name = new_name
        else:
            raise Error_type_setter(f'Argument is not an string.')

# section ######################################################################
#  #############################################################################
# kernels' functions

def fct_top_hat(T_t, shape_T_t, eval_point, a=-200, b=200):
    output = []
    for i in range(len(shape_T_t)):
        vector = np.array(T_t[i])
        # -1 if x < 0, 0 if x==0, 1 if x > 0.
        output.append(1 / (2 * (b - a)) *
                      (np.sign(vector - eval_point - a) +
                       np.sign(b - vector + eval_point))
                      )
    return output

def fct_plain(T_t, shape_T_t, eval_point):
    # no scaling parameter, would be full to use scaling on plain.
    return [np.full(shape_T_t[i], 1) for i in range(len(shape_T_t))] # full of 1.

def fct_truncnorm(T_t, shape_T_t, eval_point, a=-300, b=300, sigma=200):
    output = []
    for i in range(len(shape_T_t)):
        output.append(scipy.stats.truncnorm.pdf(np.array(T_t[i]), a / sigma, b / sigma,
                                                loc=eval_point, scale=sigma))
    return output

def fct_truncnorm_test(T_t, shape_T_t, eval_point, a=-300, b=300, sigma=200):
    output = []
    i = 0  # for output[i] after, but there shouldn't be any problem.
    for i in range(len(shape_T_t)):
        output.append(2 * scipy.stats.truncnorm.pdf(np.array(T_t[i]), a / sigma, b / sigma,
                                                    loc=eval_point, scale=sigma))
    output[i][T_t[i] < eval_point] = 0
    return output

def fct_biweight(T_t, shape_T_t, eval_point, a=-300, b=300):
    #  if important, I can generalize biweight with function beta.
    #  Thus creating like 4 kernels with one function ( BETA(1), BETA(2)...)
    assert a == -b, "The kernel only accepts symmetrical bounds."
    output = []
    for i in range(len(shape_T_t)):
        xx = (np.array(T_t[i]) - (a + b) / 2 - eval_point) * 2 / (b - a)
        # the correct order is eval_point - T_t,
        # bc we evaluate at eval_point but translated by T_t,
        # if kernel not symmetric a != b, then we also need to translate by the mid of them.
        xx[(xx < -1) | (xx > 1)] = 1
        output.append(15 / 16 * np.power(1 - xx * xx, 2) * 2 / (b - a))
    return output

def fct_epa(T_t, shape_T_t, eval_point, a=-300, b=300):
    assert a == -b, "The kernel only accepts symmetrical bounds."
    output = []
    for i in range(len(shape_T_t)):
        xx = (np.array(T_t[i]) - (a + b) / 2 - eval_point) * 2 / (b - a)
        # the correct order is eval_point - T_t,
        # bc we evaluate at eval_point but translated by T_t,
        # if kernel not symmetric a != b, then we also need to translate by the mid of them.
        xx[(xx < -1) | (xx > 1)] = 1
        output.append(3 / 4 * (1 - xx * xx) * 2 / (b - a))
    return output


from collections import Counter

import zarr
from fastai.tabular.all import *
from fastai.data.all import *
from fastai.vision.gan import *
from fastai import *
from tsai.all import *
from torch import nn
import numpy as np
import seaborn as sns
import matplotlib.colors as mcolors
import matplotlib.pyplot as plt
import torch.nn.functional as F
from model import stagerNetAAE, stagerNetCritic
from utils import LossAttrMetric, GetLatentSpace, norm_batch, UnfreezeFcCrit, \
                SwitchAttribute, distrib_regul_regression, hist_lab, plot_results

# Load the config file
config_file = 'config.json'
with open(config_file, 'r') as file:
    config = json.load(file)

# Set the device on which you want to train the model
device = torch.device(config['device'])
torch.cuda.set_device(device)

lab_area = torch.Tensor(np.load(f'{config["labels_path"]}/area_db.npy'))[:,None]
lab_arousal = torch.Tensor(np.load(f'{config["labels_path"]}/arousal_db.npy'))[:,None]
lab_duration = torch.Tensor(np.load(f'{config["labels_path"]}/duration_db.npy'))[:,None]

# Define the labels
# 1) discrete labels
lab_area = torch.Tensor(np.load(f'{config["labels_path"]}/area_db.npy'))[:,None]
lab_arousal = torch.Tensor(np.load(f'{config["labels_path"]}/arousal_db.npy'))[:,None]
lab_duration = torch.Tensor(np.load(f'{config["labels_path"]}/duration_db.npy'))[:,None]
lab_all = torch.Tensor(4*lab_area + 2*lab_arousal + lab_duration)
lab_discrete = torch.hstack((lab_area,lab_duration,lab_arousal))
# 2) switch to match the desired encoding
tmp = copy(lab_all)
lab_all[tmp==3] = 4
lab_all[tmp==4] = 3
# 3) 3-level labels ("low", "medium", "high")
lab3 = deepcopy(lab_all)
lab3[:] = 0
lab3[lab_all>1] = 1
lab3[lab_all>5] = 2
# 4) 4-level labels ("all metrics at low level", "1 metrics at high level", "2 metrics at high level", "all metrics at high level")
lab4 = deepcopy(lab_all)
lab4[lab_all>0] = 1
lab4[lab_all>3] = 2
lab4[lab_all==7] = 3
# 5) normalize the label values
lab_norm_area = torch.Tensor(np.load(f'{config["labels_path"]}/norm_area_db.npy')).unsqueeze(-1)
lab_norm_duration = torch.Tensor(np.load(f'{config["labels_path"]}/norm_duration_db.npy')).unsqueeze(-1)
lab_norm = torch.hstack((lab_norm_area,lab_norm_duration,lab_arousal))
#normalize the binary arousal value with respect to the std of area and duration labels
lab_arousal_tmp = torch.Tensor([-1 if x==0 else 1 for x in lab_arousal]).unsqueeze(-1)
lab_norm_arousal = lab_arousal_tmp * (lab_norm_area.std() + lab_norm_duration.std()) / 2
lab_gather = torch.hstack((lab_norm_area,lab_norm_duration,lab_norm_arousal))
lab_gather = lab_gather.mean(dim=1).unsqueeze(-1) # mean of all metrics
# 6) Gather all the labels in a list in right order
label_stack = torch.hstack((lab_gather, lab_area, lab_duration, lab_arousal, lab3, lab4))

# Define dls
if config['load_dls']:
    dls = torch.load(config['dls_path']) # should be a .pkl file

else:
    # Read your data (.zarr file)
    path = Path(config['data_path'])
    X = zarr.open(path, mode='r')
    t = torch.Tensor(X)
    print('data properly read')

    # Define splitter
    n_train_samples = round(len(t)*config['trainset_part'])
    n_total_samples = len(t)
    splits = (L(range(n_train_samples), use_list=True),
              L(np.arange(n_train_samples, n_total_samples), use_list=True))
    splitter = IndexSplitter(splits[1])

    getters = [ItemGetter(0), ItemGetter(1)]
    dblock = DataBlock(blocks=(TSTensorBlock,TSTensorBlock),
                       getters=getters,
                       splitter=splitter,
                       batch_tfms=norm_batch())
    src = itemify(t.to('cpu'),label_stack.to('cpu'))
    dls = dblock.dataloaders(src, bs=config['bs'], val_bs=config['val_bs'], drop_last=True)

    torch.save(dls, config['dls_path'])

    # free memory space
    del X
    time.sleep(.2)
    torch.cuda.empty_cache()
    print('memory flushed')

dls = dls.to(device)
print('dls:')
print(dls.one_batch())

### Train the AutoEncoder part ###
acc_factor = config['acc_factor']
latent_dim = config['latent_dim']
model = stagerNetAAE(latent_dim=latent_dim,acc_factor=acc_factor)
model = model.to(device)
if config['train_ae']:
    metrics = [rmse]
    learn = Learner(dls, model, loss_func = model.ae_loss_func, metrics=metrics, opt_func=ranger)
    learning_rate = learn.lr_find()
    learn.fit_flat_cos(n_epoch=config['n_epoch'], lr=learning_rate.valley,
                        cbs=[
                            GradientAccumulation(n_acc=dls.bs*acc_factor),
                            TrackerCallback(),
                            SaveModelCallback(fname=config['ae_filename']),
                            EarlyStoppingCallback(min_delta=1e-4,patience=config['patience'])])

state_dict = torch.load(f'models/{config["ae_filename"]}.pth') # load the best weights

### Train the Classifier part ###
classif_filename = config['classif_filename']
model.load_state_dict(state_dict, strict=False)
#define the metrics to show
metrics = [LossAttrMetric("gather_loss"), LossAttrMetric("simple_loss"),
           LossAttrMetric("area_loss"), LossAttrMetric("duration_loss"),
           LossAttrMetric("arousal_loss"), LossAttrMetric("ord_loss")]
#freeze the discriminator weights
for name, param in model.named_parameters():
    if "fc_crit" in name:
        param.requires_grad_(False)

if config['train_classif_discrete']:
    #define the losses to montitor
    monitor_loss = ['area_loss','duration_loss','arousal_loss']
    #set the learning rates
    learning_rates = [1e-3,5e-4,2e-4]
    # Start curriculum learning
    total_cycles = config['nb_of_metrics']
    for i in range(total_cycles):
        curr_filename = str(classif_filename)+'_level'+str(i+1)
        model.level = i+1
        met = metrics[1:i+3] + metrics[-1:]
        learn = Learner(dls, model, loss_func=model.classif_loss_func,
                       metrics=met, opt_func=ranger)

        learn.fit_flat_cos(config['n_epoch'], lr=learning_rates[i],
                            cbs=[
                                GradientAccumulation(n_acc=dls.bs*acc_factor),
                                TrackerCallback(monitor=monitor_loss[i]),
                                SaveModelCallback(fname=curr_filename,monitor=monitor_loss[i]),
                                EarlyStoppingCallback(min_delta=1e-4,patience=config['patience'],monitor=monitor_loss[i]),
                                SwitchAttribute(attribute_name='global_loss', switch_every=5)
                                ])
        learn.load(curr_filename)
        model.load_state_dict(learn.model.state_dict())

state_dict = torch.load(f'models/{classif_filename}_level3.pth') # load the best weights
model.load_state_dict(state_dict, strict=False)
if config['train_regress']:
    model.level = 0
    model.dropout_rate = .1
    learn = Learner(dls, model, loss_func=model.classif_loss_func,
                   metrics=metrics, opt_func=ranger)
    learn.fit_flat_cos(config['n_epoch'], lr=1e-3,
                            cbs=[
                                GradientAccumulation(n_acc=dls.bs*acc_factor),
                                TrackerCallback(monitor='gather_loss'),
                                SaveModelCallback(fname=classif_filename, monitor='gather_loss'),
                                EarlyStoppingCallback(min_delta=1e-4,patience=config['patience'],monitor='gather_loss'),
                                SwitchAttribute(attribute_name='global_loss', switch_every=5)])

    np.save('results/'+str(classif_filename)+'_losses.npy', learn.recorder.losses)
    np.save('results/'+str(classif_filename)+'_values.npy', learn.recorder.values)

state_dict = torch.load(f'models/{config["classif_filename"]}.pth') # load the best weights

### Train the Adversarial part ###
model.load_state_dict(state_dict, strict=False)
adv_filename = config['aae_filename']
if config['train_aae']:
    metrics = [LossAttrMetric("classif_loss"), LossAttrMetric("recons_loss"),
               LossAttrMetric("adv_loss")]
    learn = Learner(dls, model, loss_func=model.aae_loss_func,
                   metrics=metrics, opt_func=ranger)

    learn.fit_flat_cos(config['n_epoch'], lr=1e-3,
                            cbs=[
                                GradientAccumulation(n_acc=dls.bs*acc_factor),
                                TrackerCallback(monitor='classif_loss'),
                                SaveModelCallback(fname=adv_filename, monitor='classif_loss'),
                                EarlyStoppingCallback(min_delta=1e-4,patience=config['patience'],monitor='classif_loss'),
                                UnfreezeFcCrit(switch_every=2),
                                SwitchAttribute(attribute_name='global_loss', switch_every=5)])

state_dict = torch.load(f'models/{adv_filename}.pth') # load the best weights

### Extract the latent space ###
result_filename = config['result_filename']
model.load_state_dict(state_dict, strict=False)
learn = Learner(dls,model,loss_func=model.aae_loss_func)
if config['load_latent_space']:
    new_zi = torch.load(f'data/z_{result_filename}.pt')
    print(f'latent space loaded with shape {new_zi.shape}')
else:
    learn.zi_valid = torch.tensor([]).to(device)
    learn.get_preds(ds_idx=0,cbs=[GetLatentSpace(cycle_len=1)])
    new_zi = learn.zi_valid
    learn.zi_valid = torch.tensor([]).to(device)
    learn.get_preds(ds_idx=1,cbs=[GetLatentSpace(cycle_len=1)])
    new_zi = torch.vstack((new_zi,learn.zi_valid))

print("new_zi shape: "+str(new_zi.shape))
torch.save(new_zi,f'data/z_{result_filename}.pt')

### Display the latent space ###
plot_results(new_zi.to(device),lab_gather,learn,result_filename)