# -*- coding: utf-8 -*-
from email.policy import default
import os, pickle
import argparse
from torch import nn

import numpy as np
import torch
import torchvision
import pytorch_lightning as pl
import warnings
from torchvision import  models
warnings.filterwarnings("ignore")
from pytorch_lightning.callbacks.early_stopping import EarlyStopping
import trainer, transforms
from sklearn.metrics.pairwise import euclidean_distances
from pytorch_lightning.trainer.supporters import CombinedLoader
import wandb, time, random,sys
import pandas as pd
sys.path.insert(0, '..')
import evals.embed_evals as evals

class MTL(pl.LightningModule):
    def __init__(self, *args, **kwargs):
        super().__init__()
        self.save_hyperparameters()
        self.setup_data()

        self.encoder = models.resnet18(pretrained=self.hparams.pretrained)
        num_features = 512

        self.pdist = nn.PairwiseDistance()
        self.triplet_loss = nn.TripletMarginLoss()

        self.criterion = nn.CrossEntropyLoss()
        self.nonlinear = nn.Softmax()

        num_features = self.encoder.fc.weight.shape[1]
        self.encoder.fc = nn.Sequential(nn.Linear(num_features, self.hparams.embed_dim, bias=False))
        self.classifier = nn.Sequential(
            nn.BatchNorm1d(self.hparams.embed_dim), nn.ReLU(), nn.Linear(self.hparams.embed_dim, self.hparams.num_class))

        self.loader_mode = 'max_size_cycle'

    def setup_data(self):
        train_transform = transforms.get_transform(self.hparams.transform, aug=True)
        valid_transform = transforms.get_transform(self.hparams.transform, aug=False)
        self.train_dataset = torchvision.datasets.ImageFolder(self.hparams.train_dir, transform=train_transform)
        self.valid_dataset = torchvision.datasets.ImageFolder(self.hparams.valid_dir, transform=valid_transform)
        self.test_dataset = torchvision.datasets.ImageFolder(self.hparams.test_dir, transform=valid_transform)

        self.train_input = torch.tensor(np.array([data[0].numpy() for data in self.train_dataset])).cuda()
        self.valid_input = torch.tensor(np.array([data[0].numpy() for data in self.valid_dataset])).cuda()
        self.test_input = torch.tensor(np.array([data[0].numpy() for data in self.test_dataset])).cuda()
        self.train_label = torch.tensor(np.array([data[1] for data in self.train_dataset]))#.cuda()
        self.valid_label = torch.tensor(np.array([data[1] for data in self.valid_dataset]))#.cuda()
        self.test_label = torch.tensor(np.array([data[1] for data in self.test_dataset]))#.cuda()

        self.train_triplets = np.array(pickle.load(open(self.hparams.train_triplets, "rb")))
        self.valid_triplets = np.array(pickle.load(open(self.hparams.valid_triplets, "rb")))
        self.test_triplets = np.array(pickle.load(open(self.hparams.test_triplets, "rb")))

        if self.hparams.syn:
            self.syn_x_train = pickle.load(open(self.hparams.train_synthetic, "rb"))
            self.syn_x_valid = pickle.load(open(self.hparams.valid_synthetic, "rb"))
            self.syn_x_test = pickle.load(open(self.hparams.test_synthetic, "rb"))

    def forward(self, inputs):
        inputs = inputs.to(self.device)
        embeds = self.encoder(inputs)
        return embeds

    def clf_loss_acc(self, embeds, labels):
        logits = self.classifier(embeds)
        # probs = self.nonlinear(logits)
        # if self.hparams.num_class < 3:
        #     labels = labels.type_as(logits).unsqueeze(1)
        preds = (logits.argmax(1) == labels).float()
        clf_loss = self.criterion(logits, labels)
        # m = trainer.metrics(probs, labels, num_class=self.hparams.num_class)
        m = {"pred":preds}
        return clf_loss, m

    def triplet_loss_acc(self, triplets):
        triplet_loss = self.triplet_loss(*triplets)
        d_ap = self.pdist(triplets[0], triplets[1])
        d_an = self.pdist(triplets[0], triplets[2])
        triplet_acc = (d_ap < d_an).float().mean()
        return triplet_loss, triplet_acc

    def test_mixed_triplets(self):
        triplet_idx = torch.tensor(self.test_triplets).long()
        train_embeds, test_embeds = self(self.train_input), self(self.test_input)
        x1, x2, x3 = test_embeds[triplet_idx[:,0]], train_embeds[triplet_idx[:,1]], train_embeds[triplet_idx[:,2]]
        triplets = (x1, x2, x3)
        return self.triplet_loss_acc(triplets)[1]

    def train_triplets_step(self, triplet_idx, clf_idx, labels):
        uniques = torch.unique(torch.concat([clf_idx, triplet_idx.flatten()]))
        if len(uniques) < len(self.train_input):
            val2idx = {val.item():i for i,val in enumerate(uniques)}
            for i, triplet in enumerate(triplet_idx):
                for j, val in enumerate(triplet):
                    triplet_idx[i][j] = val2idx[val.item()]
            triplet_idx = triplet_idx.long()
            for i, val in enumerate(clf_idx):
                clf_idx[i] = val2idx[val.item()]
            input = self.train_input[uniques]
        else: input = self.train_input
        embeds = self(input)

        x1, x2, x3 = embeds[triplet_idx[:,0]], embeds[triplet_idx[:,1]], embeds[triplet_idx[:,2]]
        triplets = (x1, x2, x3)

        triplet_loss, triplet_acc = self.triplet_loss_acc(triplets)
        clf_loss, m = self.clf_loss_acc(embeds[clf_idx], labels)

        total_loss = self.hparams.lamda * clf_loss + (1-self.hparams.lamda) * triplet_loss
        return clf_loss, m, triplet_loss, triplet_acc, total_loss

    def mixed_triplets_step(self, triplet_idx, clf_idx, labels, mode):
        train_uniques = torch.unique(triplet_idx[:,1:])
        if len(train_uniques) < len(self.train_input):
            val2idx = {val.item():i for i,val in enumerate(train_uniques)}
            for i, triplet in enumerate(triplet_idx):
                for j, val in enumerate(triplet[1:]):
                    triplet_idx[i][j+1] = val2idx[val.item()]
            triplet_idx = triplet_idx.long()
            input = self.train_input[train_uniques]
        else: input = self.train_input
        train_embeds = self(input)
        embeds = self(self.valid_input) if mode == "valid" else self(self.test_input)

        x1, x2, x3 = embeds[triplet_idx[:,0]], train_embeds[triplet_idx[:,1]], train_embeds[triplet_idx[:,2]]
        triplets = (x1, x2, x3)

        triplet_loss, triplet_acc = self.triplet_loss_acc(triplets)
        clf_loss, m = self.clf_loss_acc(embeds[clf_idx], labels)

        total_loss = self.hparams.lamda * clf_loss + (1-self.hparams.lamda) * triplet_loss
        return clf_loss, m, triplet_loss, triplet_acc, total_loss

    def training_step(self, batch, batch_idx):
        triplet_idx = batch["triplet"][0]
        clf_idx, labels = batch["clf"]

        clf_loss, m, triplet_loss, triplet_acc, total_loss = self.train_triplets_step(triplet_idx, clf_idx, labels)

        self.log('train_clf_loss', clf_loss)
        # self.log('train_clf_acc', m['acc'], prog_bar=False)
        self.log('train_triplet_loss', triplet_loss)
        self.log('train_triplet_acc', triplet_acc, prog_bar=False)
        self.log('train_total_loss', total_loss)
        return total_loss

    def validation_step(self, batch, batch_idx):
        triplet_idx = batch["triplet"][0]
        clf_idx, labels = batch["clf"]

        clf_loss, m, triplet_loss, triplet_acc, total_loss = self.mixed_triplets_step(triplet_idx, clf_idx, labels, "valid")

        self.log('valid_clf_loss', clf_loss)
        # self.log('valid_clf_acc', m['acc'], prog_bar=False)
        # self.log('valid_auc', m['auc'], prog_bar=False)
        self.log('valid_triplet_loss', triplet_loss)
        self.log('valid_triplet_acc', triplet_acc, prog_bar=False)
        self.log('valid_total_loss', total_loss)

    def test_step(self, batch, batch_idx):
        if batch_idx == 0: self.test_corrects = torch.zeros(len(self.test_input) )
        triplet_idx = batch["triplet"][0]
        clf_idx, labels = batch["clf"]

        clf_loss, m, triplet_loss, triplet_acc, total_loss = self.mixed_triplets_step(triplet_idx, clf_idx, labels, "test")
        self.test_corrects[clf_idx] = m["pred"].reshape(-1).float().cpu().detach()
        self.log('test_clf_loss', clf_loss)
        # self.log('test_clf_acc', m['acc'], prog_bar=False)
        # self.log('test_auc', m['auc'], prog_bar=False)
        self.log('test_triplet_loss', triplet_loss)
        self.log('test_triplet_acc', triplet_acc, prog_bar=False)
        self.log('test_total_loss', total_loss)
        return triplet_acc

    def embed_dataset(self, dataset):
        self.eval()
        zs, dl = [], torch.utils.data.DataLoader(dataset, batch_size=self.hparams.train_batch_size)
        for x, _ in iter(dl):
            zs.append(self(x.to(self.device)).cpu())
        return torch.cat(zs)

    def test_epoch_end(self, outputs):
        results = self.eval_knn_ds(self.test_dataset, self.train_dataset, self.syn_x_train, self.syn_x_test)
        for k,v in results.items(): self.log(k,v)

    def eval_knn_ds(self, test_ds, train_ds, syn_x_train=None, syn_x_test=None):
        z_train = self.embed_dataset(train_ds).numpy()
        z_test = self.embed_dataset(test_ds).numpy()
        y_train, y_test = self.train_label.numpy(), self.test_label.numpy()

        ## predicted labels
        if self.hparams.model == "TN":
            y_pred = evals.get_knn_score(z_train, y_train, z_test, y_test, metric="preds")
        else:
            y_pred = np.array([not y if not m else y for y, m in zip(y_test, self.test_corrects)])

        knn_acc = evals.get_knn_score(z_train, y_train, z_test, y_test)
        results = {"test_1nn_acc":knn_acc}

        if self.hparams.syn:
            ### decision support evals
            to_log = ["NINO_ds_acc", "rNINO_ds_acc", "NIFO_ds_acc"]
            syn_evals = evals.syn_evals(z_train, y_train, z_test, y_test, y_pred, syn_x_train, syn_x_test,
            self.hparams.weights, self.hparams.powers, k=1)
            for eval in to_log: results[eval] = syn_evals[eval]

            #### h2h evals
            if self.hparams.model != "RESN":
                RESN_dir = "../data/embeds/" + self.hparams.resn_embed_dir
                for dim in [50,512]:
                    NI_h2h, NO_h2h = [], []
                    for seed in range(3):
                        RESN_train = pickle.load(open(f"{RESN_dir}/RESN_train_d{dim}_seed{seed}.pkl","rb"))
                        RESN_test = pickle.load(open(f"{RESN_dir}/RESN_test_d{dim}_seed{seed}.pkl","rb"))
                        if self.hparams.predicted_labels: RESN_pred = pickle.load(open(f"{RESN_dir}/RESN_preds_d{dim}_seed{seed}.pkl","rb"))
                        else: RESN_pred = y_test
                        euc_dist_M = euclidean_distances(RESN_test,RESN_train)
                        RESN_NINOs = evals.get_NINO(euc_dist_M, y_train, RESN_pred, k=1)
                        RESN_NIs, RESN_NOs = RESN_NINOs[:,0], RESN_NINOs[:,1]
                        wins, _, ties = evals.nn_comparison(syn_x_train, syn_x_test, syn_evals["NINOs"][:,0], RESN_NIs, self.hparams.weights, self.hparams.powers)
                        NI_h2h.append((wins + ties*0.5)/len(y_test))
                        wins, _, ties = evals.nn_comparison(syn_x_train, syn_x_test, syn_evals["NINOs"][:,1], RESN_NOs, self.hparams.weights, self.hparams.powers)
                        NO_h2h.append((wins + ties*0.5)/len(y_test))
                    results[f"NI_h2h_d{dim}"] = np.array(NI_h2h).mean()
                    results[f"NO_h2h_d{dim}"] = np.array(NO_h2h).mean()

        return results

    def configure_optimizers(self):
        optimizer = torch.optim.Adam(self.parameters(), lr=self.hparams.learning_rate)
        self.opt = optimizer
        return optimizer

    def train_dataloader(self):
        triplet_dataset = torch.utils.data.TensorDataset(torch.tensor(self.train_triplets))
        clf_dataset = torch.utils.data.TensorDataset(torch.tensor(np.arange(len(self.train_dataset))), self.train_label)
        print(f"\nlen_clf_train:{len(clf_dataset)}")
        print(f"\nlen_triplet_train:{len(triplet_dataset)}")
        triplet_loader = trainer.get_dataloader(triplet_dataset, self.hparams.triplet_batch_size, "train", self.hparams.dataloader_num_workers)
        clf_loader = trainer.get_dataloader(clf_dataset, self.hparams.train_batch_size, "train", self.hparams.dataloader_num_workers)

        return CombinedLoader({"triplet": triplet_loader, "clf": clf_loader}, mode=self.loader_mode)

    def val_dataloader(self):
        triplet_dataset = torch.utils.data.TensorDataset(torch.tensor(self.valid_triplets))
        clf_dataset = torch.utils.data.TensorDataset(torch.tensor(np.arange(len(self.valid_dataset))), self.valid_label)
        print(f"\nlen_clf_valid:{len(clf_dataset)}")
        print(f"\nlen_triplet_valid:{len(triplet_dataset)}")
        triplet_loader = trainer.get_dataloader(triplet_dataset, len(triplet_dataset), "valid", self.hparams.dataloader_num_workers)
        clf_loader = trainer.get_dataloader(clf_dataset, len(clf_dataset), "valid", self.hparams.dataloader_num_workers)

        return CombinedLoader({"triplet": triplet_loader, "clf": clf_loader}, mode=self.loader_mode)

    def test_dataloader(self):
        triplet_dataset = torch.utils.data.TensorDataset(torch.tensor(self.test_triplets))
        clf_dataset = torch.utils.data.TensorDataset(torch.tensor(np.arange(len(self.test_dataset))), self.test_label)
        print(f"\nlen_clf_test:{len(clf_dataset)}")
        print(f"\nlen_triplet_test:{len(triplet_dataset)}")
        triplet_loader = trainer.get_dataloader(triplet_dataset, len(triplet_dataset), "test", self.hparams.dataloader_num_workers)
        clf_loader = trainer.get_dataloader(clf_dataset, len(clf_dataset), "test", self.hparams.dataloader_num_workers)
        return CombinedLoader({"triplet": triplet_loader, "clf": clf_loader}, mode=self.loader_mode)

