import os
import sys
import time
from threading import Thread

from sys import platform
from bs4 import BeautifulSoup
from lxml import html
from selenium import webdriver

from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager

import config
import helpers

BASE_URL = "https://www.sears.com/"
AFFILIATE_KEY = "sears"

class Searscom(Thread):
    def __init__(self, searchkey):
        self.searchkey = searchkey
        self.first_driver = ''
        self.goodlist = []
        self.timeout = 30
        self.start_url = 'https://www.sears.com/search='
        print("search key===>", searchkey)
        if platform == "linux" or platform == "linux2":
            self.input_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "chromedriver")
        elif platform == "win32":
            self.input_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "chromedriver.exe")
        super(Searscom, self).__init__()

    def scroller(self, timeout):
        last_height = self.first_driver.execute_script("return document.body.scrollHeight")
        new_height = 0
        while True:
            self.first_driver.execute_script(f"window.scrollTo(0,  {new_height+100});")
            new_height +=100
            if new_height >= last_height*10:
                break
        time.sleep(1)

    def run(self):
        try :
            self.first_driver = self.open_chrome()
            self.first_driver.delete_all_cookies()
            chrome = self.first_driver.get(self.start_url + self.searchkey)
            # if (chrome is None):
            #     self.first_driver.quit()
            #     print( "Sears.com : Access Denied")
            #     return
            time.sleep(5)

            total = 0

            while True:
                self.scroller(3)
                soup = BeautifulSoup( self.first_driver.page_source, "html.parser")
                obj = soup.find_all(class_="card-container card-border")

                if ( len(obj) == 0):
                    print("Sears.com : Please Enter Correct Key.")
                    self.first_driver.quit()
                    return

                for li in obj:
                    dic ={}
                    image = li.find(class_="card-image-height-content")
                    if ( image is not None):
                        dic['image1'] = image.img['ng-src']
                        # dic['detail'] = helpers.format_impact_affiliate_link("https://Sears.com/" + image['href'],BASE_URL,AFFILIATE_KEY)
                    print("image===>", dic['image1'])
                    price = li.find("span", class_="card-price-orig")
                    if ( price is not None ):
                        dic['price'] = li.get_text()
                    else: dic['price'] = ""
                    print("price---->", dic['price'])

                    title = li.find("h3", class_="card-title")
                    if ( title is not None ):
                        dic['title'] = title.a.get_text()
                    else: dic['title'] = ""
                    print("title====>", dic['title'])

                    rate = li.find("span", attrs={"bo-if": "!!product.reviewCount"})
                    if ( rate is not None ):
                        dic['rate'] = rate.get_text()
                    else: dic['rate'] = ""
                    print("rate====>", dic['rate'])

                    shipmsg = li.find("span", attrs={"bo-text": "'Sold by ' + product.storeOrigin"})
                    if ( shipmsg is not None ):
                        dic['shipmsg'] = shipmsg.get_text()
                    else: dic['shipmsg'] = ""

                    shipping = li.find("span", attrs={"bo-text" : "product.bestDealMessage"})
                    if ( shipping is not None ):
                        dic['shipping'] = shipping.get_text()
                    else: dic['shipping'] = ""
                    print("shipping===>", dic['shipping'])

                    dic["source"] = "sears"
                    total += 1
                    print("total-------------------->,", total)
                    self.goodlist.append(dic)
                # print("SEARS ",total)
                    if ( total>=8):
                        print("sears.com:  " , total)
                        self.first_driver.quit()
                        return

                pagenation = soup.find("div", attrs={"ng-if" : "selectedPage.id < totalPages"})
                if ( pagenation is not None ):
                    nextbtn = self.first_driver.find_element_by_xpath("//div[@ng-if='selectedPage.id < totalPages']")
                    nextbtn.click()
                    time.sleep(2)
                else:
                    self.first_driver.quit()
                    print("Sears.com:  " , total)
                    return
        except Exception as e:
            self.first_driver.quit()
            print("ERROR in sears")
            print(e)

    def open_chrome(self):
        options = webdriver.ChromeOptions()
        options.add_argument("--headless")
        options.add_argument("--disable-gpu")
        options.add_argument("--no-sandbox")
        options.add_argument("enable-automation")
        options.add_argument("--disable-infobars")
        options.add_argument("--disable-dev-shm-usage")
        # options.add_argument("--proxy-server=us.smartproxy.com:10000" )
        # options.add_argument("--remote-debugging-port=9229")
        # _driver = webdriver.Chrome(chrome_options=options)
        _driver = webdriver.Chrome(options=options, executable_path='scrapers/chromedriver.exe')
        _driver.set_page_load_timeout(config.max_duration)

        return _driver


import numpy as np
from sklearn.model_selection import KFold

from sklearn import svm
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.neural_network import MLPClassifier

import matplotlib.pyplot as plt

PLOTS_PATH = 'Cross_valid_plots'

class crossValidator():
    '''
    This class perfroms k-Fold Cross-Validation on training set to choose
    the proper hyperparameters for each model. It selects the models
    with best performance.
    '''
    def __init__(self, train_x, train_y, num_of_folds):
        self.set_x = train_x
        self.set_y = train_y
        self.k = num_of_folds
        self.kf = KFold(n_splits=num_of_folds)
        self.best_svm = None
        self.best_knn = None
        self.best_random_forest = None
        self.best_mlp = None
        self.num_of_classes = 13

    def tuneSVM(self, c_steps ,type='coarse', graphic=True):
        scores = []
        c_list = c_steps
        for C in c_list:
            print("training for C=", C)
            avg_score = 0
            model = svm.SVC(kernel='rbf', C=C)
            for train_index, test_index in self.kf.split(self.set_x):
                x_train, x_test = self.set_x[train_index], self.set_x[test_index]
                y_train, y_test = self.set_y[train_index], self.set_y[test_index]
                model.fit(x_train, y_train)
                score = model.score(x_test, y_test)
                print("score ", score)
                avg_score += score
            scores.append(avg_score/self.k)
            print("avg score = ", avg_score/self.k)

        if graphic:
            plt.plot(c_list, scores)
            plt.title('C hyperparameter tuning for SVM')
            plt.ylabel('Accuracy')
            plt.xlabel('C value')
            if type == 'coarse':
                plt.xscale('log')
                name = 'SVM_C_hyper_fig_coarse.png'
            else:
                name = 'SVM_C_hyper_fig_fine.png'
            fig = plt.gcf()
            path = PLOTS_PATH + '\\' + name
            fig.savefig(path, bbox_inches='tight')
            plt.show()

    def tuneKNN(self, k_stop,graphic=True):
        scores_uniform = []
        scores_distance = []
        k_list = np.arange(1, k_stop, 2)
        for k in k_list:
            print("training for k=", k)
            avg_score_uniform = 0
            avg_score_distance = 0
            knn_uniform = KNeighborsClassifier(n_neighbors=k, weights='uniform')
            knn_distance = KNeighborsClassifier(n_neighbors=k, weights='distance')
            for train_index, test_index in self.kf.split(self.set_x):
                x_train, x_test = self.set_x[train_index], self.set_x[test_index]
                y_train, y_test = self.set_y[train_index], self.set_y[test_index]
                knn_uniform.fit(x_train, y_train)
                knn_distance.fit(x_train, y_train)
                score_uniform = knn_uniform.score(x_test, y_test)
                score_distance = knn_distance.score(x_test,y_test)
                avg_score_uniform += score_uniform
                avg_score_distance += score_distance
            scores_uniform.append(avg_score_uniform/self.k)
            scores_distance.append(avg_score_distance/self.k)
            print("avg scores = ", avg_score_uniform/self.k, "  ", avg_score_distance/self.k)

        if graphic:
            plt.plot(k_list, scores_uniform, label="uniform")
            plt.plot(k_list, scores_distance, label='distance')
            plt.title('k hyper parameter tuning for knn')
            plt.ylabel('Accuracy')
            plt.xlabel('number of neighbours')
            plt.legend()
            fig = plt.gcf()
            path = PLOTS_PATH + '\\' + 'k_hyper_fig.png'
            fig.savefig(path, bbox_inches='tight')
            plt.show()

    def tuneNForest(self, n_stop, graphic=True):
        scores1 = []
        scores2 = []
        scores3 = []
        n_list = np.arange(1, n_stop, 1)
        for n in n_list:
            print("training for n=", n)
            avg_score1 = 0
            avg_score2 = 0
            avg_score3 = 0

            forest1 = RandomForestClassifier(n_estimators=n, max_depth=5, min_samples_split=0.1)
            forest2 = RandomForestClassifier(n_estimators=n, max_depth=15, min_samples_split=0.1)
            forest3 = RandomForestClassifier(n_estimators=n, max_depth=35, min_samples_split=0.1)
            for train_index, test_index in self.kf.split(self.set_x):
                x_train, x_test = self.set_x[train_index], self.set_x[test_index]
                y_train, y_test = self.set_y[train_index], self.set_y[test_index]
                forest1.fit(x_train, y_train)
                forest2.fit(x_train, y_train)
                forest3.fit(x_train, y_train)
                score1 = forest1.score(x_test, y_test)
                score2 = forest2.score(x_test, y_test)
                score3 = forest3.score(x_test, y_test)
                avg_score1 += score1
                avg_score2 += score2
                avg_score3 += score3
            scores1.append(avg_score1/self.k)
            scores2.append(avg_score2 / self.k)
            scores3.append(avg_score3 / self.k)

        if graphic:
            plt.plot(n_list, scores1, label="depth=5")
            plt.plot(n_list, scores2, label="depth=10")
            plt.plot(n_list, scores3, label="depth=15")
            plt.title('num of estimators hyper parameter tuning for random forest')
            plt.ylabel('Accuracy')
            plt.xlabel('number of estimators')
            plt.legend()
            fig = plt.gcf()
            path = PLOTS_PATH + '\\' + 'n_hyper_forest_fig.png'
            fig.savefig(path, bbox_inches='tight')
            plt.show()

    def tuneDepthForest(self, d_stop,graphic=True):
        scores1 = []
        scores2 = []
        scores3 = []
        d_list = np.arange(1, d_stop, 1)
        for d in d_list:
            print("training for d=", d)
            avg_score1 = 0
            avg_score2 = 0
            avg_score3 = 0
            forest1 = RandomForestClassifier(n_estimators=25, max_depth=d, min_samples_split=0.1)
            forest2 = RandomForestClassifier(n_estimators=50, max_depth=d, min_samples_split=0.1)
            forest3 = RandomForestClassifier(n_estimators=100, max_depth=d, min_samples_split=0.1)
            for train_index, test_index in self.kf.split(self.set_x):
                x_train, x_test = self.set_x[train_index], self.set_x[test_index]
                y_train, y_test = self.set_y[train_index], self.set_y[test_index]
                forest1.fit(x_train, y_train)
                forest2.fit(x_train, y_train)
                forest3.fit(x_train, y_train)
                score1 = forest1.score(x_test, y_test)
                score2 = forest2.score(x_test, y_test)
                score3 = forest3.score(x_test, y_test)
                avg_score1 += score1
                avg_score2 += score2
                avg_score3 += score3
            scores1.append(avg_score1/self.k)
            scores2.append(avg_score2 / self.k)
            scores3.append(avg_score3 / self.k)

        if graphic:
            plt.plot(d_list, scores1, label="n_estimators=25")
            plt.plot(d_list, scores2, label="n_estimators=50")
            plt.plot(d_list, scores3, label="n_estimators=100")
            plt.title('max depth hyper parameter tuning for random forest')
            plt.ylabel('Accuracy')
            plt.xlabel('max depth')
            plt.legend()
            fig = plt.gcf()
            path = PLOTS_PATH + '\\' + 'd_hyper_forest_fig.png'
            fig.savefig(path, bbox_inches='tight')
            plt.show()

    def tuneSplitForest(self, graphic=True):
        scores1 = []
        scores2 = []
        scores3 = []
        s_list = np.arange(0.01, 1, 0.05)
        for s in s_list:
            print("training for s=", s)
            avg_score1 = 0
            avg_score2 = 0
            avg_score3 = 0
            forest1 = RandomForestClassifier(n_estimators=50, max_depth=5, min_samples_split=s)
            forest2 = RandomForestClassifier(n_estimators=50, max_depth=15, min_samples_split=s)
            forest3 = RandomForestClassifier(n_estimators=50, max_depth=50, min_samples_split=s)
            for train_index, test_index in self.kf.split(self.set_x):
                x_train, x_test = self.set_x[train_index], self.set_x[test_index]
                y_train, y_test = self.set_y[train_index], self.set_y[test_index]
                forest1.fit(x_train, y_train)
                forest2.fit(x_train, y_train)
                forest3.fit(x_train, y_train)
                score1 = forest1.score(x_test, y_test)
                score2 = forest2.score(x_test, y_test)
                score3 = forest3.score(x_test, y_test)
                avg_score1 += score1
                avg_score2 += score2
                avg_score3 += score3
            scores1.append(avg_score1/self.k)
            scores2.append(avg_score2 / self.k)
            scores3.append(avg_score3 / self.k)

        if graphic:
            plt.plot(s_list, scores1, label="max_depth=5")
            plt.plot(s_list, scores2, label="max_depth=15")
            plt.plot(s_list, scores3, label="max_depth=50")
            plt.title('min_samples_split parameter tuning for random forest')
            plt.ylabel('Accuracy')
            plt.xlabel('Min samples allowed to split')
            plt.legend()
            fig = plt.gcf()
            path = PLOTS_PATH + '\\' + 's_hyper_forest_fig.png'
            fig.savefig(path, bbox_inches='tight')
            plt.show()

    def tuneMLP(self, max_iter = 500, graphic=True):
        scores1 = []
        scores2 = []
        scores3 = []
        scores4 = []
        scores5 = []
        scores6 = []
        h_list = np.arange(15, 170, 20)
        for h in h_list:
            print("training for h=", h)
            avg_score1 = 0
            avg_score2 = 0
            avg_score3 = 0
            avg_score4 = 0
            avg_score5 = 0
            avg_score6 = 0
            mlp1 = MLPClassifier([h], activation='relu', max_iter=max_iter)
            mlp2 = MLPClassifier([h, h], activation='relu', max_iter=max_iter)
            mlp3 = MLPClassifier([h, h, h], activation='relu', max_iter=max_iter)
            mlp4 = MLPClassifier([h], activation='tanh', max_iter=max_iter)
            mlp5 = MLPClassifier([h, h], activation='tanh', max_iter=max_iter)
            mlp6 = MLPClassifier([h, h, h], activation='tanh', max_iter=max_iter)
            for train_index, test_index in self.kf.split(self.set_x):
                x_train, x_test = self.set_x[train_index], self.set_x[test_index]
                y_train, y_test = self.set_y[train_index], self.set_y[test_index]
                mlp1.fit(x_train, y_train)
                mlp2.fit(x_train, y_train)
                mlp3.fit(x_train, y_train)
                mlp4.fit(x_train, y_train)
                mlp5.fit(x_train, y_train)
                mlp6.fit(x_train, y_train)
                score1 = mlp1.score(x_test, y_test)
                score2 = mlp2.score(x_test, y_test)
                score3 = mlp3.score(x_test, y_test)
                score4 = mlp4.score(x_test, y_test)
                score5 = mlp5.score(x_test, y_test)
                score6 = mlp6.score(x_test, y_test)
                avg_score1 += score1
                avg_score2 += score2
                avg_score3 += score3
                avg_score4 += score4
                avg_score5 += score5
                avg_score6 += score6
            scores1.append(avg_score1 / self.k)
            scores2.append(avg_score2 / self.k)
            scores3.append(avg_score3 / self.k)
            scores4.append(avg_score4 / self.k)
            scores5.append(avg_score5 / self.k)
            scores6.append(avg_score6 / self.k)

        if graphic:
            plt.plot(h_list, scores1, label="1 hidden, relu")
            plt.plot(h_list, scores2, label="2 hidden, relu")
            plt.plot(h_list, scores3, label="3 hidden, relu")
            plt.plot(h_list, scores4, label="1 hidden, tanh")
            plt.plot(h_list, scores5, label="2 hidden, tanh")
            plt.plot(h_list, scores6, label="3 hidden, tanh")
            plt.title('hidden layers size tuning for MLP')
            plt.ylabel('Accuracy')
            plt.xlabel('hidden layers size')
            plt.legend()
            fig = plt.gcf()
            path = PLOTS_PATH + '\\' + 'mlp_h_fig.png'
            fig.savefig(path, bbox_inches='tight')
            plt.show()


