from ortools.sat.python import cp_model

class DiGraphSolver:

    def __init__(self, desc):
        self.model  = cp_model.CpModel()
        self.status = cp_model.UNKNOWN
        self.timing = None

        # AddCircuit needs a numeric index for each node.
        # Here's two lazy key->index / index->key lookups.
        self.keys = {k: i for i, k in enumerate(desc.nodes.keys()) }
        self.revs = {i: k for k, i in self.keys.items() }

        # Determine the objective of the problem
        self.min_maxing = desc.steps in ['min', 'max']
        self.find_max   = desc.steps == 'max'
        self.step_count = desc.steps if not self.min_maxing else None

        # Determine the start and stop nodes
        self.start = self.keys[desc.start]
        self.stop = self.keys[desc.stop]

        # Store the nodes dict in it's indexed form.
        self.nodes = {self.keys[head]: [self.keys[t] for t in tails] for head,tails in desc.nodes.items()}

        self.arcs = []
        self.vars = []
        self.result = []

    def setup(self):
        # AddCircuit uses a list of directed arcs as tuples: each is (head, tail, boolVariable)
        # where the  boolVar truth value represents if the arc is used or not used.
        # List comprehension: for each head(node), create an arc for each of it's tails.
        self.arcs = [
            (head,tail, self.model.NewBoolVar(f'{head}:{tail}')) for head, tails in self.nodes.items() for tail in tails
        ]

        # vars is a list of all the arcs defined in the problem.
        self.vars = [arc[2] for arc in self.arcs]

        # Add self loops for all *optional* nodes (because AddCircuit requires a Hamiltonian Circuit)
        # for this example, that's everywhere except for 'start' and 'stop'
        # We just use the keys of self.revs (the index values).
        loops = [(n, n, self.model.NewBoolVar(f'{n}:{n}')) for n in self.revs if n not in [self.start, self.stop]]
        self.arcs += loops

        # connect the stop variable to the start variable as a dummy arc to complete the hamiltonian circuit.
        # Because start and stop are not self-closing (non-optional), we don't need to set truth values.
        loop = (self.stop, self.start, self.model.NewBoolVar(f'loop'))
        self.arcs.append(loop)

        # Now add the circuit as a constraint.
        self.model.AddCircuit(self.arcs)

        # Now set the objective.
        if self.min_maxing:
            if self.find_max:
                 self.model.Maximize(sum(self.vars))  # look for the longest network.
            else:
                self.model.Minimize(sum(self.vars))   # look for the shortest network.
        else:
            self.model.Add(sum(self.vars) == self.step_count)

    def solve(self) -> bool:
        cp_solver = cp_model.CpSolver()
        cp_solver.parameters.max_time_in_seconds = 1
        cp_solver.parameters.num_search_workers = 12
        self.status = cp_solver.Solve(self.model)
        return self.summarise(cp_solver)

    def summarise(self, cp_solver) -> bool:
        if self.status in (cp_model.OPTIMAL, cp_model.FEASIBLE):
            self.store(cp_solver)
            return True
        else:
            if self.status == cp_model.INFEASIBLE:
                print(f"Challenge for {self.step_count} arc{'s ' if self.step_count > 1 else ' '}is infeasible after {cp_solver.WallTime()}s.")
            else:
                print(f"Solver ran out of time.")
            return False

    def store(self, cp_solver):
        self.timing = cp_solver.WallTime()
        used = [arc for arc in self.arcs if cp_solver.Value(arc[2])]
        arc = None, self.start
        while True:
            arc = next((link for link in used if link[0] == arc[1]), None)
            self.result.append(self.revs[arc[0]])
            if arc[1] == self.start:
                break
        self.step_count = len(self.result) - 1

    def show(self):
        print(f"{'-'.join(self.result)}")

class RandomDigraph:
    """
        define a problem.
        51 nodes, labelled 'a' ... 'Z'
        start at 'a', stop at 'Z'
        Each node other than 'Z' has a 4 outgoing arcs (random but not going to 'a')
    """
    def __init__(self):
        from random import sample  #
        names = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
        arcs = 4
        self.steps = 1
        self.start = 'a'
        self.stop = 'Z'
        but_first = set(names) ^ set(self.start)
        self.nodes = {v: sample(but_first - set(v), arcs) for v in names}
        self.nodes[self.stop] = []

    def print_nodes(self):
        for key, value in self.nodes.items():
            print(f'{key}: {value}')

def solve_with_steps(problem, steps, show) -> int:
    problem.steps = steps
    solver = DiGraphSolver(problem)
    solver.setup()
    if solver.solve() and show:
        solver.show()
    return solver.step_count

def solve_az_paths_of_a_random_digraph():
    problem = RandomDigraph()
    problem.print_nodes()
    print()
    min_steps = solve_with_steps(problem, 'min', True)
    max_steps = solve_with_steps(problem, 'max', False)
    for p in range(min_steps+1, max_steps+1):
        solve_with_steps(problem, p, True)

if __name__ == '__main__':
    solve_az_paths_of_a_random_digraph()


import os
from pathlib import Path
from typing import Dict, Tuple

import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import load_diabetes
from sklearn.metrics import mean_squared_error, r2_score

__THIS_PATH = Path(os.path.dirname(os.path.realpath(__file__)))
ARTIFACTS_PATH = __THIS_PATH / ".." / "artifacts"

def generate_data() -> Tuple[np.array, np.array, np.array, np.array]:
    """
    Generates a dataset extracted from the diabetes dataset in sklearn.

    Returns:
        diabetes_X_train: the training dataset
        diabetes_y_train: The output corresponding to the training set
        diabetes_X_test: the test dataset
        diabetes_y_test: The output corresponding to the test set
    """
    # Load the diabetes dataset
    diabetes_X, diabetes_y = load_diabetes(return_X_y=True)

    # Use only one feature
    diabetes_X = diabetes_X[:, np.newaxis, 2]

    # Split the data into training/testing sets
    diabetes_X_train = diabetes_X[:-20]
    diabetes_X_test = diabetes_X[-20:]

    # Split the targets into training/testing sets
    diabetes_y_train = diabetes_y[:-20].reshape(-1, 1)
    diabetes_y_test = diabetes_y[-20:].reshape(-1, 1)

    print(
        f"# Training Samples: {len(diabetes_X_train)}; # Test samples: {len(diabetes_X_test)};"
    )
    return diabetes_X_train, diabetes_y_train, diabetes_X_test, diabetes_y_test

def get_scores(y: np.array, y_predicted: np.array) -> Dict[str, float]:
    r2 = r2_score(y, y_predicted)
    mse = mean_squared_error(y, y_predicted)
    rmse = mean_squared_error(y, y_predicted, squared=False)

    return {"r2": r2, "mse": mse, "rmse": rmse}

def evaluate(model, X: np.array, y: np.array, y_predicted: np.array) -> None:
    """
    Calculates evaluation metrics, print them along with a plot showing the fitted model.
    Returns:
        scores: mse and R2 score of the predicted outputs
    """
    # The coefficients
    print(f"Slope: {model.W}; Intercept: {model.b}")

    scores = get_scores(y, y_predicted)
    # The mean squared error
    mse = scores["mse"]
    print(f"Mean squared error: {mse:.2f}")
    # The coefficient of determination: 1 is perfect prediction
    r2 = scores["r2"]
    print(f"Coefficient of determination: {r2:.2f}")

    # Plot outputs
    plt.scatter(X, y, color="black")
    plt.plot(X, y_predicted, color="blue", linewidth=3)

    plt.xticks(())
    plt.yticks(())

    plt.show()

    if r2 >= 0.4:
        print("****** Success ******")
    else:
        print("****** Failed ******")


import dgl
import torch
import numpy as np
from sklearn.metrics import f1_score
from transformers import TrainingArguments, DataCollatorWithPadding
from transformers import RobertaTokenizer
from transformers.utils import logging
from datasets import load_dataset,load_metric,load_from_disk
from models.diffuser_app import DiffuserForSequenceClassification
from models.diffuser_utils import DiffuserConfig
from graphtrainer import graphTrainer

logging.set_verbosity_debug()
logger = logging.get_logger()

def preprocess_function(examples):
    return tokenizer(examples['text'], padding = 'max_length', truncation=True, max_length = 1024)

def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    predictions = np.argmax(predictions, axis=1)
    acc = metric_acc.compute(predictions=predictions, references=labels)["accuracy"]
    f1_score = metric_f1.compute(predictions=predictions, references=labels)["f1"]
    return {"accuracy": acc, "f1_score": f1_score }

metric_acc = load_metric('./metrics/accuracy')
metric_f1 = load_metric('./metrics/f1')

imdb = load_dataset("imdb")
# tokenizer = RobertaTokenizer.from_pretrained("./roberta-tokenizer", max_length = 1024)
tokenizer = RobertaTokenizer(tokenizer_file = "./roberta-tokenizer/tokenizer.json",
                             vocab_file     = "./roberta-tokenizer/vocab.json",
                             merges_file    = "./roberta-tokenizer/merges.txt",
                             max_length     = 1024)

tokenized_imdb = imdb.map(preprocess_function, batched=True)
tokenized_imdb = tokenized_imdb.map(remove_columns=["text"])
# tokenized_imdb = load_from_disk("/home")

data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

config = DiffuserConfig.from_json_file("./models/config.json")
config.num_labels = 2

model =  DiffuserForSequenceClassification(config = config).cuda()

training_args = TrainingArguments(
    output_dir = "./save_imdb",
    learning_rate = 3e-5,
    per_device_train_batch_size = 2,
    per_device_eval_batch_size = 2,
    num_train_epochs = 20,
    weight_decay = 0.01,
    evaluation_strategy = "steps",
    eval_steps = 2000,
    logging_steps = 500,
    save_steps = 500,
    seed = 42,
    warmup_steps = 200,
    gradient_accumulation_steps = 8,
    prediction_loss_only=True
)

steps_per_train_epoch       = int(len(tokenized_imdb['train'])/(training_args.per_device_train_batch_size*torch.cuda.device_count()*training_args.gradient_accumulation_steps ))
training_args.eval_steps    = int(steps_per_train_epoch)
training_args.logging_steps = int(steps_per_train_epoch/5)
training_args.save_steps    = int(steps_per_train_epoch)

trainer = graphTrainer(
    model = model,
    config = config,
    args = training_args,
    train_dataset = tokenized_imdb["train"],
    eval_dataset = tokenized_imdb["test"],
    tokenizer = tokenizer,
    data_collator = data_collator,
    compute_metrics = compute_metrics
)

trainer.train()


import argparse
from typing import Tuple
import numpy as np
import tensorflow as tf
import scipy.sparse as sp
import os

from util.helper import log
from model.load_gnn import GNNLoader
from util.helper import ensure_dir