def main():
    parser = trainer.config_parser()
    config_files = parser.parse_args()
    configs = trainer.load_configs(config_files)

    print(configs)

    pl.seed_everything(configs["seed"])
    model = MTL(**configs)
    monitor = "valid_total_loss"
    trainer.generic_train(model, configs, monitor)

if __name__ == "__main__":
    main()


import string, re
import numpy as np

class Numeral:
    def __init__(self, value, level, is_multiplier, is_eleven_to_nineteen=False):
        self.value = value
        self.level = level
        self.is_multiplier = is_multiplier
        self.is_eleven_to_nineteen = is_eleven_to_nineteen

class NumericToken:
    def __init__(self, numeral, error=0):
        self.numeral = numeral
        self.error = error
        self.is_significant = False

class ParserResult:
    def __init__(self, value, error=0):
        self.value = value
        self.error = error

class RussianNumbers:
    def __init__(self):
        self.tokens_fractions = {
            "целых": Numeral(1, 0, True),
            "целым": Numeral(1, 0, True),
            "целой": Numeral(1, 0, True),
            "целая": Numeral(1, 0, True),

            "точка": Numeral(0.1, 0, True),
            "запятая": Numeral(1, 0, True),

            "десятых": Numeral(0.1, -1, True),
            "десятым": Numeral(0.1, -1, True),
            "десятая": Numeral(0.1, -1, True),
            "сотых": Numeral(0.01, -3, True),
            "сотым": Numeral(0.01, -3, True),
            "сотая": Numeral(0.01, -3, True),
            "тысячных": Numeral(0.001, -4, True),
            "тысячным": Numeral(0.001, -4, True),
            "тысячная": Numeral(0.001, -4, True),
            "десятитысячных": Numeral(0.0001, -5, True),
            "десятитысячная": Numeral(0.0001, -5, True),

            # "половиной": Numeral(0.5, -1, False),
        }

        self.tokens = {
            "ноль": Numeral(0, 1, False),
            "нулю": Numeral(0, 1, False),
            "нолю": Numeral(0, 1, False),
            "ноля": Numeral(0, 1, False),

            "полтора": Numeral(1.5, 1, False),
            "полторы": Numeral(1.5, 1, False),

            "один": Numeral(1, 1, False),
            "одна": Numeral(1, 1, False),
            "одной": Numeral(1, 1, False),
            "первое": Numeral(1, 1, False),
            "первый": Numeral(1, 1, False),
            "первая": Numeral(1, 1, False),
            "первого": Numeral(1, 1, False),
            "первой": Numeral(1, 1, False),
            "первом": Numeral(1, 1, False),

            "два": Numeral(2, 1, False),
            "две": Numeral(2, 1, False),
            "двум": Numeral(2, 1, False),
            "двух": Numeral(2, 1, False),
            "второе": Numeral(2, 1, False),
            "второй": Numeral(2, 1, False),
            "вторая": Numeral(2, 1, False),
            "второго": Numeral(2, 1, False),
            "втором": Numeral(2, 1, False),

            "три": Numeral(3, 1, False),
            "трем": Numeral(3, 1, False),
            "трех": Numeral(3, 1, False),
            "трёх": Numeral(3, 1, False),
            "трём": Numeral(3, 1, False),
            "третье": Numeral(3, 1, False),
            "третий": Numeral(3, 1, False),
            "третья": Numeral(3, 1, False),
            "третьего": Numeral(3, 1, False),
            "третьей": Numeral(3, 1, False),
            "третьем": Numeral(3, 1, False),

            "четыре": Numeral(4, 1, False),
            "четырем": Numeral(4, 1, False),
            "четырех": Numeral(4, 1, False),
            "четырём": Numeral(4, 1, False),
            "четырёх": Numeral(4, 1, False),
            "четвертое": Numeral(4, 1, False),
            "четвертый": Numeral(4, 1, False),
            "четвертая": Numeral(4, 1, False),
            "четвертого": Numeral(4, 1, False),
            "четвертой": Numeral(4, 1, False),
            "четвертом": Numeral(4, 1, False),

            "пять": Numeral(5, 1, False),
            "пяти": Numeral(5, 1, False),
            "пятое": Numeral(5, 1, False),
            "пятый": Numeral(5, 1, False),
            "пятая": Numeral(5, 1, False),
            "пятого": Numeral(5, 1, False),
            "пятой": Numeral(5, 1, False),
            "пятом": Numeral(5, 1, False),

            "шесть": Numeral(6, 1, False),
            "шести": Numeral(6, 1, False),
            "шестое": Numeral(6, 1, False),
            "шестой": Numeral(6, 1, False),
            "шестая": Numeral(6, 1, False),
            "шестого": Numeral(6, 1, False),
            "шестом": Numeral(6, 1, False),

            "семь": Numeral(7, 1, False),
            "семи": Numeral(7, 1, False),
            "седьмое": Numeral(7, 1, False),
            "седьмой": Numeral(7, 1, False),
            "седьмая": Numeral(7, 1, False),
            "седьмого": Numeral(7, 1, False),
            "седьмом": Numeral(7, 1, False),

            "восемь": Numeral(8, 1, False),
            "восьми": Numeral(8, 1, False),
            "восьмое": Numeral(8, 1, False),
            "восьмой": Numeral(8, 1, False),
            "восьмая": Numeral(8, 1, False),
            "восьмого": Numeral(8, 1, False),
            "восьмом": Numeral(8, 1, False),

            "девять": Numeral(9, 1, False),
            "девяти": Numeral(9, 1, False),
            "девятое": Numeral(9, 1, False),
            "девятый": Numeral(9, 1, False),
            "девятая": Numeral(9, 1, False),
            "девятого": Numeral(9, 1, False),
            "девятой": Numeral(9, 1, False),
            "девятом": Numeral(9, 1, False),

            "десять": Numeral(10, 1, False),
            "десятью": Numeral(10, 1, False),
            "десяти": Numeral(10, 1, False),
            "десятое": Numeral(10, 1, False),
            "десятый": Numeral(10, 1, False),
            "десятая": Numeral(10, 1, False),
            "десятой": Numeral(10, 1, False),
            "десятого": Numeral(10, 1, False),
            "десятом": Numeral(10, 1, False),

            "одиннадцать": Numeral(11, 1, False, True),
            "одиннадцатью": Numeral(11, 1, False, True),
            "одиннадцати": Numeral(11, 1, False, True),
            "одиннадцатое": Numeral(11, 1, False, True),
            "одиннадцатый": Numeral(11, 1, False, True),
            "одиннадцатая": Numeral(11, 1, False, True),
            "одиннадцатого": Numeral(11, 1, False, True),
            "одиннадцатой": Numeral(11, 1, False, True),
            "одиннадцатом": Numeral(11, 1, False, True),

            "двенадцать": Numeral(12, 1, False, True),
            "двенадцатью": Numeral(12, 1, False, True),
            "двенадцати": Numeral(12, 1, False, True),
            "двенадцатое": Numeral(12, 1, False, True),
            "двенадцатый": Numeral(12, 1, False, True),
            "двенадцатая": Numeral(12, 1, False, True),
            "двенадцатого": Numeral(12, 1, False, True),
            "двенадцатой": Numeral(12, 1, False, True),
            "двенадцатом": Numeral(12, 1, False, True),

            "тринадцать": Numeral(13, 1, False, True),
            "тринадцатью": Numeral(13, 1, False, True),
            "тринадцати": Numeral(13, 1, False, True),
            "тринадцатое": Numeral(13, 1, False, True),
            "тринадцатый": Numeral(13, 1, False, True),
            "тринадцатая": Numeral(13, 1, False, True),
            "тринадцатого": Numeral(13, 1, False, True),
            "тринадцатой": Numeral(13, 1, False, True),
            "тринадцатом": Numeral(13, 1, False, True),

            "четырнадцать": Numeral(14, 1, False, True),
            "четырнадцатью": Numeral(14, 1, False, True),
            "четырнадцати": Numeral(14, 1, False, True),
            "четырнадцатое": Numeral(14, 1, False, True),
            "четырнадцатый": Numeral(14, 1, False, True),
            "четырнадцатая": Numeral(14, 1, False, True),
            "четырнадцатого": Numeral(14, 1, False, True),
            "четырнадцатой": Numeral(14, 1, False, True),
            "четырнадцатом": Numeral(14, 1, False, True),

            "пятнадцать": Numeral(15, 1, False, True),
            "пятнадцатью": Numeral(15, 1, False, True),
            "пятнадцати": Numeral(15, 1, False, True),
            "пятнадцатое": Numeral(15, 1, False, True),
            "пятнадцатый": Numeral(15, 1, False, True),
            "пятнадцатая": Numeral(15, 1, False, True),
            "пятнадцатого": Numeral(15, 1, False, True),
            "пятнадцатой": Numeral(15, 1, False, True),
            "пятнадцатом": Numeral(15, 1, False, True),

            "шестнадцать": Numeral(16, 1, False, True),
            "шестнадцатью": Numeral(16, 1, False, True),
            "шестнадцати": Numeral(16, 1, False, True),
            "шестнадцатое": Numeral(16, 1, False, True),
            "шестнадцатый": Numeral(16, 1, False, True),
            "шестнадцатая": Numeral(16, 1, False, True),
            "шестнадцатого": Numeral(16, 1, False, True),
            "шестнадцатой": Numeral(16, 1, False, True),
            "шестнадцатом": Numeral(16, 1, False, True),

            "семнадцать": Numeral(17, 1, False, True),
            "семнадцатью": Numeral(17, 1, False, True),
            "семнадцати": Numeral(17, 1, False, True),
            "семнадцатое": Numeral(17, 1, False, True),
            "семнадцатый": Numeral(17, 1, False, True),
            "семнадцатая": Numeral(17, 1, False, True),
            "семнадцатого": Numeral(17, 1, False, True),
            "семнадцатой": Numeral(17, 1, False, True),
            "семнадцатом": Numeral(17, 1, False, True),

            "восемнадцать": Numeral(18, 1, False, True),
            "восемнадцатью": Numeral(18, 1, False, True),
            "восемнадцати": Numeral(18, 1, False, True),
            "восемнадцатое": Numeral(18, 1, False, True),
            "восемнадцатый": Numeral(18, 1, False, True),
            "восемнадцатая": Numeral(18, 1, False, True),
            "восемнадцатого": Numeral(18, 1, False, True),
            "восемнадцатой": Numeral(18, 1, False, True),
            "восемнадцатом": Numeral(18, 1, False, True),

            "девятнадцать": Numeral(19, 1, False, True),
            "девятнадцатью": Numeral(19, 1, False, True),
            "девятнадцати": Numeral(19, 1, False, True),
            "девятнадцатое": Numeral(19, 1, False, True),
            "девятнадцатый": Numeral(19, 1, False, True),
            "девятнадцатая": Numeral(19, 1, False, True),
            "девятнадцатого": Numeral(19, 1, False, True),
            "девятнадцатой": Numeral(19, 1, False, True),
            "девятнадцатом": Numeral(19, 1, False, True),

            "двадцать": Numeral(20, 2, False),
            "двадцатью": Numeral(20, 2, False),
            "двадцати": Numeral(20, 2, False),
            "двадцатое": Numeral(20, 2, False),
            "двадцатый": Numeral(20, 2, False),
            "двадцатая": Numeral(20, 2, False),
            "двадцатого": Numeral(20, 2, False),
            "двадцатой": Numeral(20, 2, False),
            "двадцатом": Numeral(20, 2, False),

            "тридцать": Numeral(30, 2, False),
            "тридцатью": Numeral(30, 2, False),
            "тридцати": Numeral(30, 2, False),
            "тридцатое": Numeral(30, 2, False),
            "тридцатый": Numeral(30, 2, False),
            "тридцатая": Numeral(30, 2, False),
            "тридцатого": Numeral(30, 2, False),
            "тридцатой": Numeral(30, 2, False),
            "тридцатом": Numeral(30, 2, False),

            "сорок": Numeral(40, 2, False),
            "сорока": Numeral(40, 2, False),
            "сороковое": Numeral(40, 2, False),
            "сороковой": Numeral(40, 2, False),
            "сороковая": Numeral(40, 2, False),
            "сорокового": Numeral(40, 2, False),
            "сороковом": Numeral(40, 2, False),

            "пятьдесят": Numeral(50, 2, False),
            "пятидесяти": Numeral(50, 2, False),
            "пятьюдесятью": Numeral(50, 2, False),
            "пятидесятое": Numeral(50, 2, False),
            "пятидесятый": Numeral(50, 2, False),
            "пятидесятая": Numeral(50, 2, False),
            "пятидесятого": Numeral(50, 2, False),
            "пятидесятой": Numeral(50, 2, False),
            "пятидесятом": Numeral(50, 2, False),

            "шестьдесят": Numeral(60, 2, False),
            "шестидесяти": Numeral(60, 2, False),
            "шестьюдесятью": Numeral(60, 2, False),
            "шестидесятое": Numeral(60, 2, False),
            "шестидесятый": Numeral(60, 2, False),
            "шестидесятая": Numeral(60, 2, False),
            "шестидесятого": Numeral(60, 2, False),
            "шестидесятой": Numeral(60, 2, False),
            "шестидесятом": Numeral(60, 2, False),

            "семьдесят": Numeral(70, 2, False),
            "семидесяти": Numeral(70, 2, False),
            "семьюдесятью": Numeral(70, 2, False),
            "семидесятое": Numeral(70, 2, False),
            "семидесятый": Numeral(70, 2, False),
            "семидесятая": Numeral(70, 2, False),
            "семидесятого": Numeral(70, 2, False),
            "семидесятой": Numeral(70, 2, False),
            "семидесятом": Numeral(70, 2, False),

            "восемьдесят": Numeral(80, 2, False),
            "восьмидесяти": Numeral(80, 2, False),
            "восемьюдесятью": Numeral(80, 2, False),
            "восьмидесятое": Numeral(80, 2, False),
            "восьмидесятый": Numeral(80, 2, False),
            "восьмидесятая": Numeral(80, 2, False),
            "восьмидесятого": Numeral(80, 2, False),
            "восьмидесятой": Numeral(80, 2, False),
            "восьмидесятом": Numeral(80, 2, False),

            "девяносто": Numeral(90, 2, False),
            "девяноста": Numeral(90, 2, False),
            "девяностое": Numeral(90, 2, False),
            "девяностый": Numeral(90, 2, False),
            "девяностая": Numeral(90, 2, False),
            "девяностого": Numeral(90, 2, False),
            "девяностой": Numeral(90, 2, False),
            "девяностом": Numeral(90, 2, False),

            "сто": Numeral(100, 3, False),
            "ста": Numeral(100, 3, False),
            "сотое": Numeral(100, 3, False),
            "сотый": Numeral(100, 3, False),
            # "сотая": Numeral(100, 3, False),
            "сотого": Numeral(100, 3, False),
            "сотой": Numeral(100, 3, False),
            "сотом": Numeral(100, 3, False),

            "двести": Numeral(200, 3, False),
            "двухсот": Numeral(200, 3, False),
            "двумстам": Numeral(200, 3, False),
            "двухстах": Numeral(200, 3, False),
            "двустам": Numeral(200, 3, False),
            "двухсотый": Numeral(200, 3, False),
            "двухсотая": Numeral(200, 3, False),
            "двухсотого": Numeral(200, 3, False),
            "двухсотой": Numeral(200, 3, False),
            "двухсотом": Numeral(200, 3, False),

            "триста": Numeral(300, 3, False),
            "трехсот": Numeral(300, 3, False),
            "тремстам": Numeral(300, 3, False),
            "трехстах": Numeral(300, 3, False),
            "трёхстах": Numeral(300, 3, False),
            "трехсотое": Numeral(300, 3, False),
            "трехсотый": Numeral(300, 3, False),
            "трехсотая": Numeral(300, 3, False),
            "трехсотого": Numeral(300, 3, False),
            "трехсотой": Numeral(300, 3, False),
            "трехсотом": Numeral(300, 3, False),
            "трёхсот": Numeral(300, 3, False),
            "трёмстам": Numeral(300, 3, False),
            "трёхсотое": Numeral(300, 3, False),
            "трёхсотый": Numeral(300, 3, False),
            "трёхсотая": Numeral(300, 3, False),
            "трёхсотого": Numeral(300, 3, False),
            "трёхсотой": Numeral(300, 3, False),
            "трёхсотом": Numeral(300, 3, False),

            "четыреста": Numeral(400, 3, False),
            "четырехсот": Numeral(400, 3, False),
            "четыремстам": Numeral(400, 3, False),
            "четырехстах": Numeral(400, 3, False),
            "четырёхстах": Numeral(400, 3, False),
            "четырехсотое": Numeral(400, 3, False),
            "четырехсотый": Numeral(400, 3, False),
            "четырехсотая": Numeral(400, 3, False),
            "четырехсотого": Numeral(400, 3, False),
            "четырехсотой": Numeral(400, 3, False),
            "четырехсотом": Numeral(400, 3, False),
            "четырёхсот": Numeral(400, 3, False),
            "четырёмстам": Numeral(400, 3, False),
            "четырёхсотое": Numeral(400, 3, False),
            "четырёхсотый": Numeral(400, 3, False),
            "четырёхсотая": Numeral(400, 3, False),
            "четырёхсотого": Numeral(400, 3, False),
            "четырёхсотой": Numeral(400, 3, False),
            "четырёхсотом": Numeral(400, 3, False),

            "пятьсот": Numeral(500, 3, False),
            "пятистах": Numeral(500, 3, False),
            "пятисот": Numeral(500, 3, False),
            "пятистам": Numeral(500, 3, False),
            "пятьсотое": Numeral(500, 3, False),
            "пятьсотый": Numeral(500, 3, False),
            "пятьсотая": Numeral(500, 3, False),
            "пятьсотого": Numeral(500, 3, False),
            "пятьсотой": Numeral(500, 3, False),
            "пятьсотом": Numeral(500, 3, False),

            "шестьсот": Numeral(600, 3, False),
            "шестистах": Numeral(600, 3, False),
            "шестисот": Numeral(600, 3, False),
            "шестистам": Numeral(600, 3, False),
            "шестисотое": Numeral(600, 3, False),
            "шестисотый": Numeral(600, 3, False),
            "шестисотая": Numeral(600, 3, False),
            "шестисотого": Numeral(600, 3, False),
            "шестисотой": Numeral(600, 3, False),
            "шестисотом": Numeral(600, 3, False),

            "семьсот": Numeral(700, 3, False),
            "семистах": Numeral(700, 3, False),
            "семисот": Numeral(700, 3, False),
            "семистам": Numeral(700, 3, False),
            "семисотое": Numeral(700, 3, False),
            "семисотый": Numeral(700, 3, False),
            "семисотая": Numeral(700, 3, False),
            "семисотого": Numeral(700, 3, False),
            "семисотой": Numeral(700, 3, False),
            "семисотом": Numeral(700, 3, False),

            "восемьсот": Numeral(800, 3, False),
            "восьмистах": Numeral(800, 3, False),
            "восьмисот": Numeral(800, 3, False),
            "восьмистам": Numeral(800, 3, False),
            "восьмисотое": Numeral(800, 3, False),
            "восьмисотый": Numeral(800, 3, False),
            "восьмисотая": Numeral(800, 3, False),
            "восьмисотого": Numeral(800, 3, False),
            "восьмисотой": Numeral(800, 3, False),
            "восьмисотом": Numeral(800, 3, False),

            "девятьсот": Numeral(900, 3, False),
            "девятистах": Numeral(900, 3, False),
            "девятисот": Numeral(900, 3, False),
            "девятистам": Numeral(900, 3, False),
            "девятьсотое": Numeral(900, 3, False),
            "девятьсотый": Numeral(900, 3, False),
            "девятьсотая": Numeral(900, 3, False),
            "девятьсотого": Numeral(900, 3, False),
            "девятьсотой": Numeral(900, 3, False),
            "девятьсотом": Numeral(900, 3, False),

            "тысяч": Numeral(1000, 4, True),
            "тысяча": Numeral(1000, 4, True),
            "тысячи": Numeral(1000, 4, True),
            "тысячном": Numeral(1000, 4, True),

            "миллион": Numeral(1000000, 5, True),
            "миллиона": Numeral(1000000, 5, True),
            "миллионов": Numeral(1000000, 5, True),
            "миллионом": Numeral(1000000, 5, True),

            "миллиард": Numeral(1000000000, 6, True),
            "миллиарда": Numeral(1000000000, 6, True),
            "миллиардов": Numeral(1000000000, 6, True),
            "миллиардном": Numeral(1000000000, 6, True),

            "триллион": Numeral(1000000000000, 7, True),
            "триллиона": Numeral(1000000000000, 7, True),
            "триллионов": Numeral(1000000000000, 7, True),
            "триллионом": Numeral(1000000000000, 7, True)
        }

        self.max_token_error = 0.3

    def get_token_sum_error_from_lists(self, token):
        token_sum_error = 0

        if isinstance(token, NumericToken):
            token_sum_error += token.error

        else:
            for sub_token in token:
                token_sum_error += self.get_token_sum_error_from_lists(sub_token)

        return token_sum_error

    def parse_tokens(self, text_line, matrix_d, level, fraction=False):
        if text_line in self.tokens.keys():
            return [NumericToken(self.tokens[text_line])]
        elif fraction:
            return [NumericToken(self.tokens_fractions[text_line])]
        else:
            return [None]

    def parse(self, text):
        text = text.strip().lower()

        if len(text) == 0:
            return ParserResult(value=0, error=1)

        # Массив для рассчета расстояния Левенштейна
        max_token_length = 13
        matrix_d = np.zeros((2, max_token_length), dtype=np.float32)

        # Разбиваем текст на токены
        raw_token_list = re.split(r"\s+", text)
        all_token_list = []
        token_list = []
        result_text_list = []
        left_space_for_number = False
        current_level = 0

        # Обрабатываем токены
        for token_idx, raw_token in enumerate(raw_token_list):
            clean_token = raw_token.strip(string.punctuation)
            current_token_list = self.parse_tokens(clean_token, matrix_d, 0)

            # Определение дробного числа:
            if clean_token in ["целых", "целой", "целым", "целая"] and token_idx != 0:

                try:
                    if raw_token_list[token_idx - 1] in self.tokens \
                            and raw_token_list[token_idx + 1] in self.tokens \
                            or raw_token_list[token_idx + 1] == "и":
                        if raw_token_list[token_idx + 2] in self.tokens_fractions \
                                or raw_token_list[token_idx + 3] in self.tokens_fractions \
                                or raw_token_list[token_idx + 4] in self.tokens_fractions:
                            current_token_list = self.parse_tokens(clean_token, matrix_d, 0, fraction=True)

                except IndexError:
                    pass

            # Обработка первого порядка:
            if clean_token in ["десятых", "десятой", "десятым", "десятая"] and token_idx != 0:

                if raw_token_list[token_idx - 1] in self.tokens:
                    current_token_list = self.parse_tokens(clean_token, matrix_d, 0, fraction=True)

            # Обработка второго порядка:
            if clean_token in ["сотых", "сотой", "сотым", "сотая"] and token_idx != 0:

                if raw_token_list[token_idx - 1] in self.tokens:
                    current_token_list = self.parse_tokens(clean_token, matrix_d, 0, fraction=True)

            # Обработка третьего порядка:
            if clean_token in ["тысячных", "тысячной", "тысячным", "тысячная"] and token_idx != 0:

                if raw_token_list[token_idx - 1] in self.tokens:
                    current_token_list = self.parse_tokens(clean_token, matrix_d, 0, fraction=True)

            # Обработка четвёртого порядка:
            if clean_token in ["десятитысячных", "десятитысячной", "десятитысячным", "десятитысячная"] and token_idx != 0:

                if raw_token_list[token_idx - 1] in self.tokens:
                    current_token_list = self.parse_tokens(clean_token, matrix_d, 0, fraction=True)

            if raw_token == "тысяча":
                if token_idx == 0 or raw_token_list[token_idx - 1] != "одна":
                    current_token_list = [NumericToken(numeral=Numeral(1, 1, False)), current_token_list[0]]
                    current_level = 1

                    if len(token_list) > 0:
                        all_token_list.append(token_list)
                        token_list = []

                    left_space_for_number = False

            if raw_token == "тысячная":
                if token_idx == 0 or raw_token_list[token_idx - 1] != "одна":
                    current_token_list = [NumericToken(numeral=Numeral(1000, 1, False))]
                    current_level = 0

                    if len(token_list) > 0:
                        all_token_list.append(token_list)
                        token_list = []

                    left_space_for_number = False

            if raw_token == "десятая":
                if token_idx == 0 or raw_token_list[token_idx - 1] != "одна":
                    current_token_list = [NumericToken(numeral=Numeral(10, 1, False))]
                    current_level = 0

                    if len(token_list) > 0:
                        all_token_list.append(token_list)
                        token_list = []

                    left_space_for_number = False

            if raw_token == "сотая":
                if token_idx == 0 or raw_token_list[token_idx - 1] != "одна":
                    current_token_list = [NumericToken(numeral=Numeral(100, 1, False))]
                    current_level = 0

                    if len(token_list) > 0:
                        all_token_list.append(token_list)
                        token_list = []

                    left_space_for_number = False

            if raw_token == "ноль":
                if token_idx != 0 or raw_token_list[token_idx - 1] == "ноль":
                    current_token_list = [NumericToken(numeral=Numeral(0, 1, False)), current_token_list[0]]
                    current_level = 0

                    if len(token_list) > 0:
                        all_token_list.append(token_list)
                        token_list = []

                    left_space_for_number = False

            if current_token_list[0] is not None:
                previous_level = current_level
                current_level = current_token_list[len(current_token_list) - 1].numeral.level
                is_eleven_to_nineteen = current_token_list[0].numeral.is_eleven_to_nineteen

                if current_level != 0 and previous_level != 0 and (
                        (previous_level < current_level <= 3) or current_level == previous_level or (current_level < previous_level <= 2 and is_eleven_to_nineteen)):
                    all_token_list.append(token_list)
                    token_list = []
                    left_space_for_number = False

            bad_tokens = True

            for current_token in current_token_list:
                if current_token is None:
                    break

                if current_token.error <= self.max_token_error:
                    bad_tokens = False
                    break

            if bad_tokens is True:
                del current_token_list
                current_token_list = [None]

            if current_token_list[0] is not None:
                if left_space_for_number is False:
                    result_text_list.append("")
                    left_space_for_number = True

                for current_token in current_token_list:
                    token_list.append(current_token)

            else:
                result_text_list.append(raw_token)
                left_space_for_number = False
                current_level = 0

                if len(token_list) > 0:
                    all_token_list.append(token_list)
                    token_list = []

        if len(token_list) > 0:
            all_token_list.append(token_list)
            token_list = []

        parser_result_list = []

        for token_list in all_token_list:
            global_level = None
            local_level = None
            global_value = None
            local_value = None
            critical_error = False

            token_count = len(token_list)

            for current_token in token_list:
                current_error = self.get_token_sum_error_from_lists(current_token)

                if current_error > self.max_token_error:
                    continue

                value = current_token.numeral.value
                level = current_token.numeral.level
                multiplier = current_token.numeral.is_multiplier

                if multiplier:
                    if global_level is None:
                        if local_level is None:
                            global_value = value
                        else:
                            global_value = np.round(local_value * value, 5)

                        global_level = level
                        local_value = None
                        local_level = None

                        current_token.is_significant = True

                    elif global_level > level:
                        if local_level is None:
                            global_value = global_value + value
                        else:
                            if value == 0.1:
                                global_value = np.round((global_value + local_value * value), 1)
                            elif value == 0.01:
                                global_value = np.round((global_value + local_value * value), 2)
                            elif value == 0.001:
                                global_value = np.round((global_value + local_value * value), 3)
                            elif value == 0.0001:
                                global_value = np.round((global_value + local_value * value), 4)

                        global_level = level
                        local_value = None
                        local_level = None

                        current_token.is_significant = True

                    else:
                        # Ошибка несоответствия уровней
                        current_token.error = 1
                        current_token.is_significant = True
                        critical_error = True

                else:
                    # Простое числительное
                    if local_level is None:
                        local_value = value
                        local_level = level

                        current_token.is_significant = True

                    elif local_level > level:
                        local_value = local_value + value
                        local_level = level

                        current_token.is_significant = True

                    else:
                        # Ошибка несоответствия уровней
                        current_token.error = 1
                        current_token.is_significant = True
                        critical_error = True

            # Считаем общий уровень ошибки
            if token_count == 0:
                total_error = 1

            else:
                total_error = 0
                significant_token_count = 0

                for current_token in token_list:
                    if current_token.is_significant:
                        total_error += current_token.error
                        significant_token_count += 1

                total_error /= significant_token_count

            if critical_error:
                # Имела место критическая ошибка
                if total_error >= 0.5:
                    total_error = 1

                else:
                    total_error *= 2

            result_value = 0

            if global_value is not None:
                result_value += global_value

            if local_value is not None:
                result_value += local_value
            parser_result_list.append(ParserResult(result_value, total_error))

        return parser_result_list, result_text_list