import logging
import os
import torch
from torch.utils.data import DataLoader
from tqdm import tqdm
from transformers import AutoModelForSequenceClassification, AutoTokenizer, Trainer, TrainingArguments

from utils import TextDataset, compute_metrics, move_dict_to_device, separate_labels

def train_classification_model(args, train_data, val_data):
    """Trains classification model."""

    classification_model_path = os.path.join(args.output_dir, args.cls_model_save_name)
    if not args.cls_overwrite_output_dir:
        try:
            logging.info("Trying to load classification model if it exists..")
            device = torch.device("cuda")
            model = AutoModelForSequenceClassification.from_pretrained(classification_model_path).to(device)
            tokenizer = AutoTokenizer.from_pretrained(classification_model_path)
            logging.info(f"Successfully loaded classification model and tokenizer from {classification_model_path}.")
            return {"model": model, "tokenizer": tokenizer}

        except OSError:
            logging.info("Classification model not found. Will need to train new model.")
            pass

    logging.info(f"Loading pretrained model {args.cls_model}...")
    tokenizer = AutoTokenizer.from_pretrained(args.cls_model)
    model = AutoModelForSequenceClassification.from_pretrained(args.cls_model).to('cuda')
    logging.info("Model and tokenizer loaded.")

    logging.info("Tokenizing and creating validation and train dataset..")
    train_texts, train_labels = separate_labels(train_data)
    train_dataset = TextDataset(args, tokenizer, train_texts, train_labels)
    val_texts, val_labels = separate_labels(val_data)
    val_dataset = TextDataset(args, tokenizer, val_texts, val_labels)
    logging.info(f"Created train ({len(train_dataset)} examples) and valid ({len(val_dataset)} examples) dataset.")

    training_args = TrainingArguments(
        output_dir=classification_model_path,
        overwrite_output_dir=True,
        evaluation_strategy="steps",
        eval_steps=500,
        per_device_train_batch_size=args.cls_per_device_train_batch_size,
        per_device_eval_batch_size=args.cls_per_device_eval_batch_size,
        learning_rate=args.cls_learning_rate,
        num_train_epochs=args.cls_num_train_epochs,
        logging_strategy="steps",
        logging_steps=500,
        logging_dir=os.path.join(args.output_dir, "cls_logs", args.exp_name),
        disable_tqdm=False,
        load_best_model_at_end=True,
        metric_for_best_model="f1_score",
        report_to=["tensorboard"],
    )

    trainer = Trainer(
        model=model,
        args=training_args,
        train_dataset=train_dataset,
        eval_dataset=val_dataset,
        tokenizer=tokenizer,
        compute_metrics=compute_metrics,
    )

    logging.info("Training classification model..")
    trainer.train()

    logging.info(f"Saving model to {classification_model_path}")
    trainer.save_model(classification_model_path)

    return {"model": model, "tokenizer": tokenizer}

def run_classification_model(args, classification_model, texts, progress_bar=False):
    """Runs classification model and returns predictions."""

    if len(texts) == 0:
        return []

    model, tokenizer = classification_model["model"], classification_model["tokenizer"]
    model.eval()

    text_dataset = TextDataset(args, tokenizer, texts)
    dataloader = DataLoader(
        text_dataset, batch_size=args.cls_per_device_eval_batch_size, shuffle=False, num_workers=4, pin_memory=True
    )
    if progress_bar:
        dataloader = tqdm(dataloader)

    predictions = []
    for batch in dataloader:
        with torch.no_grad():
            move_dict_to_device(batch, "cuda")
            logits = model(**batch).logits
            batch_predictions = logits.argmax(dim=1).tolist()
            predictions.extend(batch_predictions)

    return predictions

def find_leakages(args, classification_model, data, cache=None):
    """Finds the leakages given the classification model and the data."""

    texts = []
    labels = []
    for i in data:
        y, x = i.split(sep="\t")
        texts.append(x)
        labels.append(int(y))

    if cache is None:
        predictions = run_classification_model(args, classification_model, texts, progress_bar=True)
    else:
        predictions = [cache["prediction_map"][text] for text in texts]

    leakages = []
    filter_candidates = 0
    for text, label, prediction in zip(texts, labels, predictions):
        if prediction == 1:
            filter_candidates += 1
        elif label == 1 and prediction == 0:
            leakages.append(text)
        else:
            continue

    return leakages, filter_candidates

def get_embedding_model(model):
    """Gets base transformer model from SequenceClassification Model."""

    basenames = ["bert", "roberta", "distilbert"]
    for basename in basenames:
        if hasattr(model, basename):
            return getattr(model, basename)

    raise ValueError("Couldn't find the base model.")

def compute_embeddings(args, texts, embedding_model, convert_to_numpy=False, progress_bar=False):
    """Computes embeddings of texts given an embedding model."""

    model, tokenizer = embedding_model["model"], embedding_model["tokenizer"]
    model.eval()
    text_dataset = TextDataset(args, tokenizer, texts)
    dataloader = DataLoader(
        text_dataset, batch_size=args.cls_per_device_eval_batch_size, shuffle=False, num_workers=4, pin_memory=True
    )
    if progress_bar:
        dataloader = tqdm(dataloader)

    embeddings = []

    for batch in dataloader:
        with torch.no_grad():
            move_dict_to_device(batch, "cuda")
            batch_embeddings = model(**batch, output_hidden_states=True).hidden_states[-1][:, 0, :]
            if convert_to_numpy:
                batch_embeddings = batch_embeddings.cpu()
            embeddings.append(batch_embeddings)
    embeddings = torch.cat(embeddings)

    if convert_to_numpy:
        embeddings = embeddings.numpy()

    return embeddings


#!/urs/env/bin python

""" Bagging meta-estimator for 8 classes (1,2,3,4,5,6,7,8)
"""