class SGL(object):
    def __init__(self, args:argparse.Namespace, data_generator:GNNLoader,
    pretrain_embedding:np.array) -> None:
        super().__init__()
        """ Parse arguments for SGL """
        self._parse_args(args, data_generator, pretrain_embedding)

        """ Create placeholder for training inputs """
        self._build_inputs()

        """ Create variable for training weights """
        with tf.device("/device:GPU:" + args.gpu_id[0]):
            self._build_weights()

        """ Compute code representation via GNN """
        with tf.device("/device:GPU:" + args.gpu_id[0]):
            self._build_inter_model()

        """ Build model/loss for code classification """
        with tf.device("/device:GPU:" + args.gpu_id[1]):
            if self.classification_train:
                self._build_classification_model()
                self._build_classification_loss()

        """ Build model for code clone """
        with tf.device("/device:GPU:" + args.gpu_id[1]):
            if self.clone_train:
                self._build_clone_model()
                self._build_clone_loss()

        """ Build model for code cluster """
        # self._build_cluster_model()

        """ Count the number of model parameters """
        self._statistics_params()

    def setup_sess(self) -> tf.Session:
        tf_config = tf.ConfigProto()
        tf_config.gpu_options.allow_growth = True
        # tf_config.log_device_placement = True
        sess = tf.Session(config=tf_config)
        sess.run(tf.global_variables_initializer())
        return sess

    def _parse_args(self, args:argparse.Namespace, data_generator:GNNLoader,
    pretrain_embedding:np.array) -> None:
        """"""
        # pretrain word2vec embedding
        self.pretrain_embedding = pretrain_embedding

        # learning flags
        self.clone_train = args.clone_test_supervised
        self.classification_train = args.classification_test

        # statistics for dataset
        self.n_entity = data_generator.n_entity
        self.max_entity_stat = data_generator.max_entity_stat
        self.max_entity_func = data_generator.max_entity_func

        # setting for gnn
        self.entity_dim = args.type_dim * 2
        self.weight_size = eval(args.layer_size)
        self.n_layer = len(self.weight_size)
        self.agg_type = args.agg_type
        if self.agg_type in ['none']:
            self.inter_dim = self.entity_dim
        else:
            self.inter_dim = self.entity_dim + sum(self.weight_size) # sum -> gnn aggregation
        # assert(self.entity_dim == self.weight_size[0])

        # setting for loss function (optimization)
        self.regs = eval(args.regs)
        self.opt_type = args.opt_type
        self.lr = args.lr

        # init adjacency matrix for gnn (setup n_fold to avoid 'run out of
        # memory' during model training)
        self.A_in = data_generator.A_in
        self.n_fold = 1

        self.model_type = '%s_%s_%s' % (args.model_type, args.adj_type, self.agg_type)
        self.layer_type = '-'.join([str(l) for l in eval(args.layer_size)])
        self.reg_type = '-'.join([str(r) for r in eval(args.regs)])

        # setting for code classification
        self.classification_num = data_generator.classification_num

    def _build_inputs(self) -> None:
        """"""
        # dropout: message dropout (adopted on GNN aggregation)
        self.mess_dropout =tf.placeholder(tf.float32, shape=[None], name='mess_dropout')

        # input function/label for code classification
        self.f_classification = tf.placeholder(dtype=tf.int64, name='f_classification',
            shape=[None, self.max_entity_func])
        self.y_classification = tf.placeholder(dtype=tf.int64, name='y_classification',
            shape=[None])

        # input a pair of functions for code clone
        # f_e shape: [batch_size, entity_num]
        self.f1_clone = tf.placeholder(dtype=tf.int64, name='f1_clone',
            shape=[None, self.max_entity_func])
        self.f2_clone = tf.placeholder(dtype=tf.int64, name='f2_clone',
            shape=[None, self.max_entity_func])
        self.y_clone = tf.placeholder(dtype=tf.float32, name='y_clone',
            shape=[None, 1])

        # input function for code cluster
        self.f_cluster = tf.placeholder(dtype=tf.int64, name='f_cluster',
            shape=[None, self.max_entity_func])

        log.info("Finish building inputs for SGL")

    def _build_weights(self) -> None:
        """"""
        all_weight = dict()
        initializer = tf.contrib.layers.xavier_initializer(seed=2022)

        # weights for entity embeddings: fine-tune does not alter entity embeddings
        entity_trainable = False

        if self.pretrain_embedding is None:
            all_weight['entity_embedding'] = tf.Variable(
            initial_value=initializer([self.n_entity, self.entity_dim]),
            dtype=tf.float32,
            trainable=entity_trainable,
            name='entity_embedding')
            log.info("Init entity embeddings with Xavier")
        else:
            all_weight['entity_embedding'] = tf.Variable(
            initial_value=self.pretrain_embedding,
            trainable=entity_trainable,
            name='entity_embedding')
            log.info("Init entity embeddings with pre-trained word2vec embeddings")

        # we add a `ghost` entity whose embedding is [0,..,0] to allow using
        # tf.nn.embedding_lookup for obtaining program statement representations
        paddings = tf.constant([[0, 1], [0, 0]])
        all_weight['entity_embedding'] = tf.pad(
            all_weight['entity_embedding'], paddings, "CONSTANT",
            name='entity_embedding_pad')

        weight_size_list = [self.entity_dim] + self.weight_size

        # weights for gnn
        for k in range(self.n_layer):
            if self.agg_type in ['gcn']:
                all_weight['w_gcn_%d' % k] = tf.Variable(
                    initial_value=initializer([weight_size_list[k], weight_size_list[k + 1]]),
                    name='w_gcn_%d' % k)
                all_weight['b_gcn_%d' %k] = tf.Variable(
                    initial_value=initializer([1, weight_size_list[k + 1]]),
                    name='b_gcn_%d' % k)

            elif self.agg_type in ['gnn']:
                all_weight['w_gnn_%d' % k] = tf.Variable(
                    initial_value=initializer([weight_size_list[k] * 2, weight_size_list[k + 1]]),
                    name='w_gnn_%d' % k)
                all_weight['b_gnn_%d' % k] = tf.Variable(
                    initial_value=initializer([1, weight_size_list[k + 1]]),
                    name='b_gnn_%d' % k)

            elif self.agg_type in ['ggnn']:
                all_weight['ggnn_gru'] = tf.contrib.rnn.GRUCell(self.weight_size[0])
                for size in self.weight_size:
                    assert(size == self.weight_size[0])
                break

            elif self.agg_type in ['none', 'lightgcn']:
                pass

            elif self.agg_type in ['kgat']:
                all_weight['w1_kgat_%d' % k] = tf.Variable(
                    initial_value=initializer([weight_size_list[k], weight_size_list[k + 1]]),
                    name='w1_kgat_%d' % k)
                all_weight['b1_kgat_%d' % k] = tf.Variable(
                    initial_value=initializer([1, weight_size_list[k + 1]]),
                    name='b1_kgat_%d' % k)
                all_weight['w2_kgat_%d' % k] = tf.Variable(
                    initial_value=initializer([weight_size_list[k], weight_size_list[k + 1]]),
                    name='w2_kgat_%d' % k)
                all_weight['b2_kgat_%d' % k] = tf.Variable(
                    initial_value=initializer([1, weight_size_list[k + 1]]),
                    name='b2_kgat_%d' % k)

            else:
                log.error('aggregator type for GNN is unknown')
                exit(-1)

        # weights for code classification
        if self.classification_train:
            all_weight['w_classification'] = tf.Variable(
                initial_value=initializer([self.inter_dim, self.classification_num]), name='w_classification')
            all_weight['b_classification'] = tf.Variable(
                initial_value=initializer([1, self.classification_num]), name='b_classification')

        # weights for code clone
        if self.clone_train:
            all_weight['w_clone'] = tf.Variable(
                initial_value=initializer([self.inter_dim, 1]), name='w_clone')
            all_weight['b_clone'] = tf.Variable(
                initial_value=initializer([1, 1]), name='b_clone')

        self.weights = all_weight

        log.info("Finish building weights for SGL")

    def _build_inter_model(self):
        """ Different Convolutional Layer:
        1. gcn: 'Semi-Supervised Classification with Graph Convolutional
           Networks',ICLR'18
        2. lightgcn: 'LightGCN: Simplifying and Powering Graph Convolution
           Network for Recommendation', SIGIR'20
        3. ggnn: 'Gated graph sequence neural networks', ICLR'16. """
        if self.agg_type in ['gnn']:
            self.g_embedding = self._create_gnn_embed()
        elif self.agg_type in ['gcn']:
            self.g_embedding = self._create_gcn_embed()
        elif self.agg_type in ['lightgcn']:
            self.g_embedding = self._create_lightgcn_embed()
        elif self.agg_type in ['ggnn']:
            self.g_embedding = self._create_ggnn_embed()
        elif self.agg_type in ['kgat']:
            self.g_embedding = self._create_kgat_embed()
        elif self.agg_type in ['none']:
            log.warn("No use of GNN")
            self.g_embedding = self.weights['entity_embedding']
        else:
            log.error('aggregator type for GNN is unknown')
            exit(-1)

        log.info('Finish building model for GNN')

    def _split_A_hat(self, A:sp.coo_matrix) -> list:
        """"""
        A_fold_hat = []
        fold_len = self.n_entity // self.n_fold

        A = A.tocsr()

        for i_fold in range(self.n_fold):
            start = i_fold * fold_len
            if i_fold == self.n_fold - 1:
                end = self.n_entity + 1
            else:
                end = (i_fold + 1) * fold_len

            A_fold_hat.append(self._convert_sp_mat_to_sp_tensor(A[start:end]))

        return A_fold_hat

    def _convert_sp_mat_to_sp_tensor(self, X:sp.csr_matrix) -> tf.SparseTensor:
        """"""
        coo = X.tocoo().astype(np.float32)
        indices = np.mat([coo.row, coo.col]).transpose()

        if len(coo.data) > 0:
            return tf.SparseTensor(indices, coo.data, coo.shape)
        else:
            return tf.SparseTensor(indices=np.empty((0,2), dtype=np.int64), values=coo.data, dense_shape=coo.shape)

    def _create_gcn_embed(self) -> list:
        """"""
        # adjacency matrix for information propagation
        A = self.A_in

        # generate a set of adjacency sub-matrix.
        A_fold_hat = self._split_A_hat(A)

        # previous embedding (before update): shape = [n_entity + 1, inter_dim]
        pre_embedding = self.weights['entity_embedding']
        g_embeddings = [pre_embedding]

        for k in range(self.n_layer):
            # propagation
            # neighbor_embedding shape = [n_entity + 1, inter_dim]
            if self.n_fold == 1:
                neighbor_embedding = tf.sparse_tensor_dense_matmul(
                    A_fold_hat[0], pre_embedding, name='gcn_neighbor_%d' % k)
            else:
                temp_embed = []
                for i in range(self.n_fold):
                    temp_embed.append(tf.sparse_tensor_dense_matmul(
                        A_fold_hat[i], pre_embedding))
                neighbor_embedding = tf.concat(temp_embed, axis=0, name='gcn_neighbor_%d' % k)

            # aggregation
            # (W(eh + eNh) + b)
            pre_embedding = neighbor_embedding + pre_embedding
            pre_embedding = tf.nn.leaky_relu(
                tf.matmul(pre_embedding, self.weights['w_gcn_%d' % k]) + self.weights['b_gcn_%d' % k],
                name='gcn_agg_%d' % k)

            # dropout
            pre_embedding = tf.nn.dropout(
                pre_embedding, rate=self.mess_dropout[k], name='gcn_dropout_%d' % k)

            # normalize the distribution of entity embeddings
            norm_embeddings = tf.math.l2_normalize(pre_embedding, axis=1, name='gcn_norm_%d' % k)

            # concatenate information from different layers
            g_embeddings += [norm_embeddings]

        g_embeddings = tf.concat(g_embeddings, 1)

        return g_embeddings

    def _create_lightgcn_embed(self) -> list:
        """"""
        # adjacency matrix for information propagation
        A = self.A_in

        # generate a set of adjacency sub-matrix.
        A_fold_hat = self._split_A_hat(A)

        # previous embedding (before update): shape = [n_entity + 1, inter_dim]
        pre_embedding = self.weights['entity_embedding']
        g_embeddings = [pre_embedding]

        for k in range(self.n_layer):
            # propagation
            # neighbor_embedding shape = [n_entity + 1, inter_dim]
            if self.n_fold == 1:
                neighbor_embedding = tf.sparse_tensor_dense_matmul(
                    A_fold_hat[0], pre_embedding, name='lightgcn_neighbor_%d' % k)
            else:
                temp_embed = []
                for i in range(self.n_fold):
                    temp_embed.append(tf.sparse_tensor_dense_matmul(
                        A_fold_hat[i], pre_embedding))
                neighbor_embedding = tf.concat(temp_embed, axis=0, name='lightgcn_neighbor_%d' % k)

            # aggregation
            # (W(eh + eNh) + b)
            pre_embedding = neighbor_embedding + pre_embedding

            # dropout
            pre_embedding = tf.nn.dropout(
                pre_embedding, rate=self.mess_dropout[k], name='lightgcn_dropout_%d' % k)

            # normalize the distribution of entity embeddings
            norm_embeddings = tf.math.l2_normalize(pre_embedding, axis=1, name='lightgcn_norm_%d' % k)

            # concatenate information from different layers
            g_embeddings += [norm_embeddings]

        g_embeddings = tf.concat(g_embeddings, 1)

        return g_embeddings

    def _create_gnn_embed(self) -> list:
        """"""
        # adjacency matrix for information propagation
        A = self.A_in

        # generate a set of adjacency sub-matrix.
        A_fold_hat = self._split_A_hat(A)

        # previous embedding (before update): shape = [n_entity + 1, inter_dim]
        pre_embedding = self.weights['entity_embedding']
        g_embeddings = [pre_embedding]

        for k in range(self.n_layer):
            # propagation
            # neighbor_embedding shape = [n_entity + 1, inter_dim]
            if self.n_fold == 1:
                neighbor_embedding = tf.sparse_tensor_dense_matmul(
                    A_fold_hat[0], pre_embedding, name='gnn_neighbor_%d' % k)
            else:
                temp_embed = []
                for i in range(self.n_fold):
                    temp_embed.append(tf.sparse_tensor_dense_matmul(
                        A_fold_hat[i], pre_embedding))
                neighbor_embedding = tf.concat(temp_embed, axis=0, name='gnn_neighbor_%d' % k)

            # aggregation: (W(eh || eNh) + b)
            pre_embedding = tf.concat([pre_embedding, neighbor_embedding], 1)
            pre_embedding = tf.nn.leaky_relu(
                tf.matmul(pre_embedding, self.weights['w_gnn_%d' % k]) + self.weights['b_gnn_%d' % k],
                name='gnn_agg_%d' % k)

            # dropout
            pre_embedding = tf.nn.dropout(
                pre_embedding, rate=self.mess_dropout[k], name='gnn_dropout_%d' % k)

            # normalize the distribution of entity embeddings
            norm_embeddings = tf.math.l2_normalize(pre_embedding, axis=1, name='gnn_norm_%d' % k)

            # concatenate information from different layers
            g_embeddings += [norm_embeddings]

        g_embeddings = tf.concat(g_embeddings, 1)
        # g_embeddings = tf.reduce_sum(g_embeddings, axis=0)

        return g_embeddings

    def _create_kgat_embed(self) -> list:
        """"""
        # adjacency matrix for information propagation
        A = self.A_in

        # generate a set of adjacency sub-matrix
        A_fold_hat = self._split_A_hat(A)

        # previous embedding (before update): shape = [n_entity + 1, inter_dim]
        pre_embedding = self.weights['entity_embedding']
        g_embeddings = [pre_embedding]

        for k in range(self.n_layer):
            temp_embed = []
            if self.n_fold == 1:
                neighbor_embedding = tf.sparse_tensor_dense_matmul(
                    A_fold_hat[0], pre_embedding, name='kgat_neighbor_%d' % k)
            else:
                for i in range(self.n_fold):
                    temp_embed.append(tf.sparse_tensor_dense_matmul(
                        A_fold_hat[i], pre_embedding))
                neighbor_embedding = tf.concat(temp_embed, axis=0, name='kgat_neighbor_%d' % k)

            # LeakyReLU (W1(eh + eNh) + b)
            add_embedding = neighbor_embedding + pre_embedding
            sum_embedding = tf.nn.leaky_relu(tf.matmul(add_embedding, self.weights['w1_kgat_%d' % k]) + self.weights['b1_kgat_%d' % k])

            # LeakyReLU (W2(eh ⊙ eNh))
            dot_embedding = tf.multiply(neighbor_embedding, pre_embedding)
            bi_embedding = tf.nn.leaky_relu(tf.matmul(dot_embedding, self.weights['w2_kgat_%d' % k]) + self.weights['b2_kgat_%d' % k])

            pre_embedding = sum_embedding + bi_embedding

            # dropout
            pre_embedding = tf.nn.dropout(pre_embedding, rate=self.mess_dropout[k], name='kgat_dropout_%d' % k)

            # normalize the distribution of entity embeddings
            norm_embeddings = tf.math.l2_normalize(pre_embedding, axis=1, name='kgat_norm_%d' % k)

            g_embeddings += [norm_embeddings]

        g_embeddings = tf.concat(g_embeddings, 1)

        return g_embeddings

    def _create_ggnn_embed(self) -> list:
        """"""
        # adjacency matrix for information propagation
        A = self.A_in

        # generate a set of adjacency sub-matrix.
        A_fold_hat = self._split_A_hat(A)

        # previous embedding (before update): shape = [n_entity + 1, inter_dim]
        pre_embedding = self.weights['entity_embedding']
        g_embeddings = [pre_embedding]

        for k in range(self.n_layer):
            # propagation
            # neighbor_embedding shape = [n_entity + 1, inter_dim]
            if self.n_fold == 1:
                neighbor_embedding = tf.sparse_tensor_dense_matmul(
                    A_fold_hat[0], pre_embedding, name='ggnn_neighbor_%d' % k)
            else:
                temp_embed = []
                for i in range(self.n_fold):
                    temp_embed.append(tf.sparse_tensor_dense_matmul(
                        A_fold_hat[i], pre_embedding))
                neighbor_embedding = tf.concat(temp_embed, axis=0, name='ggnn_neighbor_%d' % k)

            # aggregation
            # GRU(eNh, eh)
            pre_embedding = self.weights['ggnn_gru'](neighbor_embedding, pre_embedding)[1]

            # dropout
            pre_embedding = tf.nn.dropout(
                pre_embedding, rate=self.mess_dropout[k], name='ggnn_dropout_%d' % k)

            # normalize the distribution of entity embeddings
            norm_embeddings = tf.math.l2_normalize(pre_embedding, axis=1, name='ggnn_norm_%d' % k)

            # concatenate information from different layers
            g_embeddings += [norm_embeddings]

        g_embeddings = tf.concat(g_embeddings, 1)

        return g_embeddings

    def _build_classification_model(self):
        f_classification_e = tf.nn.embedding_lookup(self.g_embedding, self.f_classification)
        f_classification_e = tf.nn.l2_normalize(f_classification_e, 1)

        # max pooling entity embeddings in a function
        # f_classification shape: [batch_size, inter_dim]
        f_classification_p = tf.layers.average_pooling1d(
            inputs=f_classification_e,
            pool_size=self.max_entity_func, strides=1,
            name='f_classification_p')
        f_classification_p = tf.reshape(f_classification_p, [-1, self.inter_dim])
        f_classification_p = tf.nn.l2_normalize(f_classification_p, 1)
        f_classification_p = tf.nn.dropout(
                f_classification_p, rate=self.mess_dropout[0])

        # fully connected layer: f_classification_fc shape: [batch_size, fc_dim]
        self.f_classification_fc = tf.matmul(f_classification_p, self.weights['w_classification']) + self.weights['b_classification']

        # code classification prediction
        self.classification_prediction = self.f_classification_fc

    def _build_classification_loss(self):
        """"""
        # code classification applies cross entropy as the loss function
        classification_cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
            labels=self.y_classification,
            logits=self.f_classification_fc,
            name='classification_cross_entropy')
        self.classification_loss = tf.reduce_mean(classification_cross_entropy, name='classification_loss')

         # rep optimization
        if self.opt_type in ['Adam', 'adam']:
            self.classification_opt = tf.train.AdamOptimizer(learning_rate=self.lr).minimize(self.classification_loss)
        elif self.opt_type in ['SGD', 'sgd']:
            self.classification_opt = tf.train.GradientDescentOptimizer(learning_rate=self.lr).minimize(self.classification_loss)
        elif self.opt_type in ['AdaDelta']:
            self.classification_opt = tf.train.AdadeltaOptimizer(learning_rate=self.lr).minimize(self.classification_loss)
        else:
            log.error('Optimization is unknown')
            exit(-1)

        log.info('Finish building loss for code classification')

    def _build_cluster_model(self):
        f_cluster_e = tf.nn.embedding_lookup(self.weights['entity_embedding'], self.f_cluster)
        f_cluster_e = tf.nn.l2_normalize(f_cluster_e, 1)

        # max pooling entity embeddings in a function
        # f_cluster shape: [batch_size, entity_dim]
        f_cluster_p = tf.layers.average_pooling1d(
            inputs=f_cluster_e,
            pool_size=self.max_entity_func, strides=1,
            name='f_cluster_p')
        f_cluster_p =tf.reshape(f_cluster_p, [-1, self.entity_dim])

        # This norm may not be necessary
        norm_f_cluster_e = tf.nn.l2_normalize(f_cluster_p, 1)

        self.f_cluster_rep = norm_f_cluster_e

    def _build_clone_model(self):
        """"""
        f1_clone_e = tf.nn.embedding_lookup(self.g_embedding, self.f1_clone)
        f2_clone_e = tf.nn.embedding_lookup(self.g_embedding, self.f2_clone)
        f1_clone_e = tf.nn.l2_normalize(f1_clone_e, 1)
        f2_clone_e = tf.nn.l2_normalize(f2_clone_e, 1)

        # max pooling entity embeddings in a function
        # f1_clone shape: [batch_size, inter_dim]
        f1_clone_p = tf.layers.average_pooling1d(
            inputs=f1_clone_e,
            pool_size=self.max_entity_func, strides=1,
            name='f1_clone_p')
        f1_clone_p = tf.reshape(f1_clone_p, [-1, self.inter_dim])

        f2_clone_p = tf.layers.average_pooling1d(
            inputs=f2_clone_e,
            pool_size=self.max_entity_func, strides=1,
            name='f2_clone_p')
        f2_clone_p = tf.reshape(f2_clone_p, [-1, self.inter_dim])

        """ unsupervised training """
        norm_f1_clone_e = tf.nn.l2_normalize(f1_clone_p, 1)
        norm_f2_clone_e = tf.nn.l2_normalize(f2_clone_p, 1)
        self.clone_prediction_unsupervised = tf.reduce_sum(tf.multiply(norm_f1_clone_e, norm_f2_clone_e), axis=1)

        """ supervised training """
        clone_distance = tf.abs(norm_f1_clone_e - norm_f2_clone_e)
        self.clone_sim = tf.matmul(clone_distance, self.weights['w_clone']) + self.weights['b_clone']
        self.clone_prediction_supervised = self.clone_sim

    def _build_clone_loss(self):
        """"""
        clone_sim = tf.nn.sigmoid_cross_entropy_with_logits(
            labels=self.y_clone,
            logits=self.clone_sim,
            name='clone_cross_entropy')

        self.clone_loss = tf.reduce_mean(clone_sim, name='clone_loss')

        # rep optimization
        if self.opt_type in ['Adam', 'adam']:
            self.clone_opt = tf.train.AdamOptimizer(learning_rate=self.lr).minimize(self.clone_loss)
        elif self.opt_type in ['SGD', 'sgd']:
            self.clone_opt = tf.train.GradientDescentOptimizer(learning_rate=self.lr).minimize(self.clone_loss)
        elif self.opt_type in ['AdaDelta']:
            self.clone_opt = tf.train.AdadeltaOptimizer(learning_rate=self.lr).minimize(self.clone_loss)
        else:
            log.error('Optimization is unknown')
            exit(-1)

        log.info('Finish building loss for code clone')

    def _statistics_params(self) -> None:
        """"""
        total_parameters = 0
        for var in self.weights:
            if var.find('ggnn') == -1:
                shape = self.weights[var].get_shape()
                var_para = 1
                for dim in shape:
                    var_para *= dim.value
                log.debug('Variable name: %s Shape: %d' % (var, var_para))
                total_parameters += var_para
            else:
                log.debug('Variable name: %s Shape: N/A' % (var))
        log.debug('%s has %d parameters' % (self.model_type, total_parameters))

    def train_classification(self, sess:tf.Session, feed_dict:dict) -> Tuple[float, float]:
        run_options = tf.RunOptions(report_tensor_allocations_upon_oom=True)
        return sess.run([self.classification_opt, self.classification_loss], feed_dict, options=run_options)

    def train_clone(self, sess:tf.Session, feed_dict:dict) -> Tuple[float, float]:
        run_options = tf.RunOptions(report_tensor_allocations_upon_oom=True)
        return sess.run([self.clone_opt, self.clone_loss], feed_dict, options=run_options)

    def eval_classification(self, sess:tf.Session, feed_dict:dict) -> list:
        """"""
        return sess.run(self.classification_prediction, feed_dict)

    def eval_clone_unsupervised(self, sess:tf.Session, feed_dict:dict) -> list:
        """"""
        return sess.run(self.clone_prediction_unsupervised, feed_dict)

    def eval_clone_supervised(self, sess:tf.Session, feed_dict:dict) -> list:
        """"""
        return sess.run(self.clone_prediction_supervised, feed_dict)

    def eval_cluster(self, sess:tf.Session, feed_dict:dict) -> list:
        """"""
        return sess.run(self.f_cluster_rep, feed_dict)

    def get_variables_to_restore(self) -> list:
        variables_to_restore = []
        variables = tf.global_variables()
        for v in variables:
            if v.name.split(':')[0] != 'entity_embedding':
                variables_to_restore.append(v)
        return variables_to_restore

    def store_model(self, sess:tf.Session, pretrain_path:str, epoch:int) -> None:
        """"""
        model_save_dir = '%s/%s/%s/%s/%s/' % \
            (pretrain_path, self.model_type, self.lr, self.layer_type, self.reg_type)
        model_save_path = model_save_dir + 'model.weights'
        ensure_dir(model_save_path)

        variables_to_restore = self.get_variables_to_restore()

        model_saver = tf.train.Saver(variables_to_restore, max_to_keep=1)
        model_saver.save(sess, model_save_path, global_step=epoch)

        log.info("Model save in %s" % model_save_path)

    def load_model(self, sess:tf.Session, pretrain_path:str) -> None:
        checkpoint_path = '%s/%s/%s/%s/%s/checkpoint' % \
            (pretrain_path, self.model_type, self.lr, self.layer_type, self.reg_type)

        ckpt = tf.train.get_checkpoint_state(os.path.dirname(checkpoint_path))
        if ckpt and ckpt.all_model_checkpoint_paths:
            log.info("Load model from %s" % os.path.dirname(checkpoint_path))
            variables_to_restore = self.get_variables_to_restore()
            model_saver = tf.train.Saver(variables_to_restore)
            model_saver.restore(sess, ckpt.all_model_checkpoint_paths[0])
        else:
            log.error("fail to load model in %s" % checkpoint_path)
            exit(-1)