import sys
import math
sys.path.insert(0, '../../AI_BASE')
import deepLearning_GPU as DL
import deepLearning_GPU_helper as helper
import deepLearning_main as DLmain
import AIBASE_main as main
import formula as f
import random
import readData as RD
import numpy as np

# q : q[l](t) = (x[l](t), y[l](t), h[l](t))
#     q[n][l] = (x[n][l], y[n][l], h[n][l])

# state: q[n][l], {a[n][l][k_l]}, {R[n][k_l]}
# each UAV : UAV0 = [x0, y0, h0], UAV1 = [x1, y1, h1], ...
# where x0 = [x00, x01, ..., x0t], x1 = [x10, x11, ..., x1t], ...
#       y0 = [y00, y01, ..., y0t], ...
#       h0 = [h00, h01, ..., h0t], ...
# that is, in the form of UAV[uav_No][x/y/h][time]
def getS(UAV, n, l, a, R):

    # q[n][l]      (q[n][l]) : the location of UAV l (1d array - x, y and h)
    # a[n][l][k_l] (a[n][l]) : the number of times that each device communicates with UAV l (1d array, for all devices)
    # R[n][k_l]    (R[n])    : the average throughput of devices (for each device k),
    #                          in l-th cluster (1d array, for the devices in l-th cluster)
    q = f.getqFromUAV(UAV, n)

    return [q, a[n][l], R[n]]