__author__ = 'Nanxiang Zhao'
__email__ = 'samzhao@umich.edu'

import numpy as np
from sklearn.ensemble import BaggingClassifier
from sklearn.model_selection import cross_val_score

# training the model
clf = BaggingClassifier(max_samples = 0.65)

# loading the data
training = np.load('eight_class_train.npy')
testing = np.load('eight_class_test.npy')

# cross-validation
scores = cross_val_score(clf, training[:, 0:20], training[:, 20], cv=5)
print(scores.mean())
print(scores.std())

# predict on testing data
clf = clf.fit(training[:, 0:20], training[:, 20])
answers = clf.predict(testing[:, 0:20])
accuracy = np.sum(answers == testing[:, 20]) / float(len(testing[:,20]))
score_prob = clf.predict_proba(testing[:, 0:20])
print(score_prob)
print(accuracy)


# Copyright (c) 2014 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import time

import requests

class WebpageTestClient(object):

    def __init__(self, wpt_url, api_key):
        self.wpt_url = wpt_url
        self.api_key = api_key

    def start_test(self, test_url, test_location, runs=5):
        """Starts webpage test

        :param test_url: the url which needs performance metrics
        :param test_location: location to run the test on
        :param runs: number of test runs
        :returns: url to access the test details
        """
        start_url = (self.wpt_url + '/runtest.php?url=' + test_url +
                     '&k=' + self.api_key + '&location=' + test_location +
                     '&runs=' + str(runs) + '&f=json')
        response = requests.post(start_url)
        test = response.json()
        return test['data']['jsonUrl']

    def get_test_details(self, test_url):
        """Gets test results in json format

        :param test_url: the url pointing to json test results
        :returns: test details json
        """
        response = requests.get(test_url)
        return response.json()

    def wait_for_test_status(self, test_url, status='COMPLETE',
                             retry_timeout=6000, retry_interval=10):

        """Waits for the wpt test to be completed.

        :param test_url: the url pointing to json test results
        :param status: expected status from WPT server
            200 (COMPLETE) indicates test is completed.
            1XX means the test is still in progress.
            And 4XX indicates some error.
        """
        current_status = ''
        start_time = int(time.time())
        stop_time = start_time + retry_timeout
        if status == 'COMPLETE':
            status_code = 200
        while current_status != status:
            time.sleep(retry_interval)
            test_details = self.get_test_details(test_url=test_url)
            current_status = test_details['statusCode']
            print('status', test_details)
            if (current_status == status_code):
                return
            current_time = int(time.time())
            if current_time > stop_time:
                return


"""
Typhon Plotting Interface
"""
############
# Standard #
############
import random
import os.path
import logging
from functools import partial

###############
# Third Party #
###############
from pydm.PyQt import uic
from pydm.PyQt.QtGui import QApplication, QWidget, QBrush
from pydm.PyQt.QtCore import Qt, pyqtSlot

##########
# Module #
##########
from .utils import (ui_dir, channel_from_signal, clean_attr, random_color,
                    clean_name)

logger = logging.getLogger(__name__)

class ChannelDisplay(QWidget):
    """
    Display for a PyDM PlotCurveItem

    Parameters
    ----------
    name : str
        Alias of channel

    color: QColor
        Color of curve in plot
    """
    def __init__(self, name, color, parent=None):
        super().__init__(parent=parent)
        self.ui = uic.loadUi(os.path.join(ui_dir, 'plotitem.ui'), self)
        self.ui.name.setText(name)
        self.ui.color.brush = QBrush(color, Qt.SolidPattern)

class TyphonTimePlot(QWidget):
    """
    Generalized widget for plotting Ophyd signals

    This widget initializes a blank ``PyDMTimePlot`` with a small interface to
    configure the plotted signals. The list of signals available to the
    operator can be controlled via :meth:`.add_available_signal`. Shortcuts can
    be added to the plot itself via :meth:`.add_curve` or the
    :meth:`.creation_requested` ``pyqtSlot`` which takes the current status of
    the requested combinations

    Parameters
    ----------
    parent: QWidget
    """
    def __init__(self, parent=None):
        super().__init__(parent=parent)
        # Load generated user interface file
        self.ui = uic.loadUi(os.path.join(ui_dir, 'plot.ui'), self)
        # Connect create button
        self.create_button.clicked.connect(self.creation_requested)
        # Data structure for name / ChannelDisplay mapping
        self.channel_map = dict()

    def add_available_signal(self, signal, name):
        """
        Add an Ophyd signal to the list of available channels

        If the Signal is not an EPICS Signal object you are responsible for
        registering this yourself, if not already done.

        Parameters
        ----------
        signal: ophyd.Signal

        name: str
            Alias for signal to display in QComboBox
        """
        self.ui.signal_combo.addItem(name, channel_from_signal(signal))

    def add_curve(self, channel, name=None, color=None):
        """
        Add a curve to the plot

        Parameters
        ----------
        channel : str
            PyDMChannel address

        name : str, optional
            Name of TimePlotCurveItem. If None is given, the ``channel`` is
            used.

        color: QColor, optional
            Color to display line in plot. If None is given, a QColor will be
            chosen randomly
        """
        name = name or channel
        # Do not allow duplicate channels
        if name in self.channel_map:
            logger.error("%r already has been added to the plot", name)
            return
        logger.debug("Adding %s to plot ...", channel)
        # Create a random color if None is supplied
        if not color:
            color = random_color()
        # Add to plot
        self.ui.timeplot.addYChannel(y_channel=channel, color=color,
                                     name=name, lineStyle=Qt.SolidLine,
                                     lineWidth=2, symbol=None)
        # Add a display to the LiveChannel display
        ch_display = ChannelDisplay(name, color)
        self.ui.live_channels.layout().insertWidget(0, ch_display)
        self.channel_map[name] = ch_display
        # Connect the removal button to the slot
        ch_display.remove_button.clicked.connect(partial(self.remove_curve,
                                                         name))
        # Establish connections for new instance
        pydm_app = QApplication.instance()
        pydm_app.establish_widget_connections(self)
        # Select new random color
        self.ui.color.brush = QBrush(random_color(), Qt.SolidPattern)

    @pyqtSlot()
    def remove_curve(self, name):
        """
        Remove a curve from the plot

        Parameters
        ----------
        name: str
            Name of the curve to remove. This should match the name given
            during the call of :meth:`.add_curve`
        """
        if name in self.channel_map:
            logger.debug("Removing %s from DeviceTimePlot ...", name)
            self.ui.timeplot.removeCurveWithName(name)
            # Remove ChannelDisplay
            ch_display = self.channel_map.pop(name)
            self.ui.live_channels.layout().removeWidget(ch_display)
            # Destroy ChannelDisplay
            ch_display.deleteLater()
        else:
            logger.error("Curve %r was not found in DeviceTimePlot", name)

    @pyqtSlot()
    def creation_requested(self):
        """
        Reaction to ``create_button`` press

        Observes the state of the selection widgets and makes the appropriate
        call to :meth:`.add_curve`
        """
        # Find requested channel
        name = self.ui.signal_combo.currentText()
        idx = self.ui.signal_combo.currentIndex()
        channel = self.ui.signal_combo.itemData(idx)
        # Add to the plot
        self.add_curve(channel, name=name, color=self.ui.color.brush.color())

class DeviceTimePlot(TyphonTimePlot):
    """
    TyphonTimePlot auto-populated by a Device

    The device is inspected for all scalar signals and are made available for
    the operator to select. Device hints are automatically added to the plot.

    Parameters
    ----------
    device : ophyd.Device

    parent: QWidget, object
    """
    def __init__(self, device, parent=None):
        super().__init__(parent=parent)
        self.device = device
        # Seed so that our colors are consistent
        random.seed(54321343)
        # Sort through components
        devices = [self.device] + [getattr(device, sub)
                                   for sub in device._sub_devices]
        for device in devices:
            for component in device.component_names:
                # Find all signals
                if component not in device._sub_devices:
                    try:
                        sig = getattr(device, component)
                        # Only include scalars
                        if sig.describe()[sig.name]['dtype'] in ('integer',
                                                                 'number'):
                            # Make component name
                            name = clean_attr(component)
                            if device != self.device:
                                name = ' '.join((clean_name(device), name))
                            # Add to list of available signals
                            self.add_available_signal(sig, name)
                            # Automatically plot if in Device hints
                            if sig.name in device.hints.get('fields', []):
                                self.add_curve(channel_from_signal(sig),
                                               name=name)
                    except Exception as exc:
                        logger.exception("Unable to add %s to "
                                         "plot-able signals",
                                         component)


from sqlalchemy.orm import sessionmaker
from typing import List
from database.Model.StockMention import StockMention, Stocks

from sqlalchemy import *
from pathlib import Path

from database.Model import Base

DATABASE_NAME = '../res/RedditStocks.db'

class DatabaseController:

    def __init__(self, database_path: str = DATABASE_NAME):
        """
        Creates a engine and a Session with the Database
        """
        create_database_if_not_exists(database_path)
        self.engine = create_engine(f'sqlite:///{database_path}')
        self.Session = sessionmaker(self.engine)

    def add_stock_mention_to_database(self, stock_mention: StockMention) -> None:
        """
        Adds a reddit post to the 'RedditStocks.db' database with all stocks mentioned.
        :param stock_mention: Model with data to be stored in Database
        """
        conn = self.engine.connect()
        with self.Session(bind=conn) as session:
            session.add(stock_mention)
            session.commit()

    def add_stocks_mention_to_database(self, stock_mentions) -> None:
        """
        Adds a list of reddit post to the 'RedditStocks.db' database with all stocks mentioned.
        :param stock_mentions: List if Models with data to be stored in Database
        :return:
        """
        conn = self.engine.connect()
        with self.Session(bind=conn) as session:
            for stock_mention in stock_mentions:
                session.add(stock_mention)
            session.commit()

    def get_stock_mention_from_database(self, stock_mention_id: int) -> List:
        """
        Gets a list tuples which contains the stock mentioned with the specific stock_mention_id
        and a stocktoken that was mentioned in the post
        :param stock_mention_id: the id of the mentioned stock post
        :return: a list of tuples (StockMention, stock_token)
        """
        conn = self.engine.connect()
        with self.Session(bind=conn) as session:
            result = session.query(StockMention, Stocks.stock_token)\
                .filter(StockMention.stock_mention_id == Stocks.stock_mention_id)\
                .filter(StockMention.stock_mention_id == stock_mention_id)\
                .all()
            return [row._data for row in result]

    def get_stocks_mention_from_database(self) -> List:
        """
        Gets a list tuples which contains all stocks mentioned out of the database
        and the stocktoken that was mentioned in the post
        :return: a list of tuples (StockMention, stock_token)
        """
        conn = self.engine.connect()
        with self.Session(bind=conn) as session:
            result = session.query(StockMention, Stocks.stock_token)\
                .filter(StockMention.stock_mention_id == Stocks.stock_mention_id)\
                .all()
            return [row._data for row in result]

def create_database_if_not_exists(database_path: str) -> None:
    """
    Creates the database if not exists
    """
    database = Path(database_path)
    if not database.is_file():
        engine = create_engine(f'sqlite:///{database_path}')
        Base.metadata.create_all(bind=engine)
    else:
        print('database already exists')