from app.config.mysqlconnection import connectToMySQL
from flask import flash
from app import  DATABASE
from app.models.user import User

class Recipe:
    def __init__(self, data):
        self.id = data["id"]
        self.name = data["name"]
        self.description = data["description"]
        self.instructions = data["instructions"]
        self.date_made = data["date_made"]
        self.under_thirty = data["under_thirty"]
        self.user_id = data["user_id"]
        self.created_at = data["created_at"]
        self.updated_at = data["updated_at"]

    @classmethod
    def create(cls, data):
        query = "INSERT INTO recipes (name, description, instructions, date_made, under_thirty, user_id) VALUES (%(name)s,%(description)s,%(instructions)s,%(date_made)s,%(under_thirty)s,%(user_id)s);"

        return connectToMySQL(DATABASE).query_db(query, data)   #returns an id

    @classmethod
    def get_all_recipes_with_users(cls):
        query = "SELECT * FROM recipes JOIN users on recipes.user_id = users.id"

        results = connectToMySQL(DATABASE).query_db(query) # list of dicts with all data from both tables

        #now we want to
        list_recipes = []
        for row in results:
            current_recipe = cls(row)
            user_data = {
                **row,
                "created_at": row["users.created_at"],
                "updated_at": row["users.updated_at"],
                "id": row["users.id"]
            }

            current_user = User( user_data)         #user in that recipe
            current_recipe.user = current_user      #add instance attribute to this recipe of that user
            list_recipes.append( current_recipe )   #append this instance into a list (of all other recipes with users attributes added)

        return list_recipes

    @classmethod
    def get_one_with_user(cls, data):
        query = "SELECT * FROM recipes JOIN users on recipes.user_id = users.id WHERE recipes.id = %(id)s"
        results = connectToMySQL(DATABASE).query_db(query, data) # list of one result of the right id

        if len(results) > 0:
            current_recipe = cls( results[0])
            user_data = {
                **results[0],
                "created_at": results[0]["users.created_at"],
                "updated_at": results[0]["users.updated_at"],
                "id": results[0]["users.id"]
            }
            current_recipe.user = User(user_data)
            return current_recipe
        else:
            return None

    # @app.route("/recipes/<int:id>/update")
    # def display_updated_recipe(id):

    @staticmethod
    def validate_recipe(data):
        is_valid = True

        if data["name"] == "":
            flash("Name must not be empty", "error_recipe_name")
            is_valid = False
        if data["description"] == "":
            flash("description must not be empty", "error_recipe_description")
            is_valid = False
        if data["instructions"] == "":
            flash("instructions must not be empty", "error_recipe_instructions")
            is_valid = False
        if data["date_made"] == "":
            flash("date made must not be empty", "error_recipe_date_made")
            is_valid = False
        if len(data["name"]) < 3:
            flash("name must be at least 3 chars long", "error_recipe_name")
            is_valid = False
        if len(data["description"]) < 3:
            flash("description must be at least 3 chars long", "error_recipe_description")
            is_valid = False
        if len(data["instructions"]) < 3:
            flash("instructions must be at least 3 chars long", "error_recipe_instructions")
            is_valid = False

        return is_valid