# get action with e-greedy while e increases
# with probability e, do the best action
# with probability (1-e), do the action randomly
def getActionWithE(Q, s, e):

    # do action randomly if Q table is empty
    if len(Q) == 0:
        actionIndex = random.randint(0, 3*3*3-1) # (3*3*3) actions in total
        return getAction(actionIndex)

    # Q Table           = [[[s0], [q00, q01, ...]], [[s1], [q10, q11, ...]], ...]
    # convert to input  = converted version of [[s0], [s1], ...]
    #            output = original  version of [[q00, q01, ...], [q10, q11, ...], ...]
    rand = random.random()
    stateIndex = 0 # index of state from the Q Table
    actionIndex = 0 # index of action from the Q Table, from 0 (-1, -1, -1) to 26 (1, 1, 1)

    # find state
    for i in range(len(Q)):
        if s == Q[i][0][0]:
            stateIndex = i
            break

    actions = len(Q[stateIndex][1]) # number of actions (len of [q00, q01, ...])

    # find action from [q00, q01, ...]
    if rand > e: # do the action randomly
        actionIndex = random.randint(0, actions-1)
    else: # do (find) the best action
        actionReward = Q[stateIndex][1][0] # init reward as q00

        # find action from [q00, q01, ...]
        for i in range(actions):
            if Q[stateIndex][1][i] > actionReward:
                actionReward = Q[stateIndex][1][i]
                actionIndex = i

    # return action with index of actionIndex
    return getAction(actionIndex)

# get action: from 0 (-1, -1, -1) to 26 (1, 1, 1)
def getAction(actionNo):
    return [int(actionNo / 9) - 1, int((actionNo % 9) / 3) - 1, actionNo % 3 - 1]

# get action space: from (-1, -1, -1) to (1, 1, 1)
def getActionSpace():
    actionSpace = []

    for x in range(-1, 2):
        for y in range(-1, 2):
            for z in range(-1, 2):
                actionSpace.append([x, y, z])

    return actionSpace

# get action index: from (-1, -1, -1) as 0, to (1, 1, 1) as 26
def getActionIndex(action):
    return (action[0]-1)*9 + (action[1]-1)*3 + (action[2]-1)

# get max(a')Q(s', a') (s' = nextState, a' = a_)
# useDL       : whether to use deep learning
# actionSpace : set of actions (in the form of [x, y, z])
def getMaxQ(s, action, n, l, R, actionSpace, clusters, B, PU, g, l_, o2, useDL):

    # get Q values for the action space of next state s'
    if useDL == True:
        (nextState, _) = getNextState(s, action, n, l, R, clusters, B, PU, g, l_, o2)
        rewardsOfActionsOfNextState = deepLearningQ_test(nextState)

    # find optimal action a' = a_ that is corresponding to max(a')Q(s', a')
    # action space = [[-1, -1, -1], [-1, -1, 0], [-1, -1, 1], [-1, 0, -1], ..., [1, 1, 1]]
    maxQ = -999999 # max(a')Q(s', a')
    for action_ in range(len(actionSpace)):

        # when using deep learning, Q value is the maximum value among rewards for actions on the next state
        # otherwise,                Q value is 0
        if useDL == True: QofNextStateAction = max(rewardsOfActionsOfNextState)
        else: QofNextStateAction = 0

        # update max(a')Q(s', a')
        if QofNextStateAction > maxQ: maxQ = QofNextStateAction

    # return
    return maxQ

# convert state = [q[n][l], {a[n][l][k_l]}, {R[n][k_l]}] to "1d array with numeric values"
# q[n][l] : the location of UAV l = (x[n][l], y[n][l], h[n][l])
def stateTo1dArray(state):
    q = state[0]
    a = state[1]
    R = state[2]
    return q + a + R # because q[n][l], a[n][l], and R[n][l] are all 1d arraies