if __name__ == '__main__':
    database_controller = DatabaseController()
    stock_mention = database_controller.get_stock_mention_from_database(2)
    print(stock_mention)
    stock_mentions = database_controller.get_stocks_mention_from_database()
    print(stock_mentions)


#@HEADER
# ************************************************************************
#
#                        Torchbraid v. 0.1
#
# Copyright 2020 National Technology & Engineering Solutions of Sandia, LLC
# (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.
# Government retains certain rights in this software.
#
# Torchbraid is licensed under 3-clause BSD terms of use:
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. Neither the name National Technology & Engineering Solutions of Sandia,
# LLC nor the names of the contributors may be used to endorse or promote
# products derived from this software without specific prior written permission.
#
# Questions? Contact Eric C. Cyr (eccyr@sandia.gov)
#
# ************************************************************************
#@HEADER

import torch
import torch.nn as nn
import torch.nn.functional as F
import unittest
import sys
import numpy as np
import statistics as stats

import torchbraid

import faulthandler
faulthandler.enable()

from mpi4py import MPI

class LSTMBlock(nn.Module):
  def __init__(self, input_size, hidden_size, num_layers):
    super(LSTMBlock, self).__init__()
    self.hidden_size = hidden_size
    self.num_layers = num_layers

    torch.manual_seed(20)
    lstm_cells = num_layers*[None]
    lstm_cells[0] = nn.LSTMCell(input_size, hidden_size)
    for i in range(num_layers-1):
      lstm_cells[i+1] = nn.LSTMCell(hidden_size, hidden_size)

    self.lstm_cells = nn.ModuleList(lstm_cells)

  def forward(self, x, h_prev, c_prev):
    h_cur = h_prev[0]
    c_cur = c_prev[0]
    x_cur = x

    hn = self.num_layers*[None]
    cn = self.num_layers*[None]
    for i in range(self.num_layers):
      hn[i], cn[i] = self.lstm_cells[i](x_cur, (h_prev[i], c_prev[i]))
      x_cur = hn[i]

    return (torch.stack(hn), torch.stack(cn))

def RNN_build_block_with_dim(input_size, hidden_size, num_layers):
  b = LSTMBlock(input_size, hidden_size, num_layers) # channels = hidden_size
  return b

def preprocess_input_data_serial_test(num_blocks, num_batch, batch_size, channels, sequence_length, input_size):
  torch.manual_seed(20)
  x = torch.randn(num_batch,batch_size,channels,sequence_length,input_size)

  data_all = []
  x_block_all = []
  for i in range(len(x)):
    image = x[i].reshape(-1, sequence_length, input_size)
    images_split = torch.chunk(image, num_blocks, dim=1)
    seq_split = []
    for blk in images_split:
      seq_split.append(blk)
    x_block_all.append(seq_split)
    data_all.append(image)

  return data_all, x_block_all

def preprocess_distribute_input_data_parallel(rank,num_procs,num_batch,batch_size,channels,sequence_length,input_size,comm):
  if rank == 0:
    torch.manual_seed(20)
    x = torch.randn(num_batch,batch_size,channels,sequence_length,input_size)

    # x_block_all[total_images][total_blocks]
    x_block_all = []
    for i in range(len(x)):
      image = x[i].reshape(-1,sequence_length,input_size)
      data_split = torch.chunk(image, num_procs, dim=1)
      seq_split = []
      for blk in data_split:
        seq_split.append(blk)
      x_block_all.append(seq_split)

    x_block = []
    for image_id in range(len(x_block_all)):
      x_block.append(x_block_all[image_id][rank])

    for block_id in range(1,num_procs):
      x_block_tmp = []
      for image_id in range(len(x_block_all)):
        x_block_tmp.append(x_block_all[image_id][block_id])
      comm.send(x_block_tmp,dest=block_id,tag=20)

    return x_block

  else:
    x_block = comm.recv(source=0,tag=20)

    return x_block

class TestRNNLayerParallel(unittest.TestCase):
  def test_forward_exact(self):
    self.forwardProp(max_levels=1,max_iters=1)

  def test_forward_approx(self):
    self.forwardProp(max_levels=3,max_iters=20)

  def test_backward_exact(self):
    self.backwardProp()

  def test_backward_approx(self):
    self.backwardProp(max_levels=3,max_iters=12,sequence_length=27,tol=1e-5)

  def test_backward_lstm(self):
    if MPI.COMM_WORLD.Get_size()==1:
      self.backwardProp_lstm()

  def copyParameterGradToRoot(self,m):
    comm     = m.getMPIComm()
    my_rank  = m.getMPIComm().Get_rank()
    num_proc = m.getMPIComm().Get_size()

    params = [p.grad for p in list(m.parameters())]

    if len(params)==0:
      return params

    if my_rank==0:
      for i in range(1,num_proc):
        remote_p = comm.recv(source=i,tag=77)
        params.extend(remote_p)

      return params
    else:
      comm.send(params,dest=0,tag=77)
      return None
  # end copyParametersToRoot

  def forwardProp(self,
                  max_levels = 1, # for testing parallel rnn
                  max_iters = 1, # for testing parallel rnn
                  sequence_length = 28, # total number of time steps for each sequence
                  input_size = 28, # input size for each time step in a sequence
                  hidden_size = 20,
                  num_layers = 2,
                  batch_size = 1):

    comm      = MPI.COMM_WORLD
    num_procs = comm.Get_size()
    my_rank   = comm.Get_rank()

    Tf              = 2.0
    channels        = 1
    images          = 10
    image_size      = 28
    print_level     = 0
    nrelax          = 1
    cfactor         = 2
    num_batch = int(images / batch_size)

    if my_rank==0:
      with torch.no_grad():

        torch.manual_seed(20)
        serial_rnn = nn.LSTM(input_size, hidden_size, num_layers,batch_first=True)
        num_blocks = 2 # equivalent to the num_procs variable used for parallel implementation
        image_all, x_block_all = preprocess_input_data_serial_test(num_blocks,num_batch,batch_size,channels,sequence_length,input_size)

        # for i in range(len(image_all)):
        for i in range(1):

          y_serial_hn = torch.zeros(num_layers, image_all[i].size(0), hidden_size)
          y_serial_cn = torch.zeros(num_layers, image_all[i].size(0), hidden_size)

          _, (y_serial_hn, y_serial_cn) = serial_rnn(image_all[i],(y_serial_hn,y_serial_cn))

    # compute serial solution

    # wait for serial processor
    comm.barrier()

    basic_block_parallel = lambda: RNN_build_block_with_dim(input_size, hidden_size, num_layers)
    num_procs = comm.Get_size()

    # preprocess and distribute input data
    ###########################################
    x_block = preprocess_distribute_input_data_parallel(my_rank,num_procs,num_batch,batch_size,channels,sequence_length,input_size,comm)

    num_steps = x_block[0].shape[1]
    # RNN_parallel.py -> RNN_Parallel() class
    parallel_rnn = torchbraid.RNN_Parallel(comm,basic_block_parallel,num_steps,hidden_size,num_layers,Tf,max_levels=max_levels,max_iters=max_iters)

    parallel_rnn.setPrintLevel(print_level)
    parallel_rnn.setSkipDowncycle(True)
    parallel_rnn.setCFactor(cfactor)
    parallel_rnn.setNumRelax(nrelax)

    # for i in range(len(x_block)):
    for i in range(1):

      y_parallel_hn,y_parallel_cn = parallel_rnn(x_block[i])

      comm.barrier()

      # send the final inference step to root
      if my_rank == comm.Get_size()-1:
        comm.send(y_parallel_hn,0)
        comm.send(y_parallel_cn,0)

      if my_rank==0:
        # recieve the final inference step
        parallel_hn = comm.recv(source=comm.Get_size()-1)
        parallel_cn = comm.recv(source=comm.Get_size()-1)
        print('cn values = ',torch.norm(y_serial_cn-parallel_cn).item()/torch.norm(y_serial_cn).item())
        print('hn values = ',torch.norm(y_serial_hn-parallel_hn).item()/torch.norm(y_serial_hn).item())
        self.assertTrue(torch.norm(y_serial_cn-parallel_cn).item()/torch.norm(y_serial_cn).item()<1e-6,'check cn')
        self.assertTrue(torch.norm(y_serial_hn-parallel_hn).item()/torch.norm(y_serial_hn).item()<1e-6,'check hn')
  # forwardProp

  def backwardProp_lstm(self,sequence_length = 2, # total number of time steps for each sequence
                        input_size = 28, # input size for each time step in a sequence
                        hidden_size = 20,
                        num_layers = 2,
                        batch_size = 1):
    """
    The goal of this test is simply to ensure that the behavior of pytorch is the same with
    regard to LSTM models.
    """
    comm      = MPI.COMM_WORLD
    num_procs = comm.Get_size()
    my_rank   = comm.Get_rank()

    Tf              = 2.0
    channels        = 1
    images          = 10
    image_size      = 28
    print_level     = 0
    nrelax          = 1
    cfactor         = 2
    num_batch = int(images / batch_size)

    image_all, x_block_all = preprocess_input_data_serial_test(2,num_batch,batch_size,channels,sequence_length,input_size)
    i = 0
    x_a = image_all[i].detach().clone()
    x_b = image_all[i].detach().clone()

    # forward
    ###########################################
    x_a.requires_grad = False
    x_b.requires_grad = False

    h0_a = torch.zeros(num_layers, x_a.size(0), hidden_size,requires_grad=False)
    c0_a = torch.zeros(num_layers, x_a.size(0), hidden_size,requires_grad=False)

    # a simulation
    ######################################3
    serial_rnn_a = LSTMBlock(input_size, hidden_size, num_layers)

    h_a = h0_a
    c_a = c0_a
    hs = (sequence_length+1)*[None]
    cs = (sequence_length+1)*[None]
    hs[0] = h_a
    cs[0] = c_a
    for i in range(sequence_length):
      with torch.no_grad():
        h_a,c_a = serial_rnn_a(x_a[:,i,:],h_a,c_a)

      hs[i+1] = h_a
      cs[i+1] = c_a

    wh_a = torch.zeros(h_a.shape)
    wc_a = torch.zeros(c_a.shape)
    torch.manual_seed(20)
    wh_a[-1,:,:] = torch.randn(h_a[-1,:,:].shape)

    wh_a_perm = wh_a

    for i in range(sequence_length):
      j = sequence_length-i-1
      hi = hs[j]
      ci = cs[j]

      hi.requires_grad = True
      ci.requires_grad = True

      with torch.enable_grad():
        ho,co = serial_rnn_a(x_a[:,j,:],hi,ci)

      ho.backward(wh_a,retain_graph=True)
      co.backward(wc_a)

      wh_a = hi.grad.detach().clone()
      wc_a = ci.grad.detach().clone()
    # end for i

    # b simulation
    ######################################3
    torch.manual_seed(20)
    serial_rnn_b = torch.nn.LSTM(input_size, hidden_size, num_layers,batch_first=True)

    h0_b = torch.zeros(num_layers, x_b.size(0), hidden_size,requires_grad=True)
    c0_b = torch.zeros(num_layers, x_b.size(0), hidden_size,requires_grad=True)

    y_b, (h_b,c_b) = serial_rnn_b(x_b,(h0_b,c0_b))

    #print('h error = ',torch.norm(h_b-h_a).item(),torch.norm(h_b).item(),h_b.shape)
    #print('c error = ',torch.norm(c_b-c_a).item(),torch.norm(c_b).item(),c_b.shape)

    self.assertTrue(torch.norm(h_b-h_a).item()<1e-6)
    self.assertTrue(torch.norm(c_b-c_a).item()<1e-6)

    w_b = torch.zeros(y_b.shape)
    w_b[:,-1,:] = wh_a_perm[-1,:,:]

    y_b.backward(w_b)

    #print('h grad error = ',torch.norm(h0_b.grad-wh_a).item(),torch.norm(h0_b.grad).item())
    #print('c grad error = ',torch.norm(c0_b.grad-wc_a).item(),torch.norm(c0_b.grad).item())
    self.assertTrue(torch.norm(h0_b.grad-wh_a).item()<1e-6)
    self.assertTrue(torch.norm(c0_b.grad-wc_a).item()<1e-6)

    #print('lengths = ',len(list(serial_rnn_b.parameters())),len(list(serial_rnn_a.parameters())))
    for pa,pb in zip(serial_rnn_a.parameters(),serial_rnn_b.parameters()):
    #  print('grad error = ',torch.norm(pa.grad-pb.grad).item(),torch.norm(pb.grad).item())
      self.assertTrue(torch.norm(pa.grad-pb.grad).item()<1e-6)
  # end lstm

  def backwardProp(self,
                   max_levels = 1, # for testing parallel rnn
                   max_iters = 1, # for testing parallel rnn
                   sequence_length = 6, # total number of time steps for each sequence
                   input_size = 28, # input size for each time step in a sequence
                   hidden_size = 20,
                   num_layers = 1,
                   batch_size = 1,
                   tol=1e-6):

    comm      = MPI.COMM_WORLD
    num_procs = comm.Get_size()
    my_rank   = comm.Get_rank()

    Tf              = 2.0
    channels        = 1
    images          = 10
    image_size      = 28
    print_level     = 0
    nrelax          = 1
    cfactor         = 2
    num_batch = int(images / batch_size)

    # wait for serial processor
    comm.barrier()

    num_procs = comm.Get_size()

    # preprocess and distribute input data
    x_block = preprocess_distribute_input_data_parallel(my_rank,num_procs,num_batch,batch_size,channels,sequence_length,input_size,comm)

    num_steps = x_block[0].shape[1]

    basic_block_parallel = lambda: RNN_build_block_with_dim(input_size, hidden_size, num_layers)
    parallel_rnn = torchbraid.RNN_Parallel(comm,basic_block_parallel,num_steps,hidden_size,num_layers,Tf,max_levels=max_levels,max_iters=max_iters)

    parallel_rnn.setPrintLevel(print_level)
    parallel_rnn.setSkipDowncycle(True)
    parallel_rnn.setCFactor(cfactor)
    parallel_rnn.setNumRelax(nrelax)

    h_0 = torch.zeros(num_layers, x_block[0].size(0), hidden_size,requires_grad=True)
    c_0 = torch.zeros(num_layers, x_block[0].size(0), hidden_size,requires_grad=True)

    with torch.enable_grad():
      y_parallel_hn,y_parallel_cn = parallel_rnn(x_block[0],(h_0,c_0))

    comm.barrier()

    w_h = torch.zeros(y_parallel_hn.shape)
    w_c = torch.zeros(y_parallel_hn.shape)
    torch.manual_seed(20)
    w_h[-1,:,:] = torch.randn(y_parallel_hn[-1,:,:].shape)

    y_parallel_hn.backward(w_h)

    # compute serial solution
    #############################################

    if my_rank==0:

      torch.manual_seed(20)
      serial_rnn = nn.LSTM(input_size, hidden_size, num_layers,batch_first=True)
      image_all, x_block_all = preprocess_input_data_serial_test(num_procs,num_batch,batch_size,channels,sequence_length,input_size)

      i = 0
      y_serial_hn_0 = torch.zeros(num_layers, image_all[i].size(0), hidden_size,requires_grad=True)
      y_serial_cn_0 = torch.zeros(num_layers, image_all[i].size(0), hidden_size,requires_grad=True)

      with torch.enable_grad():
        q, (y_serial_hn, y_serial_cn) = serial_rnn(image_all[i],(y_serial_hn_0,y_serial_cn_0))

      w_q = torch.zeros(q.shape)
      w_q[:,-1,:] = w_h[-1,:,:].detach().clone()

      q.backward(w_q)
    # end if my_rank

    # now check the answers
    #############################################

    # send the final inference step to root
    if my_rank == comm.Get_size()-1:
      comm.send(y_parallel_hn,0)
      comm.send(y_parallel_cn,0)

    if my_rank==0:
      # recieve the final inference step
      parallel_hn = comm.recv(source=comm.Get_size()-1)
      parallel_cn = comm.recv(source=comm.Get_size()-1)

      print(torch.norm(y_serial_cn-parallel_cn).item()/torch.norm(y_serial_cn).item(),'forward cn')
      print(torch.norm(y_serial_hn-parallel_hn).item()/torch.norm(y_serial_hn).item(),'forward hn')

      self.assertTrue(torch.norm(y_serial_cn-parallel_cn)/torch.norm(y_serial_cn)<tol,'cn value')
      self.assertTrue(torch.norm(y_serial_hn-parallel_hn)/torch.norm(y_serial_hn)<tol,'hn value')

      print(torch.norm(h_0.grad-y_serial_hn_0.grad).item(),'back soln hn')
      print(torch.norm(c_0.grad-y_serial_cn_0.grad).item(),'back soln cn')
      self.assertTrue(torch.norm(h_0.grad-y_serial_hn_0.grad).item()<tol)
      self.assertTrue(torch.norm(c_0.grad-y_serial_cn_0.grad).item()<tol)

      root_grads = [p.grad for p in serial_rnn.parameters()]
    else:
      root_grads = None

    ref_grads = comm.bcast(root_grads,root=0)
    for pa_grad,pb in zip(ref_grads,parallel_rnn.parameters()):
      print(my_rank,torch.norm(pa_grad-pb.grad).item()/torch.norm(pa_grad).item(),'param grad')
      self.assertTrue(torch.norm(pa_grad-pb.grad).item()/torch.norm(pa_grad).item()<tol,'param grad')
  # forwardProp