import json
import numpy as np

PREDICTION_THRESHOLD = 0.5

def gaussian(x, mu=10, sigma=1.5):
    return np.exp(-((x - mu) ** 2) / (2 * sigma ** 2))

def softmax(x):
    """
    :param x: array of predictions
    :return: array of predictions after softmax activation
    """
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum(axis=1, keepdims=True)

def relu(x):
    """
    :param x: array of predictions
    :return: array of predictions after relu activation
    """
    return np.maximum(0, x)

def sign(x):
    """
    :param x: array of predictions
    :return: array of predictions after sign activation
    """
    return np.sign(x)

def sigmoid(x):
    """
    :param x: array of predictions
    :return: array of predictions after sigmoid activation
    """
    return 1 / (1 + np.exp(-x))

def leaky_relu(x):
    """
    :param x: array of predictions
    :return: array of predictions after leaky relu activation
    """
    return np.maximum(0.1 * x, x)

class NeuralNetwork:
    def __init__(self, model, weights, biases):
        self.weights = []
        self.biases = []  # New line
        self.activations = []
        for i in range(len(model)):
            self.weights.append(np.array(weights[i]).reshape(model[i][0], model[i][1]))
            self.biases.append(biases[i])  # Update this line
            if model[i][2] == 'relu':
                self.activations.append(relu)
            elif model[i][2] == 'sigmoid':
                self.activations.append(sigmoid)
            elif model[i][2] == 'sign':
                self.activations.append(sign)
            elif model[i][2] == 'leaky_relu':
                self.activations.append(leaky_relu)
            elif model[i][2] == 'softmax':
                self.activations.append(softmax)
            elif model[i][2] == 'gaussian':
                self.activations.append(gaussian)
            else:
                raise Exception(f"Unknown activation function {model[i][2]}")

    def propagate(self, data):
        input_data = data
        z = None
        for i, weights in enumerate(self.weights):
            z = np.dot(input_data, weights) + self.biases[i]  # Updated line
            if self.activations[i] != 0:
                z = self.activations[i](z)
            input_data = z
        return np.ravel(z)

def load_network(filename):
    with open(filename, 'r') as file:
        data = json.load(file)

    weights = np.array(data['weights'][0])
    biases = data['biases'][0]
    activation_name = data['activations'][0]
    model = [[weights.shape[0], weights.shape[1], activation_name]]

    return model, [weights], [biases]

def load_test(test_file):
    with open(test_file, 'r') as file:
        lines = file.readlines()
        data = [list(map(int, line.strip())) for line in lines]
    return data

def runnet(network, output_file, X_test):
    # Run the test data through the network
    predictions = []
    for x in X_test:
        z = network.propagate(x)
        # predict using the threshold
        predictions.append(1 if z > PREDICTION_THRESHOLD else 0)

    # Write the predictions to the output file
    with open(output_file, "w") as file:
        for prediction in predictions:
            file.write(f"{prediction}\n")

# Main function
def main():
    saved_data_file = "wnet0.txt"
    testnet0 = "testnet0.txt"
    output_file = "predictions0.txt"

    # initialize the model, weights, activations and biases
    model, weights, biases = load_network(saved_data_file)

    # create the network
    network = NeuralNetwork(model, weights, biases)
    X_test = load_test(testnet0)
    # Run the test data through the network and save predictions to output file
    runnet(network, output_file, X_test)
    print("Predictions saved to predictions0.txt")

if __name__ == '__main__':
    main()


#%%
import torch
from torch.utils.data.dataset import Dataset
import torchvision
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import torch.nn as nn
from sklearn.model_selection import train_test_split
# %%
#* Importing dataset
dataset_train = pd.read_csv('../data/train.csv')
dataset_test = pd.read_csv('../data/test.csv')
print('Training data length:', len(dataset_train))
# %%
#* Having a look at the dataset
targets_np = dataset_train.label.values
features_np = dataset_train.loc[:,dataset_train.columns!='label'].values # values range to 255, so to normalize
X_train, X_test, y_train, y_test = train_test_split(features_np,targets_np,test_size=0.2, random_state=42)
#features_train = torch.from_numpy(X_train)
#target_train = torch.from_numpy(y_train)
#features_test = torch.from_numpy(X_test)
#target_test = torch.from_numpy(y_test)
print('Training Features Shape',X_train.shape)
print('Training Labels Shape',y_train.shape)
print('Testing Features Shape',X_test.shape)
print('Testing Labels Shape',y_test.shape)
#Viz of an image
plt.imshow(features_np[0].reshape(28,28),cmap='gray') # images are 28x28
plt.title('Label: '+str(targets_np[0]))
plt.show()
#%%
i=100
plt.imshow(X_test[i].reshape(28,28),cmap='gray') # images are 28x28
plt.title('Label: '+str(y_test[i]))
plt.show()
# %%
from torch.utils.data.sampler import SubsetRandomSampler
from torch.utils.data import DataLoader, Dataset
import torchvision.transforms as tt