# deep Learning using Q table (training function)
# printed : print the detail?
def deepLearningQ_training(Q, deviceName, epoch, printed):

    # Q Table           = [[[s0], [q00, q01, ...]], [[s1], [q10, q11, ...]], ...]
    # convert to input  = converted version of [[s0], [s1], ...]
    #            output = original  version of [[q00, q01, ...], [q10, q11, ...], ...]

    # input array (need to convert original array [s0])
    inputData = []
    for i in range(len(Q)): inputData.append(stateTo1dArray(Q[i][0]))

    # output array (as original)
    outputData = []
    for i in range(len(Q)): outputData.append(Q[i][1])

    # save input and output array as file
    if len(inputData) > 0:
        RD.saveArray('Q_input.txt', inputData)
    if len(outputData) > 0:
        RD.saveArray('Q_output.txt', outputData)

    # train using deep learning and save the model (testInputFile and testOutputFile is None)
    # need: modelConfig.txt
    # DON'T NEED TO APPLY SIGMOID to training output data, because DLmain.deeplearning applies it
    try:
        DLmain.deepLearning('Q_input.txt', 'Q_output.txt', None, None, None,
                            None, 0.0, None, 'modelConfig.txt', deviceName, epoch, printed, 'deepQ_model')
    except:
        print('Q_input.txt or Q_output.txt does not exist.')

# deep Learning using Q table (test function -> return reward values for each action)
def deepLearningQ_test(state):

    # convert state into 1d array
    stateArray = stateTo1dArray(state)

    # get reward values of the state
    # NEED TO APPLY INV-SIGMOID to test output data, because just getting model output
    trainedModel = deepLearning_GPU.deepLearningModel('deepQ_model', True)
    testO = deepLearning_GPU.modelOutput(trainedModel, stateArray)
    outputLayer = testO[len(testO)-1]

    # apply inverse sigmoid to output values
    for i in range(len(outputLayer)): # for each output data
        for j in range(len(outputLayer[0])): # for each value of output data
            outputLayer[i][j] = helper.invSigmoid(outputLayer[i][j])

    # return output layer
    return outputLayer

# update Q value

# Q Table: [[[s0], [q00, q01, ...]], [[s1], [q10, q11, ...]], ...]
# for new state-action-reward-nextState, get reward using deep learning,
# or set to 0 when amount of data is not big enoughly

# Q(s, a) <- (1 - alphaL)*Q(s, a) + alphaL*(Rwd(s, a) + r_*max(a')Q(s', a'))
# because using deep learning, just add Q value for this state and action

# when getting reward, for each UAV,
# 1. the UAV gets direct reward by the situation (in getReward function)
# 2. the state of UAV and the direct reward for the action is stored in the Q table
#    (for all other actions, save 0 as direct reward)
#    2-1. if there is a state that is the same with the state, update Q values for the state

# a            : array {a[n][l][k_l]}
# directReward : direct reward for the action
# alphaL       : learning rate
# r_           : discount factor
# useDL        : TRUE for getting reward using deep learning
#                FALSE for setting to 0
def updateQvalue(Q, s, action, a, directReward, alphaL, r_, n, l, R, useDL, clusters, B, PU, g, l_, o2):

    # obtain max(a')Q(s', a') (s' = nextState, a' = a_)
    actionSpace = getActionSpace()
    (nextState, deviceToCommunicate) = getNextState(s, action, n, l, R, clusters, B, PU, g, l_, o2)
    maxQ = getMaxQ(s, action, n, l, R, actionSpace, clusters, B, PU, g, l_, o2, useDL)

    # update {a[n][l][k_l]} (array of communication times)
    # where k is the index for the device to communicate
    a[n][l] = nextState[1]

    # update Q value
    sFound = False # find corresponding state?
    for i in range(len(Q)):
        if Q[i][0][0] == s: # Q[i] = [[s0], [q00, q01, ...]], Q[i][0] = [s0], Q[i][0][0] = s0
            actionIndex = getActionIndex(action)
            Q[i][1][actionIndex] = (1 - alphaL)*Q[i][1][actionIndex] + alphaL*(directReward + r_*maxQ)

            sFound = True
            break

    # when corresponding state is not found
    if sFound == False:

        # create new action-reward array,
        # and modify the value with corresponding index to the q value for action a
        qs = []
        for i in range(27): qs.append(0)
        actionIndex = getActionIndex(action)

        qs[actionIndex] = (1 - alphaL)*qs[actionIndex] + alphaL*(directReward + r_*maxQ)

        # append the state-action-reward [[s], qs] to Q table
        Q.append([[s], qs])

# get angle for the location of UAV l q[n][l] and time slot (n-1) to (n)
# IN RADIAN
def getAngle(q, n):
    if n > 0:
        locBefore = q[n-1][l]
        locAfter = q[n][l]

        # x and y value at time t-1
        xBefore = q[n-1][0]
        yBefore = q[n-1][1]

        # x and y value at time t
        xAfter = q[n][0]
        yAfter = q[n][1]

        # compute angle (angle 0 = positive direction of x axis)
        xDif = xAfter - xBefore
        yDif = yAfter - yBefore

        if yDif == 0 and xDif == 0: return 'not moved' # not moved
        if yDif > 0 and xDif == 0: return math.pi/2 # angle = 90 degree
        elif yDif < 0 and xDif == 0: return 3 * math.pi/2 # angle = 270 degree
        elif yDif >= 0: return math.atan(yDif / xDif) # 0 degree < angle < 180 degree (not 90 degree)
        elif yDif < 0: return math.atan(yDif / xDif) + math.pi # 180 degree < angle < 360 degree (not 270 degree)

    return 'n is not greater than 0'

# get next location when executing action a on state s
# s : [q[n][l], {a[n][l][k_l]}, {R[n][k_l]}]
# a : action [x, y, z]
# n : time slot index
def getNextLocation(s, action, n):

    # get current location
    curX = s[n][0] # q[n][l][0]
    curY = s[n][1] # q[n][l][1]
    curH = s[n][2] # q[n][l][2]

    # ASSUMPTION:
    # x = -1 : UAV turns to the left (-45 degrees)
    # x = 1  : UAV turns to the right (45 degrees)
    # y = -1 : UAV files backward (-5 m)
    # y = 1  : UAV flies forward (5 m)
    # z = -1 : UAV descends (-1 m)
    # z = 1  : UAV rises (1 m)

    # action ([-1, -1, -1] to [1, 1, 1])
    x = action[0]
    y = action[1]
    z = action[2]

    # get angle (radian) between time n-1 and n
    lastAngle = 0 # set default angle as 0
    for n_ in range(n, 0, -1):
        q = f.getq(curX, curY, curH)
        lastAngle = getAngle(q, n_)
        if lastAngle != 'not moved': break # lastAngle is numeric value

    # when all 'not moved' for n, n-1, n-2, ..., 0, then set as default
    if lastAngle == 'not moved': lastAngle = 0

    # get next angle by x
    if x == -1: nextAngle = (lastAngle - math.pi/4) % (math.pi*2)
    elif x == 0: nextAngle = lastAngle
    elif x == 1: nextAngle = (lastAngle + math.pi/4) % (math.pi*2)

    # get next location by x and y
    # initialize as current location
    nextX = curX
    nextY = curY

    if y == -1: # backward 5m
        nextX = curX + (-5) * math.cos(nextAngle)
        nextY = curY + (-5) * math.sin(nextAngle)
    elif y == 1: # forward 5m
        nextX = curX + 5 * math.cos(nextAngle)
        nextY = curY + 5 * math.sin(nextAngle)

    # get next height by z
    if z == -1: nextH = curH - 1
    elif z == 0: nextH = curH
    elif z == 1: nextH = curH + 1

    # return next location
    return [nextX, nextY, nextH]

# get next state
# s       : [q[n][l], {a[n][l][k_l]}, {R[n][k_l]}]
# q[n][l] : the location of UAV l = (x[n][l], y[n][l], h[n][l])
# a       : action ([-1, -1, -1] to [1, 1, 1])

## clusters: [c0_deviceList, c1_deviceList, ...]
# cK_deviceList: device list of cluster k,
#                in the form of [dev0, dev1, ...] == [[X0, Y0], [X1, Y1], ...]
def getNextState(s, action, n, l, R, clusters, B, PU, g, l_, o2):

    # get next location
    [nextX, nextY, nextH] = getNextLocation(s, action, n)

    # derive next a[n][l]
    # the number of times that each device communicates with UAV l (at time slot n)

    # from the paper: https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=8950047&tag=1
    # After flying to the next location,
    # UAV broadcasts energy flow or selects the device that owns the best channel condition
    # in its cluster for uplink communication.

    # find the device with best channel condition
    # NO NEED TO WRITE CODE FOR IT because RANDOMLY SELECT the device

    # init next a[n][l] as current a[n][l] from s (a number, not an array)
    next_a = s[1]

    # assumption: randomly select the device to communicate with
    # so, the probability for increasing next_a is 1/len(clusters[l])
    deviceToCommunicate = random.randint(0, len(clusters[l])-1)
    next_a[deviceToCommunicate] += 1

    # derive next R[n][k_l]
    # the average throughput of devices in l-th cluster
    nextR = 0
    for k in range(len(clusters[l])): # for each device in cluster l
        nextR += f.R_nkl(B, k, l, n, PU, g, l_, o2)
    nextR /= len(clusters[l])

    #### return ####
    # s' = [q'[n][l], {a'[n][l][k_l]}, {R'[n][k_l]}]
    return ([[nextX, nextY, nextH], next_a, nextR], deviceToCommunicate)

# target Q value yt = r + r_*max(a')Q(s', a', w) = r + r_*max(a')Q(s', a')
# useDL : whether to use deep learning
def yt(r, r_, Q, s, action, n, l, R, actionSpace, clusters, B, PU, g, l_, o2, useDL):
    maxQ = getMaxQ(s, action, n, l, R, actionSpace, clusters, B, PU, g, l_, o2, useDL)
    return r + r_ * maxQ

# Q^pi(s, a) = E[Sum(k=0, inf)(r_^k * r_(t+k)) | st, at, pi]
#def Qpi(r_, r, t, k, s, a, pi):
#    # ( [6] get Q(s, a) using policy pi )


import hashlib
import os
from typing import Any, Dict

import cv2
import numpy as np
import torch

from theseus.base.metrics.metric_template import Metric
from theseus.base.utilities.loggers import LoggerObserver
from theseus.cv.base.utilities.visualization.visualizer import Visualizer

# To fix tensorflow bug on Google Colab
try:
    import tensorboard as tb
    import tensorflow as tf

    tf.io.gfile = tb.compat.tensorflow_stub.io.gfile
except ModuleNotFoundError:
    pass

class EmbeddingProjection(Metric):
    """
    Visualize embedding project for classification
    """

    def __init__(self, save_dir=".temp", has_labels=False, **kwargs):
        super().__init__(**kwargs)
        self.has_labels = has_labels
        self.save_dir = save_dir
        self.visualizer = Visualizer()
        self.logger = LoggerObserver.getLogger("main")
        self.reset()

        os.makedirs(self.save_dir, exist_ok=True)

    def update(self, outputs: Dict[str, Any], batch: Dict[str, Any]):
        """
        Perform calculation based on prediction and targets
        """
        features = outputs["features"].detach().cpu().numpy()
        predictions = (
            torch.argmax(outputs["outputs"].detach().cpu(), dim=1).numpy().tolist()
        )
        inputs = batch["inputs"]
        targets = batch["targets"].numpy().tolist()
        img_names = batch["img_names"]

        for i, _ in enumerate(features):
            filename = hashlib.sha256(img_names[i].encode("utf-8")).hexdigest()
            pred_img = self.visualizer.denormalize(inputs[i])
            pred_img = cv2.resize(
                pred_img, dsize=(64, 64), interpolation=cv2.INTER_CUBIC
            )

            embedding_path = self.save_dir + r"/" + filename + "_feat.npy"
            image_path = self.save_dir + r"/" + filename + "_img.npy"
            np.save(image_path, pred_img)
            np.save(embedding_path, features[i])

            self.embeddings.append(embedding_path)
            self.imgs.append(image_path)
            self.predictions.append(predictions[i])

            if self.has_labels:
                self.labels.append(targets[i])

    def reset(self):
        self.embeddings = []
        self.imgs = []
        self.predictions = []
        if self.has_labels:
            self.labels = []
        else:
            self.labels = None

    def value(self):

        all_embeddings = [np.load(embedding_path) for embedding_path in self.embeddings]
        all_images = [np.load(image_path) for image_path in self.imgs]
        all_images = [a.transpose(2, 0, 1) for a in all_images]  # (HWC) -> (CHW)

        ## Stack into tensors
        all_embeddings = torch.from_numpy(np.stack(all_embeddings, axis=0))
        all_images = torch.from_numpy(np.stack(all_images, axis=0))

        ## Metadata, in column style
        if self.has_labels:
            metadata = [a for a in zip(self.labels, self.predictions)]
            metadata_header = ["ground truth", "prediction"]
        else:
            metadata = self.predictions
            metadata_header = ["prediction"]

        ## Log to tensorboard
        self.logger.log(
            [
                {
                    "tag": f"Validation/projection",
                    "value": all_embeddings,
                    "type": LoggerObserver.EMBED,
                    "kwargs": {
                        "step": 0,
                        "label_img": all_images,
                        "metadata": metadata,
                        "metadata_header": metadata_header,
                    },
                }
            ]
        )

        return {"projection": "Embedding projection generated"}