if __name__ == '__main__':
  unittest.main()


import operator
import types
import uuid
import warnings
from collections.abc import Iterator
try:
    from cytoolz import curry, concat, unique, merge
except ImportError:
    from toolz import curry, concat, unique, merge
from . import config, threaded
from .base import is_dask_collection, dont_optimize, DaskMethodsMixin
from .base import tokenize as _tokenize
from .compatibility import is_dataclass, dataclass_fields
from .core import quote
from .context import globalmethod
from .optimization import cull
from .utils import funcname, methodcaller, OperatorMethodMixin, ensure_dict, apply
from .highlevelgraph import HighLevelGraph
__all__ = ["Delayed", "delayed"]
def unzip(ls, nout):

    out = list(zip(*ls))
    if not out:
        out = [()] * nout
    return out
def finalize(collection):
    assert is_dask_collection(collection)
    name = "finalize-" + tokenize(collection)
    keys = collection.__dask_keys__()
    finalize, args = collection.__dask_postcompute__()
    layer = {name: (finalize, keys) + args}
    graph = HighLevelGraph.from_collections(name, layer, dependencies=[collection])
    return Delayed(name, graph)
def unpack_collections(expr):

    if isinstance(expr, Delayed):
        return expr._key, (expr,)
    if is_dask_collection(expr):
        finalized = finalize(expr)
        return finalized._key, (finalized,)
    if isinstance(expr, Iterator):
        expr = tuple(expr)
    typ = type(expr)
    if typ in (list, tuple, set):
        args, collections = unzip((unpack_collections(e) for e in expr), 2)
        args = list(args)
        collections = tuple(unique(concat(collections), key=id))
        # Ensure output type matches input type
        if typ is not list:
            args = (typ, args)
        return args, collections
    if typ is dict:
        args, collections = unpack_collections([[k, v] for k, v in expr.items()])
        return (dict, args), collections
    if typ is slice:
        args, collections = unpack_collections([expr.start, expr.stop, expr.step])
        return (slice,) + tuple(args), collections
    if is_dataclass(expr):
        args, collections = unpack_collections(
            [[f.name, getattr(expr, f.name)] for f in dataclass_fields(expr)]
        )
        return (apply, typ, (), (dict, args)), collections
    return expr, ()
def to_task_dask(expr):

    warnings.warn(
        "The dask.delayed.to_dask_dask function has been "
        "Deprecated in favor of unpack_collections",
        stacklevel=2,
    )
    if isinstance(expr, Delayed):
        return expr.key, expr.dask
    if is_dask_collection(expr):
        name = "finalize-" + tokenize(expr, pure=True)
        keys = expr.__dask_keys__()
        opt = getattr(expr, "__dask_optimize__", dont_optimize)
        finalize, args = expr.__dask_postcompute__()
        dsk = {name: (finalize, keys) + args}
        dsk.update(opt(expr.__dask_graph__(), keys))
        return name, dsk
    if isinstance(expr, Iterator):
        expr = list(expr)
    typ = type(expr)
    if typ in (list, tuple, set):
        args, dasks = unzip((to_task_dask(e) for e in expr), 2)
        args = list(args)
        dsk = merge(dasks)
        # Ensure output type matches input type
        return (args, dsk) if typ is list else ((typ, args), dsk)
    if typ is dict:
        args, dsk = to_task_dask([[k, v] for k, v in expr.items()])
        return (dict, args), dsk
    if is_dataclass(expr):
        args, dsk = to_task_dask(
            [[f.name, getattr(expr, f.name)] for f in dataclass_fields(expr)]
        )
        return (apply, typ, (), (dict, args)), dsk
    if typ is slice:
        args, dsk = to_task_dask([expr.start, expr.stop, expr.step])
        return (slice,) + tuple(args), dsk
    return expr, {}
def tokenize(*args, **kwargs):

    pure = kwargs.pop("pure", None)
    if pure is None:
        pure = config.get("delayed_pure", False)
    if pure:
        return _tokenize(*args, **kwargs)
    else:
        return str(uuid.uuid4())
@curry
def delayed(obj, name=None, pure=None, nout=None, traverse=True):

    if isinstance(obj, Delayed):
        return obj
    if is_dask_collection(obj) or traverse:
        task, collections = unpack_collections(obj)
    else:
        task = quote(obj)
        collections = set()
    if task is obj:
        if not (nout is None or (type(nout) is int and nout >= 0)):
            raise ValueError(
                "nout must be None or a non-negative integer, got %s" % nout
            )
        if not name:
            try:
                prefix = obj.__name__
            except AttributeError:
                prefix = type(obj).__name__
            token = tokenize(obj, nout, pure=pure)
            name = "%s-%s" % (prefix, token)
        return DelayedLeaf(obj, name, pure=pure, nout=nout)
    else:
        if not name:
            name = "%s-%s" % (type(obj).__name__, tokenize(task, pure=pure))
        layer = {name: task}
        graph = HighLevelGraph.from_collections(name, layer, dependencies=collections)
        return Delayed(name, graph)