def get_default_device():
    """Returns the default Device (cuda if gpu, else cpu"""
    if torch.cuda.is_available(): return torch.device('cuda')
    else: torch.device('cpu')

def to_device(data,device):
    """Shifts Data to the input device"""
    if isinstance(data,(list,tuple)): return [to_device(x,device) for x in data]
    return data.to(device, non_blocking=True)

class DeviceDataLoader():

    def __init__(self, dl, device) -> None:
        self.dl = dl
        self.device = device

    def __iter__(self):
        for b in self.dl:
            yield to_device(b,self.device)

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

#* Creating a custom dataset overriding torchvision dataset
class MyDataset(Dataset):
    def __init__(self, df_features, df_labels, transform=None) -> None:
        self.features = df_features
        self.labels = df_labels
        self.transform = transform

    def __getitem__(self, index):
        data = self.features[index]
        image = data.reshape(28,28).astype(np.uint8)
        label = self.labels[index]
        if self.transform:
            image = self.transform(image)
        return (image,label)

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

BATCH_SIZE = 16

#* Augmentation - Random affination (rotation with translation), normalization
train_tmfs = tt.Compose([
    tt.ToPILImage(),
    #tt.RandomRotation(30),
    tt.RandomAffine(degrees=30, translate = (0.1,0.1), scale = (0.9,1.1)),
    # Random Affine suits better for this purpose @https://pytorch.org/vision/stable/auto_examples/plot_transforms.html#randomaffine
    tt.ToTensor(),
    tt.Normalize(mean=[features_np.mean()/255], std=[features_np.std()/255],inplace=True)
])
test_tmfs = tt.Compose([
    tt.ToPILImage(),
    #tt.RandomRotation(30),
    tt.RandomAffine(degrees=30, translate = (0.1,0.1), scale = (0.9,1.1)),
    # Random Affine suits better for this purpose @https://pytorch.org/vision/stable/auto_examples/plot_transforms.html#randomaffine
    tt.ToTensor(),
    tt.Normalize(mean=[features_np.mean()/255], std=[features_np.std()/255],inplace=True)
])

#* Setting default Device, GPU
device = get_default_device()
print(device)

#* Creating Dataset
train_set = MyDataset(X_train, y_train, transform=train_tmfs)
test_set = MyDataset(X_test, y_test, transform=test_tmfs)

#* Loading data and then shifting data loader to the selected device
train_dl = DeviceDataLoader(DataLoader(train_set, batch_size=BATCH_SIZE, shuffle=True),device)
test_dl = DeviceDataLoader(DataLoader(test_set, batch_size=BATCH_SIZE, shuffle=True),device)
# %%
#* Sanity Check
imgs, lbls = next(iter(test_dl))
print(imgs.shape)
plt.imshow(torch.Tensor.cpu(imgs[0].data.reshape(28,28)),cmap='gray')
print('Label:',lbls[0])
plt.show()
# %%
import torch.nn.functional as F
#* Following Residual Network https://raw.githubusercontent.com/lambdal/cifar10-fast/master/net.svg
NUM_CLASSES = 10

def convolution_block(in_channels, out_channels, pool=False):
    """Returns a sequence of Convolution, Batch Normalization, Rectified Linear Activation (Relu) and Max Pooling if selected"""
    layers = [
        nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=3, padding=1),
        nn.BatchNorm2d(out_channels),
        nn.ReLU(inplace=True)
    ]
    if pool: layers.append(nn.MaxPool2d(2,2)) # 2x2 Max Pooling
    return nn.Sequential(*layers)

class Model(nn.Module):

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

        self.conv1 = convolution_block(1,32)

        self.conv2 = convolution_block(32,64,pool=True)
        self.res1 = nn.Sequential(convolution_block(64,64), convolution_block(64,64))

        self.conv3 = convolution_block(64,128, pool=True)

        self.conv4 = convolution_block(128,256, pool=True)
        self.res2 = nn.Sequential(convolution_block(256,256), convolution_block(256,256))

        self.classifier = nn.Sequential(
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Dropout(0.2), #* added later
            nn.Linear(256, NUM_CLASSES)
        )

    def forward(self, xb):
        out = self.conv1(xb) # Prep
        out = self.conv2(out) # Layer 1 Convolution Block
        out = self.res1(out) + out # Layer 1 Residual Block
        out = self.conv3(out) # Layer 2
        out = self.conv4(out) # Layer 3 Convolution Block
        out = self.res2(out) + out # Layer 3 Residual Block
        out = self.classifier(out)
        return F.log_softmax(out, dim=1)
# %%
model = Model()
to_device(model,device)
# %%
# Sanity Check
for images, labels in train_dl:
    print(images.shape)
    out = model(images)
    print(out.shape)
    break
#%%
'''
Cant find fastai modules
from fastai.basic_data import DataBunch
from fastai.train import Learner
from fastai.metrics import accuracy
'''
#%%
import time
@torch.no_grad()
def loss_batch(model, loss_func, xb,yb, opt=None, metric=None):
    preds = model(xb)
    loss = loss_func(preds,yb)

    if opt is not None:
        loss.backward()
        opt.step()
        opt.zero_grad()

    metric_result = None
    if metric is not None:
        metric_result = metric(preds, yb)

    return loss.item(), len(xb), metric_result

def accuracy(outputs, labels):
    _, preds = torch.max(outputs, dim=1)
    return torch.tensor(torch.sum(preds == labels).item() / len(preds))

def get_lr(optimizer):
    for param_group in optimizer.param_groups:
        return param_group['lr']

def fit_one_cycle(epochs, max_lr, model, train_dl, test_dl,
                    weight_decay = 0, grad_clip = None, opt_func = torch.optim.SGD):

    def evaluate(model, test_dl):
        model.eval()
        outputs = [validation_step(images.to(device),labels.to(device)) for images, labels in test_dl]
        return validation_epoch_end(outputs)

    def training_step(images, labels):
        out = model(images)
        loss = F.cross_entropy(out,labels)
        return loss

    def validation_step(images, labels):
        out = model(images)
        loss = F.cross_entropy(out,labels)
        acc = accuracy(out, labels)
        return {'val_loss': loss.detach(), 'val_acc':acc}

    def validation_epoch_end(outputs):
        batch_losses = [x['val_loss'] for x in outputs]
        epoch_losses = torch.stack(batch_losses).mean()
        batch_accs = [x['val_acc'] for x in outputs]
        epoch_acc = torch.stack(batch_accs).mean()
        return {'val_loss': epoch_losses.item(), 'val_acc':epoch_acc.item()}

    def epoch_end(epoch, result, start_time):
        print("Epoch [{}], last_lr:{:.5f}, train_loss:{:.4f}, val_loss:{:.4f}, val_acc: {:.4f}, epoch_time:{:.4f}".format(
            epoch, result['lrs'][-1], result['train_loss'], result['val_loss'], result['val_acc'], time.time()-start_time
        ))

    torch.cuda.empty_cache()
    results = []
    optimizer = opt_func(model.parameters(), max_lr, weight_decay=weight_decay)
    scheduler = torch.optim.lr_scheduler.OneCycleLR(optimizer, max_lr, epochs=epochs,
                                            steps_per_epoch=len(train_dl))

    for epoch in range(epochs):
        epoch_start_time = time.time()
        model.train()
        train_losses = []
        lrs = [] # Learning rates at each point
        #* Training Part
        for batch in train_dl:
            images, labels = batch
            images = images.to(device)
            labels = labels.to(device)

            loss = training_step(images, labels) # Generate Predictions and Calculate the loss
            train_losses.append(loss)
            loss.backward()

            if grad_clip:
                nn.utils.clip_grad_value_(model.parameters(), grad_clip)

            optimizer.step() # Compute Gradients
            optimizer.zero_grad() # Reset to zero

            lrs.append(get_lr(optimizer))
            scheduler.step()

        #* Validation part
        result = evaluate(model, train_dl)
        result['train_loss'] = torch.stack(train_losses).mean().item()
        result['lrs'] = lrs
        epoch_end(epoch, result, epoch_start_time)
        results.append(result)

    return results
# %%
EPOCHS = 10
MAX_LEARNING_RATE = 0.001
GRAD_CLIP = 0.1
WEIGHT_DECAY = 1e-4
OPT_FUNC = torch.optim.Adam
# %%
#%%time
results = []
results += fit_one_cycle(EPOCHS, MAX_LEARNING_RATE, model, train_dl, test_dl, WEIGHT_DECAY, GRAD_CLIP, OPT_FUNC)
# %%
#* Plotting training history
time_taken = '8:19'
fig, axs = plt.subplots(nrows=1,ncols=3,figsize=(30,5))
accuracies = [x['val_acc'] for x in results]
axs[0].plot(accuracies, '-x')
axs[0].title.set_text('Accuracies x Number of Epochs')
axs[0].set_ylabel('Accuracies')
axs[0].set_xlabel('Epoch')
axs[1].plot([x['val_loss'] for x in results], '-x')
axs[1].title.set_text('Loss x Number of Epochs')
axs[1].set_ylabel('Loss')
axs[1].set_xlabel('Epoch')
axs[1].plot([x['train_loss'] for x in results], '-rx')
axs[1].title.set_text('Training Loss x Number of Epochs')
lrs = np.concatenate([x.get('lrs',[]) for x in results])
axs[2].plot(lrs)
axs[2].title.set_text('Learning Rate x Batch Number')
axs[2].set_ylabel('Learning Rate')
axs[2].set_ylabel('Batch Number')
plt.suptitle('Training History')
plt.grid()
plt.show()
#%%
# Normalizing testing images
def normalize_test(X_test):
    X_test = X_test.reshape([-1,28,28]).astype(float)/255
    X_test = (X_test-features_np.mean())/features_np.std()
    return X_test
X_test = normalize_test(X_test)
# expanding dimension of test
X_test = np.expand_dims(X_test,axis=1)
X_test = torch.from_numpy(X_test).float().to(device)
# %%
# Testing some images, sanity checkk
for img, lbl in test_dl:
    ttt=img,lbl
    for i in range(BATCH_SIZE):
        plt.imshow(torch.Tensor.cpu(ttt[0][i].data.reshape(28,28)),cmap='gray')
        plt.show()
        print('Prediction',torch.argmax(model(ttt[0][i].unsqueeze(0)),1).item())
    break
# Seems to working :)
# %%
#* Prepping submission file
submission_np = dataset_test.values
class SubmissionDataset(Dataset):
    def __init__(self, df, transform=None) -> None:
        self.features = df
        self.transform = transform

    def __getitem__(self, index):
        data = self.features[index]
        image = data.reshape(28,28).astype(np.uint8)
        if self.transform:
            image = self.transform(image)
        return image

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

submission_tmfs = tt.Compose([
    tt.ToTensor(),
    tt.Normalize(mean=[features_np.mean()/255], std=[features_np.std()/255],inplace=True)
])

submission_set = SubmissionDataset(submission_np,transform=submission_tmfs)
submission_dl = DeviceDataLoader(DataLoader(submission_set, batch_size=1, shuffle=False),device)
#%%
#* Checking image by image in submission set
ctr=0
for img in submission_dl:
    for i in range(1):
        print(img[i].shape)
        plt.imshow(torch.Tensor.cpu(img[i].data.reshape(28,28)),cmap='gray')
        plt.show()
        print('Prediction',int(torch.argmax(model(img[i].unsqueeze(0)),1).item()))
    if ctr>1: break
    ctr+=1