import boto3

# Get the service resource.
dynamodb = boto3.resource('dynamodb', region_name = 'us-west-2')

# Create the DynamoDB table.
table = dynamodb.create_table(
    TableName='Games1',
    KeySchema=[
        {
            'AttributeName': 'gname',
            'KeyType': 'HASH'
        },
        {
            'AttributeName': 'gid',
            'KeyType': 'RANGE'
        }
    ],
    AttributeDefinitions=[
        {
            'AttributeName': 'gname',
            'AttributeType': 'S'
        },
        {
            'AttributeName': 'gid',
            'AttributeType': 'N'
        }
    ],
    ProvisionedThroughput={
        'ReadCapacityUnits': 5,
        'WriteCapacityUnits': 5
    }
)

table.meta.client.get_waiter('table_exists').wait(TableName='Games1')
print(table.item_count)#To know if the table was creted succesfully

table.put_item(
    Item = {
        'gid': 1,
        'gname': 'Football',
        'publisher': 'Doe',
        'rating': 4,
        'genres': 'sports',
    }
)

table.meta.client.get_waiter('table_exists').wait(TableName='Games1')
print(table.item_count)#To know if the table was creted succesfully


with open('input','r') as f:
    max = 0
    courant = 0
    for ligne in f.readlines():
        if ligne != '\n':
            L = ligne.split('\n')[0]
            cal = int(L)
            courant += cal
        else:
            if courant > max:
                max = courant
                print('reset')
            courant  = 0

print(max)


import face_recognition as fr
from engine import reconhece_rosto, getRostos

def register_face(matricula):

    while(True):
        desconhecido = reconhece_rosto(f"{matricula}.png")

        if desconhecido[0]:
            rosto_desconhecido = desconhecido[1][0]
            rostos_conhecidos, nome_rostos = getRostos()
            resultados = fr.compare_faces(rostos_conhecidos, rosto_desconhecido)
            print("resultados = ",resultados)

            for i in range(len(nome_rostos)):
                resultado = resultados[i]
                if(resultado):
                    print(f"Rosto do {matricula} foi reconhecido")
                    exit(0)
    return


"""UniFi Protect Bootstrap."""
from __future__ import annotations

import asyncio
from copy import deepcopy
from dataclasses import dataclass
from datetime import datetime
import logging
from typing import Any, Dict, List, Optional, Set, Tuple, cast
from uuid import UUID

from aiohttp.client_exceptions import ServerDisconnectedError

try:
    from pydantic.v1 import PrivateAttr, ValidationError
except ImportError:
    from pydantic import PrivateAttr, ValidationError  # type: ignore

from pyunifiprotect.data.base import (
    RECENT_EVENT_MAX,
    ProtectBaseObject,
    ProtectModel,
    ProtectModelWithId,
)
from pyunifiprotect.data.convert import create_from_unifi_dict
from pyunifiprotect.data.devices import (
    Bridge,
    Camera,
    Chime,
    Doorlock,
    Light,
    ProtectAdoptableDeviceModel,
    Sensor,
    Viewer,
)
from pyunifiprotect.data.nvr import NVR, Event, Liveview
from pyunifiprotect.data.types import EventType, FixSizeOrderedDict, ModelType
from pyunifiprotect.data.user import Group, User
from pyunifiprotect.data.websocket import (
    WSAction,
    WSJSONPacketFrame,
    WSPacket,
    WSSubscriptionMessage,
)
from pyunifiprotect.exceptions import ClientError
from pyunifiprotect.utils import utc_now

_LOGGER = logging.getLogger(__name__)

MAX_SUPPORTED_CAMERAS = 256
MAX_EVENT_HISTORY_IN_STATE_MACHINE = MAX_SUPPORTED_CAMERAS * 2
STATS_KEYS = {
    "storageStats",
    "stats",
    "systemInfo",
    "phyRate",
    "wifiConnectionState",
    "upSince",
    "uptime",
    "lastSeen",
    "recordingSchedules",
    "eventStats",
}
IGNORE_DEVICE_KEYS = {"nvrMac", "guid"}

CAMERA_EVENT_ATTR_MAP: Dict[EventType, Tuple[str, str]] = {
    EventType.MOTION: ("last_motion", "last_motion_event_id"),
    EventType.SMART_DETECT: ("last_smart_detect", "last_smart_detect_event_id"),
    EventType.SMART_DETECT_LINE: ("last_smart_detect", "last_smart_detect_event_id"),
    EventType.SMART_AUDIO_DETECT: ("last_smart_audio_detect", "last_smart_audio_detect_event_id"),
    EventType.RING: ("last_ring", "last_ring_event_id"),
}

def _remove_stats_keys(data: Dict[str, Any]) -> None:
    for key in STATS_KEYS.intersection(data):
        del data[key]

def _process_light_event(event: Event) -> None:
    if event.light is None:
        return

    dt = event.light.last_motion
    if dt is None or event.start >= dt or (event.end is not None and event.end >= dt):
        event.light.last_motion_event_id = event.id

def _process_sensor_event(event: Event) -> None:
    if event.sensor is None:
        return

    if event.type == EventType.MOTION_SENSOR:
        dt = event.sensor.motion_detected_at
        if dt is None or event.start >= dt or (event.end is not None and event.end >= dt):
            event.sensor.last_motion_event_id = event.id
    elif event.type in (EventType.SENSOR_CLOSED, EventType.SENSOR_OPENED):
        dt = event.sensor.open_status_changed_at
        if dt is None or event.start >= dt or (event.end is not None and event.end >= dt):
            event.sensor.last_contact_event_id = event.id
    elif event.type == EventType.SENSOR_EXTREME_VALUE:
        dt = event.sensor.extreme_value_detected_at
        if dt is None or event.start >= dt or (event.end is not None and event.end >= dt):
            event.sensor.extreme_value_detected_at = event.end
            event.sensor.last_value_event_id = event.id
    elif event.type == EventType.SENSOR_ALARM:
        dt = event.sensor.alarm_triggered_at
        if dt is None or event.start >= dt or (event.end is not None and event.end >= dt):
            event.sensor.last_value_event_id = event.id

def _process_camera_event(event: Event) -> None:
    if event.camera is None:
        return

    dt_attr, event_attr = CAMERA_EVENT_ATTR_MAP[event.type]
    dt = getattr(event.camera, dt_attr)
    if dt is None or event.start >= dt or (event.end is not None and event.end >= dt):
        setattr(event.camera, event_attr, event.id)
        setattr(event.camera, dt_attr, event.start)
        if event.type in (EventType.SMART_DETECT, EventType.SMART_DETECT_LINE):
            for smart_type in event.smart_detect_types:
                event.camera.last_smart_detect_event_ids[smart_type] = event.id
                event.camera.last_smart_detects[smart_type] = event.start
        elif event.type == EventType.SMART_AUDIO_DETECT:
            for smart_type in event.smart_detect_types:
                audio_type = smart_type.audio_type
                if audio_type is None:
                    continue
                event.camera.last_smart_audio_detect_event_ids[audio_type] = event.id
                event.camera.last_smart_audio_detects[audio_type] = event.start

@dataclass
class WSStat:
    model: str
    action: str
    keys: List[str]
    keys_set: List[str]
    size: int
    filtered: bool

class ProtectDeviceRef(ProtectBaseObject):
    model: ModelType
    id: str