def right(method):

    def _inner(self, other):
        return method(other, self)
    return _inner
def optimize(dsk, keys, **kwargs):
    dsk = ensure_dict(dsk)
    dsk2, _ = cull(dsk, keys)
    return dsk2
def rebuild(dsk, key, length):
    return Delayed(key, dsk, length)
class Delayed(DaskMethodsMixin, OperatorMethodMixin):

    __slots__ = ("_key", "dask", "_length")
    def __init__(self, key, dsk, length=None):
        self._key = key
        self.dask = dsk
        self._length = length
    def __dask_graph__(self):
        return self.dask
    def __dask_keys__(self):
        return [self.key]
    def __dask_layers__(self):
        return (self.key,)
    def __dask_tokenize__(self):
        return self.key
    __dask_scheduler__ = staticmethod(threaded.get)
    __dask_optimize__ = globalmethod(optimize, key="delayed_optimize")
    def __dask_postcompute__(self):
        return single_key, ()
    def __dask_postpersist__(self):
        return rebuild, (self._key, getattr(self, "_length", None))
    def __getstate__(self):
        return tuple(getattr(self, i) for i in self.__slots__)
    def __setstate__(self, state):
        for k, v in zip(self.__slots__, state):
            setattr(self, k, v)
    @property
    def key(self):
        return self._key
    def __repr__(self):
        return "Delayed({0})".format(repr(self.key))
    def __hash__(self):
        return hash(self.key)
    def __dir__(self):
        return dir(type(self))
    def __getattr__(self, attr):
        if attr.startswith("_"):
            raise AttributeError("Attribute {0} not found".format(attr))
        return DelayedAttr(self, attr)
    def __setattr__(self, attr, val):
        if attr in self.__slots__:
            object.__setattr__(self, attr, val)
        else:
            raise TypeError("Delayed objects are immutable")
    def __setitem__(self, index, val):
        raise TypeError("Delayed objects are immutable")
    def __iter__(self):
        if getattr(self, "_length", None) is None:
            raise TypeError("Delayed objects of unspecified length are not iterable")
        for i in range(self._length):
            yield self[i]
    def __len__(self):
        if getattr(self, "_length", None) is None:
            raise TypeError("Delayed objects of unspecified length have no len()")
        return self._length
    def __call__(self, *args, **kwargs):
        pure = kwargs.pop("pure", None)
        name = kwargs.pop("dask_key_name", None)
        func = delayed(apply, pure=pure)
        if name is not None:
            return func(self, args, kwargs, dask_key_name=name)
        return func(self, args, kwargs)
    def __bool__(self):
        raise TypeError("Truth of Delayed objects is not supported")
    __nonzero__ = __bool__
    def __get__(self, instance, cls):
        if instance is None:
            return self
        return types.MethodType(self, instance)
    @classmethod
    def _get_binary_operator(cls, op, inv=False):
        method = delayed(right(op) if inv else op, pure=True)
        return lambda *args, **kwargs: method(*args, **kwargs)
    _get_unary_operator = _get_binary_operator
def call_function(func, func_token, args, kwargs, pure=None, nout=None):
    dask_key_name = kwargs.pop("dask_key_name", None)
    pure = kwargs.pop("pure", pure)
    if dask_key_name is None:
        name = "%s-%s" % (
            funcname(func),
            tokenize(func_token, *args, pure=pure, **kwargs),
        )
    else:
        name = dask_key_name
    args2, collections = unzip(map(unpack_collections, args), 2)
    collections = list(concat(collections))
    if kwargs:
        dask_kwargs, collections2 = unpack_collections(kwargs)
        collections.extend(collections2)
        task = (apply, func, list(args2), dask_kwargs)
    else:
        task = (func,) + args2
    graph = HighLevelGraph.from_collections(
        name, {name: task}, dependencies=collections
    )
    nout = nout if nout is not None else None
    return Delayed(name, graph, length=nout)
class DelayedLeaf(Delayed):
    __slots__ = ("_obj", "_key", "_pure", "_nout")
    def __init__(self, obj, key, pure=None, nout=None):
        self._obj = obj
        self._key = key
        self._pure = pure
        self._nout = nout
    @property
    def dask(self):
        return HighLevelGraph.from_collections(
            self._key, {self._key: self._obj}, dependencies=()
        )
    def __call__(self, *args, **kwargs):
        return call_function(
            self._obj, self._key, args, kwargs, pure=self._pure, nout=self._nout
        )
class DelayedAttr(Delayed):
    __slots__ = ("_obj", "_attr", "_key")
    def __init__(self, obj, attr):
        self._obj = obj
        self._attr = attr
        self._key = "getattr-%s" % tokenize(obj, attr, pure=True)
    def __getattr__(self, attr):
        # Calling np.dtype(dask.delayed(...)) used to result in a segfault, as
        # numpy recursively tries to get `dtype` from the object. This is
        # likely a bug in numpy. For now, we can do a dumb for if
        # `x.dtype().dtype()` is called (which shouldn't ever show up in real
        # code). See https://github.com/dask/dask/pull/4374#issuecomment-454381465
        if attr == "dtype" and self._attr == "dtype":
            raise AttributeError("Attribute %s not found" % attr)
        return super(DelayedAttr, self).__getattr__(attr)
    @property
    def dask(self):
        layer = {self._key: (getattr, self._obj._key, self._attr)}
        return HighLevelGraph.from_collections(
            self._key, layer, dependencies=[self._obj]
        )
    def __call__(self, *args, **kwargs):
        return call_function(
            methodcaller(self._attr), self._attr, (self._obj,) + args, kwargs
        )
for op in [
    operator.abs,
    operator.neg,
    operator.pos,
    operator.invert,
    operator.add,
    operator.sub,
    operator.mul,
    operator.floordiv,
    operator.truediv,
    operator.mod,
    operator.pow,
    operator.and_,
    operator.or_,
    operator.xor,
    operator.lshift,
    operator.rshift,
    operator.eq,
    operator.ge,
    operator.gt,
    operator.ne,
    operator.le,
    operator.lt,
    operator.getitem,
]:
    Delayed._bind_operator(op)
try:
    Delayed._bind_operator(operator.matmul)
except AttributeError:
    pass
def single_key(seq):

    return seq[0]


import argparse
import numpy as np
import torch
import os
import sys
import time
import random
import IPython

from rlmamr.my_env.box_pushing_MA import BoxPushing_harder as BP_MA
from rlmamr.my_env.osd_ma_single_room import ObjSearchDelivery_v4 as OSD_S_4

from rlmamr.MA_cen_condi_ddrqn.team_cen_condi import Team_RNN
from rlmamr.MA_cen_condi_ddrqn.utils.memory_cen_condi import ReplayMemory_rand, ReplayMemory_epi
from rlmamr.MA_cen_condi_ddrqn.utils.utils import save_check_point
from rlmamr.MA_cen_condi_ddrqn.learning_methods_cen_condi import QLearn_squ_cen_condi_0

from IPython.core.debugger import set_trace

ENVIRONMENTS = {
        'BP_MA': BP_MA,
        'OSD_S_4': OSD_S_4
        }

QLearns = [QLearn_squ_cen_condi_0]