#%%
df_submission = pd.DataFrame(columns=['ImageId','Label'])
prediction = []
for img in submission_dl:
    prediction.append(int(torch.argmax(model(img[0].unsqueeze(0)),1).item()))
df_submission['Label'] = pd.Series(prediction)
#%%
df_submission['ImageId'] = df_submission.Label.index + 1
# %%
#df_submission.to_csv('submission_resnet.csv',index=False)
#%%
#torch.save(model.state_dict(),'resnet_digits_model.pth')


from json import load, dump

def get_stored_username():
    """Get stored username if available."""
    filename = "username.json"
    try:
        with open(filename) as f_obj:
            username = load(f_obj)
    except FileNotFoundError:
        return None
    else:
        return username

def get_new_username():
    """Prompt for a new username."""
    username = input("What is your name? ")
    filename = "username.json"
    with open(filename, "w") as file_object:
        dump(username, file_object)
    return username

def greet_user():
    """Load the username, if it has been stored previously.
    Otherwise, prompt for the username and store it."""
    username = get_stored_username()
    current_user = input(f"Is this your username: {username}, 'yes' or 'no'? ")
    if current_user == "no":
        username = get_new_username()
    else:
        if username:
            print(f"Welcome back, {username}!")
        else:
            username = get_new_username()
            print(f"Next time you login, we will remember you, {username}")

if __name__ == "__main__":
    greet_user()


from datetime import datetime
from abc import ABC
from abc import abstractmethod
from typing import List
import logging
from pyAutomation.DataObjects.PointReadOnlyAbstract import PointReadOnlyAbstract
from pyAutomation.DataObjects.PointAbstract import PointAbstract

class i2cPrototype(ABC):

    def __init__(self, name: str, logger: str) -> None:
        self.device_points = []  # type: List[PointAbstract]
        self._has_write_data = False  # type: bool
        self.last_io_attempt = datetime.now()  # type: datetime.datetime
        self.is_setup = False
        self.name = name
        self._logger = None
        self.logger = logging.getLogger(logger)
        self.logger.debug(
          "created device %s using logger %s",
          self.name,
          logger,
        )
        self.delay_until = None

    @abstractmethod
    def read_data(self):
        pass

    @abstractmethod
    def write_data(self):
        pass

    @abstractmethod
    def setup(self):
        pass

    @abstractmethod
    def config(self):
        pass

    def _get_name(self) -> str:
        return self._name

    def _set_name(self, name: str) -> None:
        self._name = name

    name = property(_get_name, _set_name)

    def _get_logger(self):
        return self._logger

    def _set_logger(self, logger) -> None:
        self._logger = logger

    logger = property(_get_logger, _set_logger)

    @property
    def interrupt_points(self) -> List[PointReadOnlyAbstract]:
        return []

    @property
    def has_write_data(self) -> bool:
        if self.delay_until is not None:
            if self.delay_until < datetime.now():
                self.delay_until = None
            else:
                return False

        if hasattr(self, '_has_write_data'):
            return self._has_write_data
        else:
            return False

    @property
    def next_update(self) -> datetime:
        if self.delay_until is not None:
            if self.delay_until < datetime.now():
                self.delay_until = None
            else:
                return self.delay_until

        next_point = "no point"
        next_update = datetime.max
        if self.device_points is not None:
            for p in self.device_points:
                if p is not None:
                    next_update_time = p.next_update

                    if next_update_time is None:
                        continue
                    else:
                        if next_update_time < next_update:
                            next_update = next_update_time
                            next_point = p.name

        if datetime.max == next_update:
            next_update = None

        self.logger.debug(
          "next update for: %s is %s (%s)",
          self.name,
          next_update,
          next_point,
        )

        return next_update


from django.conf.urls import url

from .views import facebook, add_date, post_date, update_project, post_project, select_supply, post_supply

"""URL routing for views.py"""

urlpatterns = [
    url('^facebook', facebook, name='Facebook'),
    url(r'^date', add_date, name='due_date'),
    url(r'^post_date', post_date, name='post_date'),
    url(r'^project', update_project, name='update_project'),
    url(r'^post_project', post_project, name='post_date'),
    url(r'^pattern', select_supply, name='select_pattern'),
    url(r'^post_supply', post_supply, name='post_supply'),
    url(r'^material', select_supply, name='select_material')
]


import requests
import os
import csv
import json
from pprint import pprint

PATH = os.path.dirname(__file__)
DATA = "SYMBOL_chart.json"
DATAPATH = os.path.join(PATH, DATA)

def api_call(ticker):
    try:
        response = requests.get('https://api.iextrading.com/1.0/stock/{}/chart'.format(ticker))
        payload = response.json()
        return payload
    except ValueError:
        return False

def api_download():
    data = {}
    while True:
        ticker = input("enter a ticker symbol or q to quit ")
        payload = api_call(ticker)
        if ticker.lower() == "q":
            print("done")
            break
        elif not payload:
            print("please try again ")
            continue
        else:
            with open(DATA, "w+") as json_file:
                columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'unadjustedVolume', 'change', 'changePercent', 'vwap', 'label' , 'changeOverTime']
                writer = csv.DictWriter(json_file, fieldnames=columns)
                data[ticker] = payload
                json.dump(data, json_file)

if __name__ == "__main__":
    api_download()
#goog, aapl, tsla


import logging
import warnings

logging.getLogger("transformers").setLevel(logging.ERROR)

from transformers import (
    ElectraTokenizerFast,
    ElectraConfig,
    TFBertMainLayer,
    BertTokenizerFast,
    BertConfig,
    XLMRobertaTokenizer,
    XLMRobertaConfig,
    T5Tokenizer,
    T5Config,
    AlbertTokenizerFast,
    AlbertConfig,
    BertTokenizer,
    LongformerTokenizerFast,
    LongformerConfig,
)
from transformers.models.t5.modeling_tf_t5 import TFT5Model
from transformers.models.electra.modeling_tf_electra import TFElectraMainLayer
from transformers.models.roberta.modeling_tf_roberta import TFRobertaMainLayer
from transformers.models.albert.modeling_tf_albert import TFAlbertMainLayer
from transformers.models.longformer.modeling_tf_longformer import TFLongformerMainLayer

from transformers.models.xlm_roberta.tokenization_xlm_roberta import (
    VOCAB_FILES_NAMES,
    PRETRAINED_VOCAB_FILES_MAP,
    PRETRAINED_POSITIONAL_EMBEDDINGS_SIZES,
)

from finetune.util.huggingface_interface import finetune_model_from_huggingface

HFXLMRoberta = finetune_model_from_huggingface(
    pretrained_weights="jplu/tf-xlm-roberta-base",
    archive_map={
        "jplu/tf-xlm-roberta-base": "https://s3.amazonaws.com/models.huggingface.co/bert/jplu/tf-xlm-roberta-base/tf_model.h5"
    },
    hf_featurizer=TFRobertaMainLayer,
    hf_tokenizer=XLMRobertaTokenizer,
    hf_config=XLMRobertaConfig,
    weights_replacement=[
        (
            "tfxlm_roberta_for_masked_lm/roberta",
            "model/featurizer/tf_roberta_main_layer",
        )
    ],
)

HFLongformer = finetune_model_from_huggingface(
    pretrained_weights="allenai/longformer-base-4096",
    archive_map={
        "allenai/longformer-base-4096": "https://cdn.huggingface.co/allenai/longformer-base-4096/tf_model.h5"
    },
    hf_featurizer=TFLongformerMainLayer,
    hf_tokenizer=LongformerTokenizerFast,
    hf_config=LongformerConfig,
    weights_replacement=[
        (
            "tf_longformer_for_masked_lm/longformer",
            "model/featurizer/tf_longformer_main_layer",
        )
    ],
    config_overrides={
        "low_memory_mode": True,
        "batch_size": 2,
        "n_epochs": 16,
        "lr": 1.68e-3,
        "max_grad_norm": 1.1,
        "lr_warmup": 0.7,  # Not a typo - just weird.
        "l2_reg": 0.194,
    },
)

HFElectraGen = finetune_model_from_huggingface(
    pretrained_weights="google/electra-base-generator",
    archive_map={
        "google/electra-base-generator": "https://cdn.huggingface.co/google/electra-base-generator/tf_model.h5"
    },
    hf_featurizer=TFElectraMainLayer,
    hf_tokenizer=ElectraTokenizerFast,
    hf_config=ElectraConfig,
    weights_replacement=[
        ("tf_electra_for_masked_lm/electra", "model/featurizer/tf_electra_main_layer")
    ],
)

HFElectraDiscrim = finetune_model_from_huggingface(
    pretrained_weights="google/electra-base-discriminator",
    archive_map={
        "google/electra-base-discriminator": "https://cdn.huggingface.co/google/electra-base-discriminator/tf_model.h5"
    },
    hf_featurizer=TFElectraMainLayer,
    hf_tokenizer=ElectraTokenizerFast,
    hf_config=ElectraConfig,
    weights_replacement=[
        (
            "tf_electra_for_pre_training/electra",
            "model/featurizer/tf_electra_main_layer",
        )
    ],
)

HFBert = finetune_model_from_huggingface(
    pretrained_weights="bert-base-uncased",
    archive_map={
        "bert-base-uncased": "https://cdn.huggingface.co/bert-base-uncased-tf_model.h5"
    },
    hf_featurizer=TFBertMainLayer,
    hf_tokenizer=BertTokenizerFast,
    hf_config=BertConfig,
    weights_replacement=[
        ("tf_bert_for_pre_training_2/bert/", "model/featurizer/tf_bert_main_layer/"),
        ("tf_bert_for_pre_training/bert/", "model/featurizer/tf_bert_main_layer/"),
    ],
)

HFT5 = finetune_model_from_huggingface(
    pretrained_weights="t5-base",
    archive_map={"t5-base": "https://cdn.huggingface.co/t5-base-tf_model.h5"},
    hf_featurizer=TFT5Model,
    hf_tokenizer=T5Tokenizer,
    hf_config=T5Config,
    weights_replacement=[
        ("tf_t5with_lm_head_model/shared/", "model/featurizer/shared/shared/"),
        ("tf_t5with_lm_head_model/encoder", "model/featurizer/encoder"),
        ("tf_t5with_lm_head_model/decoder", "model/target/decoder"),
    ],
    include_bos_eos="eos",
    add_tokens=["{", "}", "<"],  # "[", "]"],
)

HFT5Small = finetune_model_from_huggingface(
    pretrained_weights="t5-small",
    archive_map={"t5-small": "https://cdn.huggingface.co/t5-small-tf_model.h5"},
    hf_featurizer=TFT5Model,
    hf_tokenizer=T5Tokenizer,
    hf_config=T5Config,
    weights_replacement=[
        ("tf_t5with_lm_head_model/shared/", "model/featurizer/shared/shared/"),
        ("tf_t5with_lm_head_model/encoder", "model/featurizer/encoder"),
        ("tf_t5with_lm_head_model/decoder", "model/target/decoder"),
    ],
    include_bos_eos="eos",
    add_tokens=["{", "}", "<"],  # "[", "]"],
)

HFAlbert = finetune_model_from_huggingface(
    pretrained_weights="albert-base-v2",
    archive_map={
        "albert-base-v2": "https://cdn.huggingface.co/albert-base-v2-tf_model.h5"
    },
    hf_featurizer=TFAlbertMainLayer,
    hf_tokenizer=AlbertTokenizerFast,
    hf_config=AlbertConfig,
    weights_replacement=[
        ("tf_albert_for_masked_lm_1/albert/", "model/featurizer/tf_albert_main_layer/")
    ],
    config_overrides={"n_embed": 768, "n_epochs": 8, "lr": 2e-5, "batch_size": 8},
    aggressive_token_alignment=True,
)