class Bootstrap(ProtectBaseObject):
    auth_user_id: str
    access_key: str
    cameras: Dict[str, Camera]
    users: Dict[str, User]
    groups: Dict[str, Group]
    liveviews: Dict[str, Liveview]
    nvr: NVR
    viewers: Dict[str, Viewer]
    lights: Dict[str, Light]
    bridges: Dict[str, Bridge]
    sensors: Dict[str, Sensor]
    doorlocks: Dict[str, Doorlock]
    chimes: Dict[str, Chime]
    last_update_id: UUID

    # TODO:
    # legacyUFVs
    # displays
    # schedules

    # not directly from UniFi
    events: Dict[str, Event] = FixSizeOrderedDict()
    capture_ws_stats: bool = False
    mac_lookup: dict[str, ProtectDeviceRef] = {}
    id_lookup: dict[str, ProtectDeviceRef] = {}
    _ws_stats: List[WSStat] = PrivateAttr([])
    _has_doorbell: Optional[bool] = PrivateAttr(None)
    _has_smart: Optional[bool] = PrivateAttr(None)
    _has_media: Optional[bool] = PrivateAttr(None)
    _recording_start: Optional[datetime] = PrivateAttr(None)
    _refresh_tasks: Set[asyncio.Task[None]] = PrivateAttr(set())

    @classmethod
    def unifi_dict_to_dict(cls, data: Dict[str, Any]) -> Dict[str, Any]:
        api = cls._get_api(data.get("api"))
        data["macLookup"] = {}
        data["idLookup"] = {}
        for model_type in ModelType.bootstrap_models():
            key = model_type + "s"
            items: Dict[str, ProtectModel] = {}
            for item in data[key]:
                if api is not None and api.ignore_unadopted and not item.get("isAdopted", True):
                    continue

                ref = {"model": model_type, "id": item["id"]}
                items[item["id"]] = item
                data["idLookup"][item["id"]] = ref
                if "mac" in item:
                    cleaned_mac = item["mac"].lower().replace(":", "")
                    data["macLookup"][cleaned_mac] = ref
            data[key] = items

        return super().unifi_dict_to_dict(data)

    def unifi_dict(self, data: Optional[Dict[str, Any]] = None, exclude: Optional[Set[str]] = None) -> Dict[str, Any]:
        data = super().unifi_dict(data=data, exclude=exclude)

        if "events" in data:
            del data["events"]
        if "captureWsStats" in data:
            del data["captureWsStats"]
        if "macLookup" in data:
            del data["macLookup"]
        if "idLookup" in data:
            del data["idLookup"]

        for model_type in ModelType.bootstrap_models():
            attr = model_type + "s"
            if attr in data and isinstance(data[attr], dict):
                data[attr] = list(data[attr].values())

        return data

    @property
    def ws_stats(self) -> List[WSStat]:
        return self._ws_stats

    def clear_ws_stats(self) -> None:
        self._ws_stats = []

    @property
    def auth_user(self) -> User:
        user: User = self.api.bootstrap.users[self.auth_user_id]
        return user

    @property
    def has_doorbell(self) -> bool:
        if self._has_doorbell is None:
            self._has_doorbell = any(c.feature_flags.is_doorbell for c in self.cameras.values())

        return self._has_doorbell

    @property
    def recording_start(self) -> datetime | None:
        """Get earilest recording date."""

        if self._recording_start is None:
            try:
                self._recording_start = min(
                    c.stats.video.recording_start
                    for c in self.cameras.values()
                    if c.stats.video.recording_start is not None
                )
            except ValueError:
                return None
        return self._recording_start

    @property
    def has_smart_detections(self) -> bool:
        """Check if any camera has smart detections."""

        if self._has_smart is None:
            self._has_smart = any(c.feature_flags.has_smart_detect for c in self.cameras.values())
        return self._has_smart

    @property
    def has_media(self) -> bool:
        """Checks if user can read media for any camera."""

        if self._has_media is None:
            if self.recording_start is None:
                return False
            self._has_media = any(c.can_read_media(self.auth_user) for c in self.cameras.values())
        return self._has_media

    def get_device_from_mac(self, mac: str) -> ProtectAdoptableDeviceModel | None:
        """Retrieve a device from MAC address."""

        mac = mac.lower().replace(":", "").replace("-", "").replace("_", "")
        ref = self.mac_lookup.get(mac)
        if ref is None:
            return None

        devices = getattr(self, f"{ref.model.value}s")
        return cast(ProtectAdoptableDeviceModel, devices.get(ref.id))

    def get_device_from_id(self, device_id: str) -> ProtectAdoptableDeviceModel | None:
        """Retrieve a device from device ID (without knowing model type)."""

        ref = self.id_lookup.get(device_id)
        if ref is None:
            return None
        devices = getattr(self, f"{ref.model.value}s")
        return cast(ProtectAdoptableDeviceModel, devices.get(ref.id))

    def process_event(self, event: Event) -> None:
        if event.type in CAMERA_EVENT_ATTR_MAP and event.camera is not None:
            _process_camera_event(event)
        elif event.type == EventType.MOTION_LIGHT and event.light is not None:
            _process_light_event(event)
        elif event.type == EventType.MOTION_SENSOR and event.sensor is not None:
            _process_sensor_event(event)

        self.events[event.id] = event

    def _create_stat(self, packet: WSPacket, keys_set: List[str], filtered: bool) -> None:
        if self.capture_ws_stats:
            self._ws_stats.append(
                WSStat(
                    model=packet.action_frame.data["modelKey"],
                    action=packet.action_frame.data["action"],
                    keys=list(packet.data_frame.data),
                    keys_set=keys_set,
                    size=len(packet.raw),
                    filtered=filtered,
                )
            )

    def _get_frame_data(self, packet: WSPacket) -> Tuple[Dict[str, Any], Optional[Dict[str, Any]]]:
        if self.capture_ws_stats:
            return deepcopy(packet.action_frame.data), deepcopy(packet.data_frame.data)
        return packet.action_frame.data, packet.data_frame.data

    def _process_add_packet(self, packet: WSPacket, data: Dict[str, Any]) -> Optional[WSSubscriptionMessage]:
        obj = create_from_unifi_dict(data, api=self._api)

        if isinstance(obj, Event):
            self.process_event(obj)
        elif isinstance(obj, NVR):
            self.nvr = obj
        elif (
            isinstance(obj, ProtectAdoptableDeviceModel)
            and obj.model is not None
            and obj.model.value in ModelType.bootstrap_models()
        ):
            key = obj.model.value + "s"
            if not self.api.ignore_unadopted or (obj.is_adopted and not obj.is_adopted_by_other):
                getattr(self, key)[obj.id] = obj
                ref = ProtectDeviceRef(model=obj.model, id=obj.id)
                self.id_lookup[obj.id] = ref
                self.mac_lookup[obj.mac.lower().replace(":", "")] = ref
        else:
            _LOGGER.debug("Unexpected bootstrap model type for add: %s", obj.model)
            return None

        updated = obj.dict()
        self._create_stat(packet, list(updated), False)
        return WSSubscriptionMessage(
            action=WSAction.ADD, new_update_id=self.last_update_id, changed_data=updated, new_obj=obj
        )

    def _process_remove_packet(
        self, packet: WSPacket, data: Optional[Dict[str, Any]]
    ) -> Optional[WSSubscriptionMessage]:
        model = packet.action_frame.data.get("modelKey")
        device_id = packet.action_frame.data.get("id")
        devices = getattr(self, f"{model}s", None)

        if devices is None:
            return None

        self.id_lookup.pop(device_id, None)
        device = devices.pop(device_id, None)
        if device is None:
            return None
        self.mac_lookup.pop(device.mac.lower().replace(":", ""), None)

        self._create_stat(packet, [], False)
        return WSSubscriptionMessage(
            action=WSAction.REMOVE, new_update_id=self.last_update_id, changed_data={}, old_obj=device
        )

    def _process_nvr_update(
        self, packet: WSPacket, data: Dict[str, Any], ignore_stats: bool
    ) -> Optional[WSSubscriptionMessage]:
        if ignore_stats:
            _remove_stats_keys(data)
        # nothing left to process
        if not data:
            self._create_stat(packet, [], True)
            return None

        data = self.nvr.unifi_dict_to_dict(data)
        # nothing left to process
        if not data:
            self._create_stat(packet, [], True)
            return None

        old_nvr = self.nvr.copy()
        self.nvr = self.nvr.update_from_dict(deepcopy(data))

        self._create_stat(packet, list(data), False)
        return WSSubscriptionMessage(
            action=WSAction.UPDATE,
            new_update_id=self.last_update_id,
            changed_data=data,
            new_obj=self.nvr,
            old_obj=old_nvr,
        )

    def _process_device_update(
        self, packet: WSPacket, action: Dict[str, Any], data: Dict[str, Any], ignore_stats: bool
    ) -> Optional[WSSubscriptionMessage]:
        model_type = action["modelKey"]
        if ignore_stats:
            _remove_stats_keys(data)
        for key in IGNORE_DEVICE_KEYS.intersection(data):
            del data[key]
        # `last_motion` from cameras update every 100 milliseconds when a motion event is active
        # this overrides the behavior to only update `last_motion` when a new event starts
        if model_type == "camera" and "lastMotion" in data:
            del data["lastMotion"]
        # nothing left to process
        if not data:
            self._create_stat(packet, [], True)
            return None

        key = f"{model_type}s"
        devices = getattr(self, key)
        if action["id"] in devices:
            if action["id"] not in devices:
                raise ValueError(f"Unknown device update for {model_type}: { action['id']}")
            obj: ProtectModelWithId = devices[action["id"]]
            data = obj.unifi_dict_to_dict(data)
            old_obj = obj.copy()
            obj = obj.update_from_dict(deepcopy(data))
            now = utc_now()

            if isinstance(obj, Event):
                self.process_event(obj)
            elif isinstance(obj, Camera):
                if "last_ring" in data and obj.last_ring:
                    is_recent = obj.last_ring + RECENT_EVENT_MAX >= now
                    _LOGGER.debug("last_ring for %s (%s)", obj.id, is_recent)
                    if is_recent:
                        obj.set_ring_timeout()
            elif isinstance(obj, Sensor):
                if "alarm_triggered_at" in data and obj.alarm_triggered_at:
                    is_recent = obj.alarm_triggered_at + RECENT_EVENT_MAX >= now
                    _LOGGER.debug("alarm_triggered_at for %s (%s)", obj.id, is_recent)
                    if is_recent:
                        obj.set_alarm_timeout()

            devices[action["id"]] = obj

            self._create_stat(packet, list(data), False)
            return WSSubscriptionMessage(
                action=WSAction.UPDATE,
                new_update_id=self.last_update_id,
                changed_data=data,
                new_obj=obj,
                old_obj=old_obj,
            )

        # ignore updates to events that phase out
        if model_type != ModelType.EVENT.value:
            _LOGGER.debug("Unexpected %s: %s", key, action["id"])
        return None

    def process_ws_packet(
        self, packet: WSPacket, models: Optional[Set[ModelType]] = None, ignore_stats: bool = False
    ) -> Optional[WSSubscriptionMessage]:
        if models is None:
            models = set()

        if not isinstance(packet.action_frame, WSJSONPacketFrame):
            _LOGGER.debug("Unexpected action frame format: %s", packet.action_frame.payload_format)

        if not isinstance(packet.data_frame, WSJSONPacketFrame):
            _LOGGER.debug("Unexpected data frame format: %s", packet.data_frame.payload_format)

        action, data = self._get_frame_data(packet)
        if action["newUpdateId"] is not None:
            self.last_update_id = UUID(action["newUpdateId"])

        if action["modelKey"] not in ModelType.values():
            _LOGGER.debug("Unknown model type: %s", action["modelKey"])
            self._create_stat(packet, [], True)
            return None

        if len(models) > 0 and ModelType(action["modelKey"]) not in models:
            self._create_stat(packet, [], True)
            return None

        if action["action"] == "remove":
            return self._process_remove_packet(packet, data)

        if data is None or len(data) == 0:
            self._create_stat(packet, [], True)
            return None

        try:
            if action["action"] == "add":
                return self._process_add_packet(packet, data)

            if action["action"] == "update":
                if action["modelKey"] == ModelType.NVR.value:
                    return self._process_nvr_update(packet, data, ignore_stats)
                if action["modelKey"] in ModelType.bootstrap_models() or action["modelKey"] == ModelType.EVENT.value:
                    return self._process_device_update(packet, action, data, ignore_stats)
                _LOGGER.debug("Unexpected bootstrap model type deviceadoptedfor update: %s", action["modelKey"])
        except (ValidationError, ValueError) as err:
            self._handle_ws_error(action, err)

        self._create_stat(packet, [], True)
        return None

    def _handle_ws_error(self, action: Dict[str, Any], err: Exception) -> None:
        msg = ""
        if action["modelKey"] == "event":
            msg = f"Validation error processing event: {action['id']}. Ignoring event."
        else:
            try:
                model_type = ModelType(action["modelKey"])
                device_id = action["id"]
                task = asyncio.create_task(self.refresh_device(model_type, device_id))
                self._refresh_tasks.add(task)
                task.add_done_callback(self._refresh_tasks.discard)
            except (ValueError, IndexError):
                msg = f"{action['action']} packet caused invalid state. Unable to refresh device."
            else:
                msg = f"{action['action']} packet caused invalid state. Refreshing device: {model_type} {device_id}"
        _LOGGER.debug("%s Error: %s", msg, err)

    async def refresh_device(self, model_type: ModelType, device_id: str) -> None:
        """Refresh a device in the bootstrap."""

        try:
            if model_type == ModelType.NVR:
                device: ProtectModelWithId = await self.api.get_nvr()
            else:
                device = await self.api.get_device(model_type, device_id)
        except (ValidationError, TimeoutError, ClientError, ServerDisconnectedError):
            _LOGGER.warning("Failed to refresh device: %s %s", model_type, device_id)
            return

        if isinstance(device, NVR):
            self.nvr = device
        else:
            devices = getattr(self, f"{model_type.value}s")
            devices[device.id] = device
        _LOGGER.debug("Successfully refresh device: %s %s", model_type, device_id)

    async def get_is_prerelease(self) -> bool:
        """Get if current version of Protect is a prerelease version."""

        return await self.nvr.get_is_prerelease()


"""This function uses BeautifulSoup to retrieve a table from the National Center for Education Statistics with the number of
students enrolled in public schools for every state. Since the website (and much of the table format) is the same as the one
used for the web_parser1 dataset, much of the code is very similar."""

def web_parser3():
    import requests
    from bs4 import BeautifulSoup
    import pandas as pd

    response_obj_3 = requests.get("https://nces.ed.gov/programs/digest/d19/tables/dt19_203.20.asp")
    soup_obj_3 = BeautifulSoup(response_obj_3.text, "html.parser")

    student_enrollment_df = pd.DataFrame(columns = ["Fall 2017 Enrollment"])

    table_row_tags = soup_obj_3.find_all("tr")
    state_row_tags = table_row_tags[14:65]

    for state_row in state_row_tags:
        row_data = state_row.find_all("td")
        state_name = state_row.find("th").text
        student_enrollment_df.loc[str(state_name)] = int(row_data[12].text.replace(",", ""))

    student_enrollment_df.to_csv("Public School Enrollment by State.csv", index = True)

Code for Data Collection & Cleaning/web_parser3.py


# Valor inicial da divida | juro mensal | numero de meses
# total == valorInicial + (valorInicial * (Juros/100))
# Total da divida

valorInicial = float(input('Digite o valor inicial da divida: R$ '))
valorJuro = float(input('Digite a taxa de juros mensais (%): '))
meses = int(input('Digite o numero de meses para pagar: '))
valorFinal = valorInicial
i = 1
print('\n')

# Condições inválidas
if valorInicial < 0:
    print('Voce não possui uma divida!')

elif valorJuro < 0 or meses <= 0:
    print('Valores INVÁLIDOS!')
# Processando os dados corretos
else:
    while i <= meses:
        valorFinal += (valorFinal * (valorJuro / 100))
        print(f'{i}º mês sua divida estará em R$ {valorFinal:.2f}')
        i+=1
print(f'O valor final da divida é de R$ {valorFinal:.2f}')
print(f'Você pagará um total de R$ {valorFinal-valorInicial:.2f} de JUROS!')


import torch
import sys

from vision.nn.mobilenet import MobileNetV1
from extract_tf_weights import read_weights