def train(env_name, grid_dim, obs_one_hot, target_flick_prob, agent_trans_noise, target_rand_move,
          h_speed_ps, tb_m_speed, tb_m_noisy, f_p_obj_tc, f_l_obj_tc, d_pen,
          env_terminate_step, n_env, n_agent, cen_mode, total_epi, replay_buffer_size, sample_epi,
          dynamic_h, init_h, end_h, h_stable_at, eps_l_d, eps_l_d_steps, eps_end, eps_e_d, softmax_explore, h_explore, db_step,
          optim, l_rate, discount, huber_l, g_clip, g_clip_v, g_clip_norm, g_clip_max_norm,
          start_train, train_freq, target_update_freq, trace_len, sub_trace_len, batch_size,
          sort_traj, rnn, mlp_layer_size, rnn_layer_num, rnn_h_size, gru, run_id,
          eval_freq, resume, save_ckpt, seed, save_dir, device, **kwargs):

    # define the name of the directory to be created
    os.makedirs("./performance/"+save_dir+"/train", exist_ok=True)
    os.makedirs("./performance/"+save_dir+"/test", exist_ok=True)
    os.makedirs("./performance/"+save_dir+"/check_point", exist_ok=True)
    os.makedirs("./policy_nns/"+save_dir, exist_ok=True)

    if seed is not None:
        seed = seed *10
        random.seed(seed)
        np.random.seed(seed)
        torch.manual_seed(seed)
    torch.set_num_threads(1)

    # create env
    ENV = ENVIRONMENTS[env_name]
    if env_name.startswith('OSD'):
        env = ENV(terminate_step=env_terminate_step,
                  human_speed_per_step=[h_speed_ps],
                  TB_move_speed=tb_m_speed,
                  TB_move_noisy=tb_m_noisy,
                  fetch_pass_obj_tc=f_p_obj_tc,
                  fetch_look_for_obj_tc=f_l_obj_tc,
                  delay_delivery_penalty=d_pen)
    else:
        env = ENV(tuple(grid_dim),terminate_step=env_terminate_step)

    # create replay buffer
    if sample_epi:
        memory = ReplayMemory_epi(env.n_agent, env.obs_size, batch_size, size=replay_buffer_size)
    else:
        memory = ReplayMemory_rand(env.n_agent, env.obs_size, trace_len, batch_size, size=replay_buffer_size)

    # collect hyper params:
    hyper_params = {'cen_mode': cen_mode,
                    'soft_action_selection': softmax_explore,
                    'epsilon_linear_decay': eps_l_d,
                    'epsilon_linear_decay_steps': eps_l_d_steps,
                    'epsilon_end': eps_end,
                    'h_explore': h_explore,
                    'epsilon_exp_decay': eps_e_d,
                    'dynamic_h': dynamic_h,
                    'hysteretic': (init_h, end_h),
                    'optimizer': optim,
                    'learning_rate': l_rate,
                    'discount': discount,
                    'huber_loss': huber_l,
                    'grad_clip': g_clip,
                    'grad_clip_value': g_clip_v,
                    'grad_clip_norm': g_clip_norm,
                    'grad_clip_max_norm': g_clip_max_norm,
                    'sample_epi': sample_epi,
                    'trace_len': trace_len,
                    'sub_trace_len': sub_trace_len,
                    'batch_size': batch_size,
                    'sort_traj': sort_traj,
                    'device': device}

    model_params = {'mlp_layer_size': mlp_layer_size,
                    'rnn_layer_num': rnn_layer_num,
                    'rnn_h_size': rnn_h_size,
                    'GRU': gru}

    # create team
    team = Team_RNN(env, n_env, memory, env.n_agent, QLearns[cen_mode], h_stable_at,
            save_dir=save_dir, nn_model_params=model_params, eval_freq=eval_freq, **hyper_params)

    t = time.time()
    training_count=0
    target_updating_count = 0

    step = 0

    # continue training using the lastest check point
    if resume:
        team.load_check_point(run_id)
        step = team.step_count

    while team.episode_count < total_epi:
        team.step(run_id)
        if (not step % train_freq) and team.episode_count >= start_train:
            # update hysteretic learning rate
            if db_step:
                team.update_hysteretic(step)
            else:
                team.update_hysteretic(team.episode_count)

            for _ in range(n_env):
                team.train()

            # update epsilon
            if db_step:
                team.update_epsilon(step)
            else:
                team.update_epsilon(team.episode_count)

            training_count += 1
            #save_check_point(team.agents, step, team.hysteretic, team.epsilon, save_dir)
        if not step % target_update_freq:
            team.update_target_net()
            if (time.time()-t) / 3600 >= 23.0 and save_ckpt:
                save_check_point(team.cen_controller, step, team.episode_count, team.hysteretic, team.epsilon, save_dir, team.memory, run_id, team.TEST_PERFORM)
                sys.exit()
            target_updating_count += 1
            print('[{}]run, [{:.1f}K] took {:.3f}hr to finish {} episodes {} trainning and {} target_net updating (eps={})'.format(
                    run_id, step/1000, (time.time()-t)/3600, team.episode_count, training_count, target_updating_count, team.epsilon), flush=True)
        step += 1

    if team.episode_count == total_epi and save_ckpt:
        save_check_point(team.cen_controller, step, team.episode_count, team.hysteretic, team.epsilon, save_dir, team.memory, run_id, team.TEST_PERFORM)
        print("Finish entire training ... ")
    team.envs_runner.close()

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--env_name',           action='store',        type=str,             default='OSD_S_4',     help='Domain name')
    parser.add_argument('--env_terminate_step', action='store',        type=int,             default=150,           help='Maximal steps for termination')
    parser.add_argument('--grid_dim',           action='store',        type=int,  nargs=2,   default=[6,6],         help='Grid world size')
    parser.add_argument('--obs_one_hot',        action='store_true',                                                help='Whether represents observation as a one-hot vector')
    parser.add_argument('--target_flick_prob',  action='store',        type=float,           default=0.3,           help='Probability of not observing target in Capture Target domain')
    parser.add_argument('--agent_trans_noise',  action='store',        type=float,           default=0.1,           help='Agent dynamics noise in Capture Target domain')
    parser.add_argument('--target_rand_move',   action='store_true',                                                help='Whether apply target random movement')
    parser.add_argument('--n_env',              action='store',        type=int,             default=1,             help='Number of envs running in parallel')
    parser.add_argument('--n_agent',            action='store',        type=int,             default=2,             help='Number of agents')
    parser.add_argument('--cen_mode',           action='store',        type=int,             default=0,             help='Index of learning algorithm')

    #OSD params
    parser.add_argument('--h_speed_ps',         action='store',        type=int,  nargs='+', default=[18],          help='Time-step cost for each work step of human task')
    parser.add_argument('--tb_m_speed',         action='store',        type=float,           default=0.6,           help='Turtlebot move speed m/s')
    parser.add_argument('--tb_m_noisy',         action='store',        type=float,           default=0.0,           help='Turtlebot dynamics noise')
    parser.add_argument('--f_p_obj_tc',         action='store',        type=int,             default=4,             help='Time-step cost for finishing macro-action Pass_obj by Fetch robot')
    parser.add_argument('--f_l_obj_tc',         action='store',        type=int,             default=6,             help='Time-step cost for finishing macro-action Look_for_obj by Fetch robot')
    parser.add_argument('--d_pen',              action='store_true',                                                help='Whether apply penatly for delayed tool delivery')

    parser.add_argument('--total_epi',          action='store',        type=int,             default=40*1000,       help='Number of training episodes')
    parser.add_argument('--replay_buffer_size', action='store',        type=int,             default=1000,          help='Number of episodes/sequences in replay buffer')
    parser.add_argument('--sample_epi',         action='store_true',                                                help='Whether use full-episode-based replay buffer or not')
    parser.add_argument('--dynamic_h',          action='store_true',                                                help='Whether apply hysteritic learning rate decay or not')
    parser.add_argument('--init_h',             action='store',        type=float,           default=1.0,           help='Initial value of hysteretic learning rate')
    parser.add_argument('--end_h',              action='store',        type=float,           default=1.0,           help='Ending value of hysteretic learning rate')
    parser.add_argument('--h_stable_at',        action='store',        type=int,             default=4*1000,        help='Decaying period according to episodes/steps')

    parser.add_argument('--eps_l_d',            action='store_true',                                                help='Whether use epsilon linear decay for exploartion or not')
    parser.add_argument('--eps_l_d_steps',      action='store',        type=int,             default=4*1000,        help='Decaying period according to episodes/steps')
    parser.add_argument('--eps_end',            action='store',        type=float,           default=0.1,           help='Ending value of epsilon')
    parser.add_argument('--eps_e_d',            action='store_true',                                                help='Whether use episode-based epsilon linear decay or not')
    parser.add_argument('--softmax_explore',    action='store_true',                                                help='Whether apply softmac for exploration')
    parser.add_argument('--h_explore',          action='store_true',                                                help='whether use history-based policy for exploration or not')
    parser.add_argument('--db_step',            action='store_true',                                                help='Whether use step-based decaying manner or not')

    parser.add_argument('--optim',              action='store',         type=str,            default='Adam',        help='Optimizer')
    parser.add_argument('--l_rate',             action='store',         type=float,          default=0.0006,        help='Learning rate')
    parser.add_argument('--discount',           action='store',         type=float,          default=0.95,          help='Discount factor')
    parser.add_argument('--huber_l',            action='store_true',                                                help='Whether use huber loss or not')
    parser.add_argument('--g_clip',             action='store_true',                                                help='Whether use gradient clip or not')
    parser.add_argument('--g_clip_v',           action='store',         type=float,          default=0.0,           help='Absolute Value limitation for gradient clip')
    parser.add_argument('--g_clip_norm',        action='store_true',                                                help='Whether use norm-based gradient clip')
    parser.add_argument('--g_clip_max_norm',    action='store',         type=float,          default=0.0,           help='Norm limitation for gradient clip')

    parser.add_argument('--start_train',        action='store',         type=int,            default=2,             help='Training starts after a number of episodes')
    parser.add_argument('--train_freq',         action='store',         type=int,            default=30,            help='Updating performs every a number of steps')
    parser.add_argument('--target_update_freq', action='store',         type=int,            default=5000,          help='Updating target net every a number of steps')
    parser.add_argument('--trace_len',          action='store',         type=int,            default=10,            help='The length of each sequence saved in replay buffer when not using full-episode-based replay buffer')
    parser.add_argument('--sub_trace_len',      action='store',         type=int,            default=1,             help='Minimal length of a sequence for traning data filtering')
    parser.add_argument('--sort_traj',          action='store_true',                                                help='Whether sort sequences based on its valid length after squeezing experiences or not, redundant param for pytorch version later than 1.1.0')
    parser.add_argument('--batch_size',         action='store',         type=int,            default=16,            help='Number of episodes/sequences in a batch')

    parser.add_argument('--rnn',                action='store_true',                                                help='Whether using rnn-based agent')
    parser.add_argument('--mlp_layer_size',     action='store',         type=int,            default=32,            help='MLP layer dimension of decentralized policy-net')
    parser.add_argument('--rnn_layer_num',      action='store',         type=int,            default=1,             help='Number of RNN layers')
    parser.add_argument('--rnn_h_size',         action='store',         type=int,            default=64,            help='RNN hidden layer dimension')
    parser.add_argument('--gru',                action='store_true',                                                help='Whether use gru or not')

    parser.add_argument('--eval_freq',          action='store',         type=int,            default=100,           help='Pause training every 100 episodes for evaluation')
    parser.add_argument('--resume',             action='store_true',                                                help='Whether use saved ckpt to continue training or not')
    parser.add_argument('--save_ckpt',          action='store_true',                                                help='Whether save ckpt or not')
    parser.add_argument('--run_id',             action='store',         type=int,            default=0,             help='Index of a run')
    parser.add_argument('--seed',               action='store',         type=int,            default=None,          help='Random seed of a run')
    parser.add_argument('--save_dir',           action='store',         type=str,            default=None,          help='Directory name for storing trainning results')
    parser.add_argument('--device',             action='store',         type=str,            default='cpu',         help='Which device (CPU/GPU) to use.')

    params = vars(parser.parse_args())

    train(**params)

if __name__ == '__main__':
    main()


#redis client ofr python
import redis
#flask to expose api's to outside world
from flask import Flask,request,jsonify
app = Flask("autocomplete")

#creating a redis connection
r = redis.StrictRedis(host='localhost', port=7001, db=0)

#route to add a value to autocomplete list
'''
FORMAT:
localhost:5000/add?name=<name>
'''
@app.route('/add')
def add_to_dict():
    try:
        name = request.args.get('name')
        n = name.strip()
        for l in range(1,len(n)):
            prefix = n[0:l]
            r.zadd('compl',{prefix:0})
        r.zadd('compl',{n+"*":0})
        return "Added"
    except:
        return "Addition failed"

#route to get the suggestiosn
'''
FORMAT:
localhost:5000/suggestions?prefix=<prefix_you want to match>
'''
@app.route('/suggestions')
def get_suggestions():
    prefix = request.args.get('prefix')
    results = []
    rangelen = 50 # This is not random, try to get replies < MTU size
    count=5
    start = r.zrank('compl',prefix)
    if not start:
        return []
    while (len(results) != count):
         range = r.zrange('compl',start,start+rangelen-1)
         start += rangelen
         if not range or len(range) == 0:
             break
         for entry in range:
             entry=entry.decode('utf-8')
             minlen = min(len(entry),len(prefix))
             if entry[0:minlen] != prefix[0:minlen]:
                count = len(results)
                break
             if entry[-1] == "*" and len(results) != count:
                results.append(entry[0:-1])

    return jsonify(results)

'''
Start the Application through cmd:
export FLASK_APP=<path to python file>/auto-complete_redis.py
flask run
'''


from hypothesis import assume, given
from hypothesis import strategies as st

import pytest

from .strategies import list_and_element
from roster import Attack, Move, Roster, StateChange
from space import Point, Vector

def positions_unique(positions):
    return len(set(p[0] for p in positions)) == len(positions)

characters = st.builds(object)
position_lists = st.lists(st.tuples(st.from_type(Point), characters))
unique_position_lists = position_lists.filter(positions_unique)

class TestRoster:

    @given(unique_position_lists)
    def test_takes_position_character_pairs(self, positions):
        Roster(positions)

    @given(unique_position_lists.flatmap(list_and_element))
    def test_character_at_position(self, positions_and_item):
        positions, (position, character) = positions_and_item

        assert Roster(positions).character_at(position) == character

    @given(st.from_type(Point))
    def test_rejects_duplicate_position(self, point):
        positions = [(point, object()), (point, object())]
        with pytest.raises(ValueError):
            Roster(positions)

    @given(characters)
    def test_rejects_duplicate_character(self, character):
        positions = [(Point(0, 0), character), (Point(1, 1), character)]
        with pytest.raises(ValueError) as e:
            Roster(positions)

    @given(unique_position_lists)
    def test_value_equality(self, positions):
        assert Roster(positions) == Roster(list(positions))

    @given(unique_position_lists)
    def test_order_indifference(self, positions):
        assert Roster(positions) == Roster(reversed(positions))

    def test_roster_for_dict(self):
        character = object()
        roster = Roster.for_value({(0, 1): character})
        assert roster.character_at(Point(0, 1)) == character

    def test_roster_for_nothing(self):
        roster = Roster.for_value(None)
        assert not list(roster)

    def test_roster_for_itself(self):
        roster = Roster([((0, 2), object())])
        assert roster.for_value(roster) is roster