HFAlbertXLarge = finetune_model_from_huggingface(
    pretrained_weights="albert-xlarge-v2",
    archive_map={
        "albert-xlarge-v2": "https://cdn.huggingface.co/albert-xlarge-v2-tf_model.h5"
    },
    hf_featurizer=TFAlbertMainLayer,
    hf_tokenizer=AlbertTokenizerFast,
    hf_config=AlbertConfig,
    weights_replacement=[
        ("tf_albert_for_masked_lm_5/albert/", "model/featurizer/tf_albert_main_layer/")
    ],
    config_overrides={"n_embed": 2048, "n_epochs": 8, "lr": 1e-5, "batch_size": 2},
    aggressive_token_alignment=True,
)


import bpy
import serial
import serial.tools.list_ports

COMMAND_START = 0x3C
COMMAND_END = 0x3E

class UartController:
    positions = {}
    serial_ports = []
    serial_connection = None
    handling = False

    def update_positions(self, _scene, _depsgraph):
        if self.handling:
            return
        self.handling = True
        bpy.ops.export_anim.live_mode()
        self.handling = False

    def send_position(self, servo_id, position):
        if position == self.positions.get(servo_id):
            return

        command = [COMMAND_START, servo_id]
        command += position.to_bytes(2, 'big')
        command += [COMMAND_END]

        try:
            self.serial_connection.write(command)
            self.positions[servo_id] = position
        except serial.SerialException:
            bpy.ops.export_anim.stop_live_mode()

    def scan_serial_ports(self):
        self.serial_ports.clear()

        for port in serial.tools.list_ports.comports():
            self.serial_ports.append(port.device)

    def get_serial_ports(self):
        return self.serial_ports

    def open_serial_connection(self, port, baud_rate):
        try:
            self.serial_connection = serial.Serial(
                port=port, baudrate=baud_rate)
            return True
        except (serial.SerialException, ValueError):
            return False

    def close_serial_connection(self):
        if self.is_connected():
            self.serial_connection.close()

        self.serial_connection = None
        self.positions = {}

    def is_connected(self):
        return (
            isinstance(self.serial_connection, serial.Serial)
            and self.serial_connection.is_open
            and (
                self.serial_connection.port in self.serial_ports
                or bpy.app.background
            )
        )

UART_CONTROLLER = UartController()


import datetime

""" Write the string to stdout with a timestamp. """
def logMsg(string):
	print("[{}] {}".format(datetime.datetime.now(), string))

	return string

""" read a file named fname and return its contents as a string """
def getValueFromFileContents(fname):
	token = ""

	try:
		f_token = open(fname, "r")
		f_token_lines = f_token.readlines()

		for line in f_token_lines:
			line = line.rstrip()
			if len(line) > 0:
				token = line
	except IOError:
		token = ""
		print("Could not read {} file.".format(fname))
	finally:
		f_token.close()

	return token

""" get the Discord API token from the config file on disk """
def getToken():
	return getValueFromFileContents("token")


########
# USDN #
########
# data stored as string in requests
import datetime
import time
def archive(browser, wa, folder):
    name = 'USDN'
    time_str = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
    browser.retry('https://neutrino.at/stats')
    time.sleep(10)
    browser.archive_mhtml('{}/{}-{}.mhtml'.format(folder, name, time_str))


import torch
from torch import nn
from AcT import AcT

"""
Code based on: Action Transformer: A Self-Attention Model for Short-Time Pose-Based
Human Action Recognition
From Author: Vittori Mazzia, Simone Angarano1, Francesco Salvetti, Federico Angelin and Marcello Chiaberge
"""
class Ensemble(nn.Module):
	""" Ensemble of AcT models

		Parameters:
		-----------

		Attributes
		----------:

			"""
	def __init__(self, ensemble_size, B=40, T=5, N=17, C=3, nhead=1, num_layer=4, d_last_mlp=256, classes=20):

		super().__init__()

		self.ensemble = nn.ModuleList()
		self.ensemble_size = ensemble_size
		self.B = B
		self.T = T
		self.N = N
		self.C = C
		self.nhead = nhead
		self.num_layer = num_layer
		self.d_last_mlp = d_last_mlp
		self.classes = classes

	def load_weights(self, PATH):

		for i in range(0, self.ensemble_size):
			self.ensemble.append(AcT(B=self.B, T=self.T, N=self.N, C=self.C, nhead=self.nhead, num_layer=self.num_layer, d_last_mlp=self.d_last_mlp
										,classes=self.classes))
			self.ensemble[i].load_state_dict(torch.load(PATH+'/models/model_'+str(i+1)+'.pth'))
			self.ensemble[i].eval()

	def forward(self, X_in):
		out = torch.zeros(self.classes)

		for i, l in enumerate(self.ensemble):
			out = l(X_in) + out

		return out / self.ensemble_size # [B, T+1, D_model]

"""
---------------------------------------
	Testing Zone
---------------------------------------
"""
if __name__=="__main__":
	# Layer-testing zone

	batch_size, T, N, C, nhead, num_layer, classes = 1, 30, 17, 3, 2, 4, 20
	X_in = torch.randn([batch_size,T,N,C])

	model = Ensemble(B=batch_size, T=30, N=17, C=3, nhead=3, num_layer=6, d_last_mlp=502, classes=20)
	out = model(X_in)

	if batch_size == 1:
		print("models voted for: ", out.argmax(0))
	else:
		print("models voted for: ", out.argmax(1))


import os
dirname = os.path.dirname(__file__)

from qiskit.algorithms.optimizers import SPSA
import torch
from torch.autograd import Variable
from sklearn.ensemble import IsolationForest
import numpy as np

import sys
sys.path.append(os.path.join(dirname, '../../'))
from modules.classical_autoencoder.classical_autoencoder import ClassicalAutoencoder
from modules.preprocessing.preprocessing import preprocess, sample_training_data, sample_vqc_training_data
from modules.QVC_autoencoder.utils import OptimizerLog, get_classification_probabilities

sys.path.append(os.path.join(dirname, '../../../HAE_demonstrator'))
from train.models import TrainJob

class QVCAutoencoder:
	def __init__(self, qc_index=0, custom_qc={}, job=None, max_iter=100, loss_value_classical=0.023, n_samples=150):
		path_cl = f'../../data/training_results/classical/training_result_loss_{loss_value_classical}.pt'
		self.cae = ClassicalAutoencoder()
		self.cae.load_state_dict(torch.load(os.path.join(dirname, path_cl)))
		self.cae.eval()

		fit_data = Variable(torch.FloatTensor(sample_training_data(1000)[0]))
		fit_data_latent = self.cae.get_latent_space_state(fit_data).tolist()
		self.if_model = IsolationForest().fit(fit_data_latent)

		self.max_iter = max_iter
		self.n_samples = n_samples
		self.job = job
		self.log = OptimizerLog(job)
		self.optimizer = SPSA(maxiter=max_iter, callback=self.log.update)
		self.qc_index = qc_index
		self.custom_qc = custom_qc

	def loss_function_multiclass(self, theta):
		cost = 0
		x, labels = sample_vqc_training_data(self.n_samples, True)
		x = self.cae.get_latent_space_state(Variable(torch.FloatTensor(x))).tolist()

		classifications = get_classification_probabilities(x,theta, self.qc_index, self.custom_qc)
		for i, classification in enumerate(classifications):
			p = classification.get(labels[i])
			cost += -np.log(p + 1e-10)
		cost /= len(x)
		return cost

	def loss_function_binary(self, theta):
		cost = 0
		x, labels = sample_vqc_training_data(self.n_samples, True)
		labels = np.array(labels)
		labels = np.where(labels>3, 1, 0)
		x = self.cae.get_latent_space_state(Variable(torch.FloatTensor(x))).tolist()

		classifications = get_classification_probabilities(x,theta, self.qc_index, self.custom_qc, True)
		for i, classification in enumerate(classifications):
			p = classification.get(labels[i])
			cost += -np.log(p + 1e-10)
		cost /= len(x)
		return cost

	def train(self, initial_point, is_binary=False):
		if is_binary:
			loss_func = self.loss_function_binary
		else:
			loss_func = self.loss_function_multiclass

		print(f"Training Started. \n Data points in Data set: {self.n_samples} \n Max Iter: {self.max_iter}")

		result = self.optimizer.minimize(loss_func, initial_point)
		opt_theta = result.x
		min_cost = result.fun

		if self.job:
			train_job = TrainJob.objects.get(id=self.job["id"])
			custom_pqc_job =  train_job.customCircuitJob

		if self.qc_index:
			if is_binary:
				directory = f"../../data/training_results_QVC/pqc{self.qc_index}/binary_cl/"
				path = f"../../data/training_results_QVC/pqc{self.qc_index}/binary_cl/loss_{round(min_cost, 5)}.txt"
			else:
				directory = f"../../data/training_results_QVC/pqc{self.qc_index}/multi_cl/"
				path = f"../../data/training_results_QVC/pqc{self.qc_index}/multi_cl/loss_{round(min_cost, 5)}.txt"
		elif self.custom_qc:
			directory = f"../../data/training_results_QVC/custom/{'binary_cl' if is_binary else 'multi_cl'}/"
			path = f"../../data/training_results_QVC/custom/{'binary_cl' if is_binary else 'multi_cl'}/custom_{custom_pqc_job.id}_loss_{round(min_cost, 5)}.txt"
		result_path = os.path.join(dirname, path)

		try:
			f = open(result_path, 'a')
		except FileNotFoundError:
			os.makedirs(os.path.join(dirname, directory))
			f = open(result_path, 'a')

		for theta in opt_theta:
			f.write(str(theta)+"\n")
		f.close()
		return result_path

	def eval(self, theta, test_data, is_binary=False):
		x = self.cae.get_latent_space_state(Variable(torch.FloatTensor(test_data))).tolist()

		probs = get_classification_probabilities(x,theta, self.qc_index, self.custom_qc, is_binary)

		if is_binary:
			predictions = [-1 if p[0] >= p[1] else 1 for p in probs]
		else:
			predictions = [-1 if p[1]+p[2]+p[3] >= p[4]+p[5]+p[6]+p[7] else 1 for p in probs]

		return predictions


import math
import torch
import numpy as np
import pandas as pd

from torch import nn
from torch.utils.data.dataset import random_split
from sklearn.metrics import confusion_matrix

from neural_network import NeuralNetwork, train, test
from helper_functions import get_data, create_datasets, get_dataloaders
from helper_functions import plot_loss, plot_accuracy