def fill_weights_torch_model(weights, state_dict):
    for name in state_dict:
        if name == 'classifier.weight':
            weight = weights['MobilenetV1/Logits/Conv2d_1c_1x1/weights']
            weight = torch.tensor(weight, dtype=torch.float32).permute(3, 2, 0, 1)
            assert state_dict[name].size() == weight.size()
            state_dict[name] = weight
        elif name == 'classifier.bias':
            bias = weights['MobilenetV1/Logits/Conv2d_1c_1x1/biases']
            bias = torch.tensor(bias, dtype=torch.float32)
            assert state_dict[name].size() == bias.size()
            state_dict[name] = bias
        elif name.endswith('BatchNorm.weight'):
            key = name.replace("features", "MobilenetV1").replace(".", "/").replace('BatchNorm/weight', 'BatchNorm/gamma')
            weight = torch.tensor(weights[key], dtype=torch.float32)
            assert weight.size() == state_dict[name].size()
            state_dict[name] = weight
        elif name.endswith('BatchNorm.bias'):
            key = name.replace("features", "MobilenetV1").replace(".", "/").replace('BatchNorm/bias', 'BatchNorm/beta')
            bias = torch.tensor(weights[key], dtype=torch.float32)
            assert bias.size() == state_dict[name].size()
            state_dict[name] = bias
        elif name.endswith('running_mean'):
            key = name.replace("features", "MobilenetV1").replace(".", "/").replace('running_mean', 'moving_mean')
            running_mean = torch.tensor(weights[key], dtype=torch.float32)
            assert running_mean.size() == state_dict[name].size()
            state_dict[name] = running_mean
        elif name.endswith('running_var'):
            key = name.replace("features", "MobilenetV1").replace(".", "/").replace('running_var', 'moving_variance')
            running_var = torch.tensor(weights[key], dtype=torch.float32)
            assert running_var.size() == state_dict[name].size()
            state_dict[name] = running_var
        elif name.endswith('depthwise.weight'):
            key = name.replace("features", "MobilenetV1").replace(".", "/")
            key = key.replace('depthwise/weight', 'depthwise/depthwise_weights')
            weight = torch.tensor(weights[key], dtype=torch.float32).permute(2, 3, 0, 1)
            assert weight.size() == state_dict[name].size()
            state_dict[name] = weight
        else:
            key = name.replace("features", "MobilenetV1").replace(".", "/").replace('weight', 'weights')
            weight = torch.tensor(weights[key], dtype=torch.float32).permute(3, 2, 0, 1)
            assert weight.size() == state_dict[name].size()
            state_dict[name] = weight

if __name__ == '__main__':
    if len(sys.argv) < 3:
        print("Usage: python translate_tf_modelnetv1.py <tf_model.pb> <pytorch_weights.pth>")
    tf_model = sys.argv[1]
    torch_weights_path = sys.argv[2]
    print("Extract weights from tf model.")
    weights = read_weights(tf_model)

    net = MobileNetV1(1001)
    states = net.state_dict()
    print("Translate tf weights.")
    fill_weights_torch_model(weights, states)
    torch.save(states, torch_weights_path)


suma = 0
n = 0
colección = ""

interrumpido = False
while not interrumpido:
    val = float(input("Ingrese un valor (0 para salir): "))
    if val != 0:
        suma += val
        n += 1
        colección = str(val) + ", " + colección
    else:
        interrumpido = True

prom = suma / n
print(f'El promedio entre {colección[:-2]} es:\n{prom}') #Invesigue sobre los f'strings #Investigue que le hace [:-1] a un string

Ejercicios/Control de Flujo/Flujo Cíclico/Soluciones/ej0.py


__title__ = "Reinforcement Drawing Configuration"
__author__ = "Suraj"
__url__ = "https://www.freecadweb.org"

from pathlib import Path

# stroke-width of structure in drawing svg
STRUCTURE_STROKE_WIDTH = 0.5

# Fill style of structure
# - set it to "Automatic" to automatically select structure color
# - set it to "Custom" to choose structure color value from variable
#   STRUCTURE_COLOR
# - set it to none to not fill nothing in structure
STRUCTURE_COLOR_STYLE = "Automatic"

# Fill color for structure in drawing svg
# Format: (r, g, b)
# r, g, b value should be between 0 to 1, so you may need to divide value of r,
# g, b by 255 to get its value between 0 to 1
# Make sure r, g, b must be float
STRUCTURE_COLOR = (0.3, 0.9, 0.91)

# stroke-width of rebars in drawing svg
REBARS_STROKE_WIDTH = 0.35

# Color style of rebars
# - set it to "Automatic" to automatically select rebars color
# - set it to "Custom" to choose shape color value from variable REBARS_COLOR
REBARS_COLOR_STYLE = "Automatic"

# Fill color for rebars in drawing svg
# Format: (r, g, b)
# r, g, b value should be between 0 to 1, so you may need to divide value of r,
# g, b by 255 to get its value between 0 to 1
# Make sure r, g, b must be float
REBARS_COLOR = (0.67, 0.0, 0.0)

# Default Template File for Drawing
TEMPLATE_FILE = str(
    Path(__file__).parent.absolute() / "Templates" / "A4_Landscape_blank.svg"
)

# Left offset of drawing on Template File
DRAWING_LEFT_OFFSET = 20

# Top offset of drawing on Template File
DRAWING_TOP_OFFSET = 20

# ------------------------Constraints on size of drawing svg------------------
# Minimum right offset of drawing on Template File
DRAWING_MIN_RIGHT_OFFSET = 20

# Minimum bottom offset of drawing on Template File
DRAWING_MIN_BOTTOM_OFFSET = 20

# Maximum width of drawing on Template File
DRAWING_MAX_WIDTH = 297

# Maximum height of drawing on Template File
DRAWING_MAX_HEIGHT = 210

# -------------------------Reinforcement Dimensioning-------------------------
# The format of dimension label
# %M -> Rebar.Mark
# %C -> Rebar.Amount
# %D -> Rebar.Diameter
# %S -> Rebar span length
DIMENSION_LABEL_FORMAT = "%M %C⌀%D,span=%S"

# Font family of dimension label
DIMENSION_FONT_FAMILY = "DejaVu Sans"

# Font size of dimension label
DIMENSION_FONT_SIZE = 3

# The stroke-width of dimension line
DIMENSION_STROKE_WIDTH = 0.25

# The stroke style of dimension line
# Supported line styles: "Continuous", "Dash", "Dot", "DashDot" or "DashDotDot"
DIMENSION_LINE_STYLE = "Continuous"

# The color of dimension line
# Format: (r, g, b)
# r, g, b value should be between 0 to 1, so you may need to divide value of r,
# g, b by 255 to get its value between 0 to 1
# Make sure r, g, b must be float
DIMENSION_LINE_COLOR = (0.0, 0.0, 0.50)

# The color of dimension text
DIMENSION_TEXT_COLOR = (0.0, 0.33, 0.0)

# The dimension line start symbol, in case of single rebar is visible
# Supported values: "FilledArrow", "Tick", "Dot" or "None"
DIMENSION_SINGLE_REBAR_LINE_START_SYMBOL = "None"

# The dimension line end symbol, in case of single rebar is visible
# Supported values: "FilledArrow", "Tick", "Dot" or "None"
DIMENSION_SINGLE_REBAR_LINE_END_SYMBOL = "FilledArrow"

# The dimension line start symbol, in case of multiple rebars are visible
# Supported values: "FilledArrow", "Tick", "Dot" or "None"
DIMENSION_MULTI_REBAR_LINE_START_SYMBOL = "FilledArrow"

# The dimension line end symbol, in case of multiple rebars are visible
# Supported values: "FilledArrow", "Tick", "Dot" or "None"
DIMENSION_MULTI_REBAR_LINE_END_SYMBOL = "FilledArrow"

# The dimension line mid points symbol
# Supported values: "Tick", "Dot" or "None"
DIMENSION_LINE_MID_POINT_SYMBOL = "Dot"

# The left/right/top/bottom offset of dimension from drawing
DIMENSION_LEFT_OFFSET = 10
DIMENSION_RIGHT_OFFSET = 10
DIMENSION_TOP_OFFSET = 10
DIMENSION_BOTTOM_OFFSET = 10

# The increment in left/right/top/bottom offset to move each new dimension label
# away from drawing
DIMENSION_LEFT_OFFSET_INCREMENT = 6
DIMENSION_RIGHT_OFFSET_INCREMENT = 6
DIMENSION_TOP_OFFSET_INCREMENT = 6
DIMENSION_BOTTOM_OFFSET_INCREMENT = 6

# Set it to True if dimension lines to be outside of reinforcement drawing
# for automated reinforcement dimensioning, in case of single rebar is
# visible, set False otherwise
DIMENSION_SINGLE_REBAR_OUTER_DIM = False

# Set it to True if dimension lines to be outside of reinforcement drawing
# for automated reinforcement dimensioning, in case of multiple rebars are
# visible, set False otherwise
DIMENSION_MULTI_REBAR_OUTER_DIM = True

# The dimension label position type, in case of single rebar is visible
# Supported values: "StartOfLine", "MidOfLine" or "EndOfLine"
DIMENSION_SINGLE_REBAR_TEXT_POSITION_TYPE = "StartOfLine"

# The dimension label position type, in case of multiple rebars are visible
# Supported values: "StartOfLine", "MidOfLine" or "EndOfLine"
DIMENSION_MULTI_REBAR_TEXT_POSITION_TYPE = "MidOfLine"


import glob
import logging
import os
from pathlib import Path
from tempfile import NamedTemporaryFile

import pandas as pd
import pendulum
from airflow.hooks.base import BaseHook
from minio import Minio

DATA_DIR = "/data"
USE_COLS = ["city_ascii", "lat", "lng", "country", "iso2", "iso3"]

BATCH_SIZE = 5000
BATCH_SIZE = 5

def map_response(json_response: dict, return_data: dict):
    return {
        **return_data,
        "weather_descr": json_response["weather"][0]["description"],
        "weather": json_response["weather"][0]["main"],
        "temp": json_response["main"]["temp"],
        "feels_like": json_response["main"]["feels_like"],
        "wind_speed": json_response["wind"]["speed"],
        "lat": json_response["coord"]["lat"],
        "lng": json_response["coord"]["lon"],
        "timestamp": pendulum.now().to_iso8601_string(),
    }

def read_cities():
    filename = f"{DATA_DIR}/worldcities.csv"

    # keep_default_na because of iso2 of Namibia = NA
    world_cities = pd.read_csv(filename, usecols=USE_COLS, keep_default_na=False)
    # keep the number of requests small
    world_cities = world_cities.sample(25)

    data = world_cities.to_dict(orient="records")

    result = [
        {"batch": data[start : start + BATCH_SIZE]}
        for start in range(0, len(data), BATCH_SIZE)
    ]

    return result

def store_to_temp_file(cities_data, tmp_storage_location, **context):
    task_instance = context["ti"]
    map_index = task_instance.map_index
    logical_date = context["logical_date"]

    Path.mkdir(
        Path(f"{tmp_storage_location}/{logical_date}/"), parents=True, exist_ok=True
    )

    df = pd.DataFrame.from_records(cities_data)
    file_path = f"{tmp_storage_location}/{logical_date}/{map_index}-tmp.json"
    df.to_json(file_path)

def collect_and_upload_s3(s3_conn_id, tmp_storage_location, **context):
    logical_date = context["logical_date"]
    dag = context["dag"]

    all_files = glob.glob(f"{tmp_storage_location}/{logical_date}/*-tmp.json")

    file_dfs = []

    for file in all_files:
        file_df = pd.read_json(file)
        file_dfs.append(file_df)

    df = pd.concat(file_dfs, ignore_index=True)

    conn = BaseHook.get_connection(conn_id=s3_conn_id)

    minio_client = Minio(
        conn.extra_dejson["host"].split("://")[1],
        access_key=conn.login,
        secret_key=conn.password,
        secure=False,
    )

    with NamedTemporaryFile("w+b") as file:
        df.to_csv(file.name, index=False)

        bucket = "weatherdata"
        key = f"{dag.dag_id}/{logical_date}.csv"
        logging.info("Storing object: %s/%s.", bucket, key)

        minio_client.remove_object(bucket_name=bucket, object_name=key)

        result = minio_client.fput_object(
            bucket_name=bucket, object_name=key, file_path=file.name
        )

        logging.info("Result of uploading file:")
        logging.info(result)

def cleanup_files(tmp_storage_location, **context):
    logical_date = context["logical_date"]
    tmp_storage = Path(f"{tmp_storage_location}/{logical_date}/")

    all_files = glob.glob(f"{tmp_storage_location}/{logical_date}/*-tmp.json")

    for file in all_files:
        os.remove(file)

    tmp_storage.rmdir()