class TestMove:

    @given(unique_position_lists.flatmap(list_and_element))
    def test_zero_move_preserves_roster(self, positions_and_item):
        positions, (_, character) = positions_and_item
        roster = Roster(positions)
        assert Move(character, Vector.ZERO).next_roster(roster) == roster

    @given(unique_position_lists.flatmap(list_and_element), st.from_type(Vector))
    def test_character_moves(self, positions_and_item, move_vector):
        positions, (position, character) = positions_and_item
        new_position = position + move_vector
        assume(not any(pos == new_position for pos, _ in positions))
        roster = Roster(positions)
        move = Move(character, move_vector)

        next_roster = move.next_roster(roster)

        assert next_roster.character_at(new_position) == character

    def test_move_to_occupied_position(self):
        a, b = object(), object()
        roster = Roster([(Point(0, 0), a), (Point(1, 1), b)])
        move = Move(b, Vector(-1, -1))
        with pytest.raises(ValueError):
            move.next_roster(roster)

    def test_move_of_non_existent_character(self):
        a, b = object(), object()
        roster = Roster([(Point(0, 0), a)])
        move = Move(b, Vector(1, 1))
        with pytest.raises(ValueError):
            move.next_roster(roster)

    def test_move_preserves_non_moving_character(self):
        a, b = object(), object()
        roster = Roster([(Point(0, 0), a), (Point(1, 1), b)])
        move = Move(a, Vector(0, 1))
        assert move.next_roster(roster).character_at(Point(1, 1)) is b

class Target:

    def __init__(self, attack_result):
        self._attack_result = attack_result

    def attacked(self):
        return self._attack_result

class TestAttack:

    def test_fails_if_target_not_in_roster(self):
        attacker, target = object(), object()
        roster = Roster([(Point(0, 0), attacker)])
        attack = Attack(attacker, target)
        with pytest.raises(ValueError):
            attack.next_roster(roster)

    def test_fails_if_attacker_not_in_roster(self):
        attacker, target = object(), object()

        roster = Roster([(Point(0, 0), target)])
        attack = Attack(attacker, target)
        with pytest.raises(ValueError):
            attack.next_roster(roster)

    def test_attacks_target(self):
        attacker = object()
        attacked = object()
        target = Target(attacked)

        roster = Roster([(Point(0, 0), attacker), (Point(1, 1), target)])
        attack = Attack(attacker, target)
        new_roster = attack.next_roster(roster)
        assert new_roster.character_at(Point(1, 1)) is attacked

    def test_preserves_attacker(self):
        attacker = object()
        target = Target(object())

        roster = Roster([(Point(0, 0), attacker), (Point(1, 1), target)])
        attack = Attack(attacker, target)
        new_roster = attack.next_roster(roster)
        assert new_roster.character_at(Point(0, 0)) is attacker

class Character:

    def __init__(self, state):
        self.state = state

    def with_state(self, state):
        return Character(state=state)

class TestStateChange:

    def test_fails_if_character_not_in_roster(self):
        character, state = object(), object()

        state_change = StateChange(character, state)

        roster = Roster([])

        with pytest.raises(ValueError):
            state_change.next_roster(roster)

    def test_changes_character_state(self):
        character, state = Character(state=None), object()
        state_change = StateChange(character, state)
        roster = Roster([(Point(0, 1), character)])

        next_roster = state_change.next_roster(roster)

        assert next_roster.character_at(Point(0, 1)).state == state


"""
Assignment 2: Decision Trees
Part 2

@author: Divyam Jain
         17HS20047
"""

#Importing some essential libraries

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import math

#Importing the dataset

words = np.loadtxt('words.txt', dtype='str')

traindata = np.loadtxt('traindata.txt')
trainlabel = np.loadtxt('trainlabel.txt')
testdata = np.loadtxt('testdata.txt')
testlabel = np.loadtxt('testlabel.txt')

dataset_train = np.zeros((len(trainlabel),len(words)+1),dtype=int)
dataset_train[:,-1] = trainlabel[:]-1
for i in range(len(traindata)):
    dataset_train[int(traindata[i,0])-1,int(traindata[i,1])-1]+=1

dataset_test = np.zeros((len(testlabel),len(words)+1),dtype=int)
dataset_test[:,-1] = testlabel[:]-1
for i in range(len(testdata)):
    dataset_test[int(testdata[i,0])-1,int(testdata[i,1])-1]+=1

#Calculating Information Gain

def info_gain (df):

    mat=np.zeros((2,2))
    for i in range(len(df)):
        mat[df[i,0],df[i,1]]+=1

    x = np.sum(mat,axis=0)
    entropy_before =  -x[0]/(x[0]+x[1])*math.log2(x[0]/(x[0]+x[1]))-x[1]/(x[0]+x[1])*math.log2(x[1]/(x[0]+x[1]))

    val=0
    for i in range(len(mat)):
        a=mat[i,0]
        b=mat[i,1]

        if a!=0 and b!=0:
            val = val-a*(math.log2(a/(a+b)))-b*(math.log2(b/(a+b)))

    entropy_after = val/len(df)

    return entropy_before-entropy_after

#Spliting Dataset

def split (feature, dataset):

    B = []
    for j in [0,1]:
        B.append(dataset[dataset[:,feature]==j])

    return B

#Making Decison Tree using Information Gain

def best_split_info(feature_set, data, level, temp, depth):

    if(len(data[data[:,-1]==0])==len(data)):
        print(':alt.atheism', end='')
        temp['label'] = 0
    elif(len(data[data[:,-1]==1])==len(data)):
        print(':comp.graphics', end='')
        temp['label'] = 1

    elif level==depth:
        if(len(data)>0):
            count_0 = len(data[data[:,-1]==0])
            if count_0 >= len(data)-count_0:
                print(':alt.atheism', end='')
                temp['label'] = 0
            else:
                print(':comp.graphics', end='')
                temp['label'] = 1
    elif len(data)==0:
        pass

    else:

        A =[]
        for i in feature_set:
            A.append(info_gain(data[:,[i,-1]]))

        best_feature = A.index(max(A))
        B = split(best_feature, data)

        temp[best_feature] = {}
        temp = temp[best_feature]

        #feature_set.remove(best_feature)

        for i in [0,1]:

            if level>=1:
                if len(B[i])!=0:
                    print('\n')
                    for j in range(1,level):
                        print('\t', end ='')
                    print('|', end='')
                    print(best_feature,'=',sep='', end='')
                    print(i, end='')
                    temp[i]={}

            else:
                if len(B[i])!=0:
                    print('\n')
                    print(best_feature,'=',sep='', end='')
                    print(i, end='')
                    temp[i]={}

            best_split_info(feature_set, B[i], level+1, temp[i], depth)

#Predicting labels of Data

def predict (row, tree):
    while(list(tree.keys())[0]!='label'):
        x=row[list(tree.keys())[0]]
        tree = tree[list(tree.keys())[0]]
        tree = tree[x]
    return tree['label']

#Using Information Gain

dep = int(input('What is the required depth? '))

print('\n\n---Decision Tree using Information Gain---', end='')
model = {}
temp=model
feature_set=np.arange(0,len(words))
best_split_info(feature_set, dataset_train, 0, temp, dep)

#Getting Accuracies for varied depths

max_depth = 25
acc_train=[]
acc_test=[]

for j in range(1,max_depth):

    print('\n\n---Decision Tree using Information Gain---  max_depth =',j, end='')
    best_split_info(feature_set, dataset_train, 0, temp, j)

    #Finding Training Data accuracy

    pred = []

    for k in range(len(dataset_train)):
        pred.append(predict(dataset_train[k,:-1],model))

    count = 0
    for i in range(len(pred)):
        if dataset_train[i,-1]==pred[i]:
            count+=1
    accuracy = count/len(pred)*100

    acc_train.append(accuracy)

    #Finding Test Data accuracy

    pred = []

    for k in range(len(dataset_test)):
        pred.append(predict(dataset_test[k,:-1],model))

    count = 0
    for i in range(len(pred)):
        if dataset_test[i,-1]==pred[i]:
            count+=1
    accuracy = count/len(pred)*100

    acc_test.append(accuracy)

#Plotting graphs

plt.plot(np.arange(1,max_depth), acc_train, color = 'blue')
plt.plot(np.arange(1,max_depth), acc_test, color = 'red')
plt.title('Training(Blue) and Test(Red) Accuracy for various maximum depths')
plt.xlabel('Maximium Depth')
plt.ylabel('Accuracies')
plt.show()

#Using Scikit Learn

from sklearn.tree import DecisionTreeClassifier

classifier = DecisionTreeClassifier(criterion='entropy',random_state=0)
classifier.fit(dataset_train[:,:-1],dataset_train[:,-1])

scipred = classifier.predict(dataset_test[:,:-1])

count = 0
for i in range(len(scipred)):
    if dataset_test[i,-1]==scipred[i]:
        count+=1
sciaccuracy = count/len(scipred)*100

Assignment 2 - Decision Tree/Part2_A2.py


from typing import List, Dict

import torch

from ._encoder_base import Encoder
from .utils import get_mlp

@Encoder.register('mlp_encoder')
class MlpEncoder(Encoder):
    def __init__(self,
                 encode_dims: List[int],
                 activation: str = 'relu',
                 dropout: float = 0.
                 ):
        super().__init__()

        self.mlp = get_mlp(encode_dims, activation=activation, dropout=dropout)
        self.output_dim = encode_dims[-1]

        self.metrics = {}

    def get_output_dim(self) -> int:
        return self.output_dim

    def forward(self, x: torch.Tensor, **kwargs) -> Dict[str, torch.Tensor]:
        latent_feature = self.mlp(x)
        output_dict = {"latent_feature": latent_feature}

        return output_dict


#p14_3

import urllib.request
import urllib.parse
import json

url = "http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule"
data = {}
data["from"] = "AUTO"
data["to"] = "AUTO"
data["i"] = "爱你"
data["smartresult"] = "dict"
data["client"] = "fanyideskweb"
data["doctype"] = "json"
data["xmlVersion"] = "2.1"
data["keyfrom"] = "fanyi.web"
data["ue"] = "UTF-8"
data["typoResult"] = "true"
data["action"] = "FY_BY_REALTIME"
data["salt"] = "1525437242032"
data["sign"] = "8e3bd79a1b69ed33eccab6d2abe01b0b"
data = urllib.parse.urlencode(data).encode("utf-8")
response = urllib.request.urlopen(url, data)
html = response.read().decode("utf-8")
target = json.loads(html)
print(target)