def single_run(stack, learning_rate, stopping_criteria, returnVals):
    # Init model
    model = NeuralNetwork(stack)
    print(model)

    # exit()
    # CSV data as tensor objects
    X, y = get_data()

    # Tensor data set objects that are split for train, validation, and test
    trainSize = .8
    dsTrain, dsTest = create_datasets(X, y, trainSize)

    numTrainB = math.ceil(len(dsTrain)*.9)
    numVal = len(dsTrain) - numTrainB
    dsTrainB, dsVal = random_split(dsTrain, [numTrainB, numVal])

    # Data Loader objects that are use in training and testing, describe batchSize
    batchSize = 32
    dlTrain, dlTest = get_dataloaders(dsTrain, dsTest, 1)
    dlTrainB, dlVal = get_dataloaders(dsTrainB, dsVal, batchSize)

    # Model Paramaters
    loss_fn = torch.nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

    epochs = 1000

    # Train and Plot Train Loss / Validation Loss / Validation Accuracy
    countEpochsTrain, training_loss, validation_loss, validation_accuracy, predictions, true = train(
        dlTrainB, dlVal, model, loss_fn, optimizer, epochs, stopping_criteria, 100)
    confMatVal = confusion_matrix(true, predictions)
    print('Number of Epochs to Train = {}'.format(countEpochsTrain))
    print('Final Training Loss = {}'.format(training_loss[-1]))
    print('Validation Loss = {}'.format(validation_loss[-1]))
    print('Validation Accuracy = {}'.format(validation_accuracy[-1]))
    print('Validation Confusion Matrix\n{}'.format(str(confMatVal)))

    plot_loss(training_loss, validation_loss)
    plot_accuracy(validation_accuracy)

    # Retrain with all data and Test, no stopping criteria but same number of epochs
    test_loss, test_accuracy, predictions, true = test(
        dlTest, model, loss_fn, False)

    # print(predictions,  true)
    confMatTest = confusion_matrix(true, predictions)
    print('Test Accuracy = {}'.format(test_accuracy))
    print('Test Confusion Matrix\n{}'.format(str(confMatTest)))

    if returnVals == True:
        return [countEpochsTrain, training_loss[-1], validation_loss[-1], validation_accuracy[-1], test_accuracy]

hidden_layers = [
    [nn.Linear(13, 12), nn.ReLU(), nn.Linear(12, 3)],
    [nn.Linear(13, 12), nn.ReLU(), nn.Linear(
        12, 6), nn.ReLU(), nn.Linear(6, 3)],
    [nn.Linear(13, 12), nn.ReLU(), nn.Linear(12, 8), nn.ReLU(),
     nn.Linear(8, 4), nn.ReLU(), nn.Linear(4, 3)]
]

learning_rate_arr = [.0001, .001, .01]
stopping_criteria_arr = ['trainLoss', 'valLoss', 'valAccuracy']

# # # Part A
single_run(hidden_layers[0], learning_rate_arr[2], stopping_criteria_arr[1], False)

# # # Part B

# Hidden Layers and Nodes: Average performance accross other parameters
# metrics_avg = np.zeros((3, 5))
# for a in range(len(hidden_layers)):
#     hidden_layer_a = hidden_layers[a]
#     metrics_matrix = np.zeros((9, 5))
#     count = 0
#     for b in range(len(stopping_criteria_arr)):
#         stopping_criteria_b = stopping_criteria_arr[b]
#         for c in range(len(learning_rate_arr)):
#             learning_rate_c = learning_rate_arr[c]
#             metrics_arr = single_run(
#                 hidden_layer_a, learning_rate_c, stopping_criteria_b, True)
#             metrics_matrix[count] = metrics_arr
#             count +=1

#     metrics_avg[a] = np.mean(metrics_matrix, axis=0)

# pd.DataFrame(np.around(metrics_avg.T, decimals=2)).to_csv("compare_hidden_layers_and_nodes.csv")

# Learning Rate: Average performance accross other parameters
# metrics_avg = np.zeros((3, 5))
# for a in range(len(learning_rate_arr)):
#     learning_rate_a = learning_rate_arr[a]
#     metrics_matrix = np.zeros((9, 5))
#     count = 0
#     for b in range(len(stopping_criteria_arr)):
#         stopping_criteria_b = stopping_criteria_arr[b]
#         for c in range(len(hidden_layers)):
#             hidden_layers_c = hidden_layers[c]
#             metrics_arr = single_run(
#                 hidden_layers_c, learning_rate_a, stopping_criteria_b, True)
#             metrics_matrix[count] = metrics_arr
#             count +=1

#     metrics_avg[a] = np.mean(metrics_matrix, axis=0)

# pd.DataFrame(np.around(metrics_avg.T, decimals=2)).to_csv("compare_learning_rate.csv")

# # Stopping Crteria: Average performance accross other parameters
# metrics_avg = np.zeros((3, 5))
# for a in range(len(stopping_criteria_arr)):
#     stopping_criteria_a = stopping_criteria_arr[a]
#     metrics_matrix = np.zeros((9, 5))
#     count = 0
#     for b in range(len(learning_rate_arr)):
#         learning_rate_b = learning_rate_arr[b]
#         for c in range(len(hidden_layers)):
#             hidden_layers_c = hidden_layers[c]
#             metrics_arr = single_run(
#                 hidden_layers_c, learning_rate_b, stopping_criteria_a, True)
#             metrics_matrix[count] = metrics_arr
#             count +=1

#     metrics_avg[a] = np.mean(metrics_matrix, axis=0)

# pd.DataFrame(np.around(metrics_avg.T, decimals=2)).to_csv("compare_stopping_criteria.csv")


"""
Tests for example generation.
"""
from __future__ import unicode_literals
from textwrap import dedent

import pytest
from yaml import safe_load

from traitlets import TraitError

from straitlets.serializable import Serializable
from straitlets.test_utils import assert_serializables_equal, multifixture
from straitlets.traits import (
    Bool,
    Dict,
    Instance,
    Integer,
    Unicode,
)

class Point(Serializable):
    x = Integer().example(100)
    y = Integer().example(101)

class ExampleClass(Serializable):

    bool_tag = Bool().example(True)
    bool_default = Bool(default_value=False)
    bool_both = Bool(default_value=False).example(True)

    int_tag = Integer().example(1)
    int_default = Integer(default_value=2)
    int_both = Integer(default_value=100).example(3)

    dict_tag = Dict().example({'a': 'b'})
    dict_default = Dict(default_value={'c': 'd'})
    dict_both = Dict(default_value={'z': 'z'}).example({'e': 'f', 'g': 'h'})

    instance_tag = Instance(Point).example(Point(x=0, y=1))
    instance_default = Instance(Point, default_value=Point(x=2, y=3))
    instance_both = Instance(Point, default_value=Point(x=4, y=5)).example(
        Point(x=6, y=7),
    )
    # Instance provides a default example if the value is a Serializable
    # that can provide an example.
    instance_neither = Instance(Point)

@pytest.fixture
def expected_instance():
    return ExampleClass(
        bool_tag=True,
        bool_default=False,
        bool_both=True,
        int_tag=1,
        int_default=2,
        int_both=3,
        dict_tag={'a': 'b'},
        dict_default={'c': 'd'},
        dict_both={'e': 'f', 'g': 'h'},
        instance_tag=Point(x=0, y=1),
        instance_default=Point(x=2, y=3),
        instance_both=Point(x=6, y=7),
        instance_neither=Point(x=100, y=101),
    )

@pytest.fixture
def expected_yaml():
    return dedent(
        """
        bool_tag: true
        bool_default: false
        bool_both: true
        int_tag: 1
        int_default: 2
        int_both: 3
        dict_tag:
            a: b
        dict_default:
            c: d
        dict_both:
            e: f
            g: h
        instance_tag:
            x: 0
            y: 1
        instance_default:
            x: 2
            y: 3
        instance_both:
            x: 6
            y: 7
        instance_neither:
            x: 100
            y: 101
        """
    )

@multifixture
def skip_names():
    yield ()
    yield ('bool_tag',)
    yield ('bool_tag', 'int_tag')
    yield ('dict_tag', 'instance_tag', 'instance_neither')
    yield [
        name for name in ExampleClass.class_trait_names()
        if name.endswith('tag')
    ]

def test_example_instance(expected_yaml, expected_instance):
    instance = ExampleClass.example_instance()

    assert_serializables_equal(instance, expected_instance)
    assert_serializables_equal(instance, ExampleClass.from_yaml(expected_yaml))

    assert ExampleClass.example_yaml() == expected_instance.to_yaml()

def test_example_skip_names(expected_instance, skip_names):
    instance = ExampleClass.example_instance(skip=skip_names)
    assert_serializables_equal(instance, expected_instance, skip=skip_names)

    for name in skip_names:
        with pytest.raises(TraitError):
            getattr(instance, name)

def test_write_example_yaml(tmpdir, expected_instance, skip_names):

    path = tmpdir.join("test.yaml").strpath
    ExampleClass.write_example_yaml(path, skip=skip_names)

    from_file = ExampleClass.from_yaml_file(path)
    assert_serializables_equal(
        from_file,
        expected_instance,
        skip=skip_names,
    )

    for name in skip_names:
        with pytest.raises(TraitError):
            getattr(from_file, name)

def test_nested_example():

    class C(Serializable):
        point = Instance(Point)
        unicode_ = Unicode().tag(example='foo')

    class B(Serializable):
        value = Integer().tag(example=ord('b'))
        next_ = Instance(C)

    class A(Serializable):
        value = Integer().tag(example=ord('a'))
        next_ = Instance(B)

    expected = A(
        value=ord('a'),
        next_=B(
            value=ord('b'),
            next_=C(
                point=Point.example_instance(),
                unicode_='foo',
            ),
        ),
    )

    assert_serializables_equal(expected, A.example_instance())

def test_readme_example():

    class Point(Serializable):
        x = Integer().example(0)
        y = Integer().example(0)

    class Vector(Serializable):
        head = Instance(Point)
        tail = Instance(Point).example(Point(x=1, y=3))

    example_yaml = Vector.example_instance().to_yaml()
    expected = {
        "head": {"x": 0, "y": 0},
        "tail": {"x": 1, "y": 3},
    }
    assert safe_load(example_yaml) == expected


import os
import sys
from scapy.all import *

import common
# logger printing everything on stdout

class TestLogger:
    """
    Log test data to a folder (stdout and pcap)
    """

    def __init__(self, folder, name, verbose=0):
        self._folder = folder
        self._name = name
        self._verbose = verbose

        self._txt_trace_file = None
        self._pcap_out = None

        if folder is not None and name is not None:
            if not os.path.isdir(folder):
                os.makedirs(folder)
            self._txt_trace_file = open(folder + "/" + name + ".txt", "w")

        self.pkt_list = []

    def write_pkt(self, pkt):
        """
        Add a packet to the packet list, THIS DOES NOT WRITE A PCAP !
        see write_pcap
        """
        self.pkt_list.append(pkt)

    def write_pcap(self):
        """
        Write the pcap output file, with packets from pkt_list
        """
        if self._folder is not None and self._name is not None:
            self._pcap_out = wrpcap(
                self._folder + "/" + self._name + ".pcap", self.pkt_list)

    def println(self, msg="", verbose=-1):
        self.write(msg + "\n", verbose)

    def write(self, msg, verbose=-1):
        if self._verbose >= verbose:
            sys.stdout.write(msg)
            sys.stdout.flush()
        self._txt_trace(msg)

    def log_warning(self, msg):
        if self._verbose > 0:
            common.print_warning(msg)
        self._txt_trace("[!] WARNING : " + msg + "\n")

    def log_error(self, msg):
        if self._verbose > 0:
            common.print_error(msg)
        self._txt_trace("[!] ERROR : " + msg + "\n")

    def close(self):
        """
        Logger must be closed after use :
        closes the txt output file
        write the pkt_list to the pcap_output file
        """
        if self._txt_trace_file is not None:
            self._txt_trace_file.close()
        self.write_pcap()

    def _txt_trace(self, msg):
        if self._txt_trace_file is not None:
            self._txt_trace_file.write(msg)

none_logger = TestLogger(None, None, -1)
stdout_logger = TestLogger(None, None, 32768)


from flask import Flask, render_template, flash

app = Flask(__name__)
app.secret_key = 'this is a secret'

@app.route('/')
def index():
    flash('Welcome to my website guest!')
    return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=True)