#!/usr/bin/env python
"""
The file contains the class and methods for loading and aligning datasets
"""
from __future__ import print_function, division
import pickle
import numpy as np
from scipy.io import loadmat
import pandas as pd
from .utils import p2fa_phonemes
import warnings
from collections import OrderedDict
from copy import deepcopy
import sys

__author__ = "Prateek Vij"
__copyright__ = "Copyright 2017, Carnegie Mellon University"
__credits__ = ["Amir Zadeh", "Prateek Vij", "Soujanya Poria"]
__license__ = "GPL"
__version__ = "1.0.1"
__status__ = "Production"

if sys.version_info <= (3,):
    FileNotFoundError = IOError # python 2 doesn't have FileNotFoundError

class Dataset(object):
    """Primary class for loading and aligning dataset"""

    def __init__(self, dataset_file='', stored=False):
        """
        Initialise the Dataset class. Support two loading mechanism -
        from dataset files and from the pickle file, decided by the param
        stored.
        :param stored: True if loading from pickle, false if loading from
                       dataset feature files. Default False
        :param dataset_file: Filepath to the file required to load dataset
                             features. CSV or pickle file depending upon the
                             loading mechanism
        :timestamps: absolute or relative.
        """
        self.feature_dict = None
        self.timestamps = 'absolute' # this is fixed, we no longer support relative timestamps
        self.stored = stored
        self.dataset_file = dataset_file
        self.phoneme_dict = p2fa_phonemes
        self.loaded = False

    def __getitem__(self, key):
        """Adding direct access of internal data"""
        return self.feature_dict[key]

    def keys(self):
        """Wrapper for .keys() for the feature_dict"""
        return self.feature_dict.keys()

    def items(self):
        """Wrapper for .items() for the feature_dict"""
        return self.feature_dict.items()

    def load(self):
        """
        Loads feature dictionary for the input dataset
        :returns: Dictionary of features for the dataset with each modality
         as dictionary key
        """

        # Load from the pickle file if stored is True
        if self.stored:
            self.dataset_pickle = self.dataset_file
            self.feature_dict = pickle.load(open(self.dataset_pickle))
            return self.feature_dict

        # Load the feature dictionary from the dataset files
        self.dataset_csv = self.dataset_file
        self.feature_dict = self.controller()
        self.loaded = True
        return self.feature_dict

    def controller(self):
        """
        Validates the dataset csv file and loads the features for the dataset
        from its feature files
        """

        def validate_file(self):
            data = pd.read_csv(self.dataset_csv, header=None)
            data = np.asarray(data)
            #data = data[:,:7]
            self.dataset_info = {}
            modality_count = len(data[0]) - 4
            self.modalities = {}
            for i in range(modality_count):
                # key = 'modality_' + str(i)
                key = str(data[0][i + 4])
                info = {}
                info["level"] = str(data[1][i + 4])
                info["type"] = str(data[0][i + 4])
                self.modalities[key] = info

            for record in data[2:]:
                video_id = str(record[0])
                segment_id = str(record[1])
                if video_id not in self.dataset_info:
                    self.dataset_info[video_id] = {}
                if segment_id in self.dataset_info[video_id]:
                    raise NameError("Multiple instances of segment "
                                    + segment_id + " for video " + video_id)
                segment_data = {}
                segment_data["start"] = float(record[2])
                segment_data["end"] = float(record[3])
                for i in range(modality_count):
                    # key = 'modality_' + str(i)
                    key = str(data[0][i + 4])
                    segment_data[key] = str(record[i + 4])
                self.dataset_info[video_id][segment_id] = segment_data
            return

        def load_features(self):
            feat_dict = {}
            data = self.dataset_info
            modalities = self.modalities
            # timestamps = self.timestamps
            for key, value in modalities.items():
                api = value['type']
                level = value['level']
                loader_method = Dataset.__dict__["load_" + api]
                modality_feats = {}
                print("Loading features for", api)
                for video_id, video_data in data.items():
                    video_feats = {}
                    for segment_id, segment_data in video_data.items():
                        filepath = str(segment_data[key])
                        start = segment_data["start"]
                        end = segment_data["end"]
                        video_feats[segment_id] = loader_method(self,
                                                                filepath, start,
                                                                end, timestamps=self.timestamps,
                                                                level=level)
                    modality_feats[video_id] = video_feats
                    modality_feats = OrderedDict(sorted(modality_feats.items(), key=lambda x: x[0]))
                feat_dict[key] = modality_feats

            return feat_dict

        validate_file(self)
        feat_dict = load_features(self)

        return feat_dict

    def load_opensmile(self, filepath, start, end, timestamps='absolute', level='s'):
        """
        Load OpenSmile Features from the file corresponding to the param
        filepath
        :param start: Start time of the segment
        :param end: End time of the segment
        :param filepath: Path to the opensmile feature files
        :param level: 's' if the file contains features only for the segment,
                      i.e. interval (start, end), 'v' if for the entire video
        :param timestamps: relative or absolute
        :returns: List of tuples (feat_start, feat_end, feat_value)
                  corresponding to the features in the interval.
        Note: Opensmile support features for entire segment or video only and
              will return None if level is 'v' and start time is
        """
        features = []
        start_time, end_time = start, end
        if timestamps == 'relative':
            start_time = 0.0
            end_time = end - start

        if level == 's' or start == 0.0:
            feats = open(filepath).readlines()[-1].strip().split(',')[1:]
            feats = [float(feat_val) for feat_val in feats]
            feat_val = np.asarray(feats, dtype=np.float32)
            features.append((start_time, end_time, feat_val))
        else:
            print("Opensmile support features for the entire segment")
            return None
        return features

    def load_covarep(self, filepath, start, end, timestamps='absolute', level='s'):
        """
        Load COVAREP Features from the file corresponding to the param
        filepath
        :param start: Start time of the segment
        :param end: End time of the segment
        :param filepath: Path to the opensmile feature files
        :param level: 's' if the file contains features only for the segment,
                      i.e. interval (start, end), 'v' if for the entire video
        :param timestamps: relative or absolute
        :returns: List of tuples (feat_start, feat_end, feat_value)
                  corresponding to the features in the interval.
        """
        features = []
        time_period = 0.01
        try:
            f_content = loadmat(filepath)
        except (FileNotFoundError, TypeError, ValueError) as e: # depending on loadmat("XXX") or loadmat("XXX.mat"), error will be different
            print(".mat file cannot load at {}!".format(filepath))
            return [] # if no feature file present, return an empty list
        feats = f_content['features']
        start_time, end_time = start, end
        if timestamps == "relative":
            start_time, end_time = 0.0, end - start

        if level == 's':
            feat_start = start_time
            for feat in feats:
                feat_end = feat_start + time_period
                feat_val = np.asarray(feat)
                features.append((max(feat_start - start_time, 0), max(feat_end - start_time, 0), feat_val))
                feat_start += time_period
        else:
            feat_count = feats.shape[0]
            start_index = int(min((start / time_period), feat_count))
            end_index = int(min((end / time_period), feat_count))
            feat_start = start_time
            for feat in feats[start_index:end_index]:
                feat_end = feat_start + time_period
                feat_val = np.asarray(feat)
                features.append((max(feat_start - start_time, 0), max(feat_end - start_time, 0), feat_val))
                feat_start += time_period
        return features

    def load_phonemes(self, filepath, start, end, timestamps='relative', level='s'):
        """
        Load P2FA phonemes as Features from the file corresponding to the
        param filepath
        :param start: Start time of the segment
        :param end: End time of the segment
        :param filepath: Path to the opensmile feature files
        :param level: 's' if the file contains features only for the segment,
                      i.e. interval (start, end), 'v' if for the entire video
        :param timestamps: relative or absolute
        :returns: List of tuples (feat_start, feat_end, feat_value)
                  corresponding to the features in the interval.
        """
        features = []
        start_time, end_time = start, end
        if timestamps == "relative":
            start_time, end_time = 0.0, end - start

        if level == 's':
            with open(filepath, 'r') as f_handle:
                for line in f_handle.readlines():
                    line = line.strip()
                    if not line:
                        break
                    feat_start = float(line.split(",")[0])
                    feat_end = float(line.split(",")[1])
                    feat_val = [float(val) for val in line.split(",")[2:]]
                    feat_val = np.asarray(feat_val)
                    features.append((max(feat_start, 0), max(feat_end, 0), feat_val))
        else:
            with open(filepath, 'r') as f_handle:
                for line in f_handle.readlines():
                    line = line.strip()
                    if not line:
                        break
                    feat_start = float(line.split(",")[0])
                    feat_end = float(line.split(",")[1])
                    feat_time = feat_end - feat_start
                    if ((feat_start <= start and feat_end > end)
                        or (feat_start >= start and feat_end < end)
                        or (feat_start <= start
                            and start - feat_start < feat_time / 2)
                        or (feat_start >= start
                            and end - feat_start > feat_time / 2)):

                        feat_start = feat_start - start
                        feat_end = feat_end - start
                        feat_val = [float(val) for val in line.split(",")[2:]]
                        feat_val = np.asarray(feat_val)
                        features.append((max(feat_start, 0), max(feat_end, 0), feat_val))
        return features

    def load_embeddings(self, filepath, start, end, timestamps='relative', level='s'):
        """
        Load Word Embeddings from the file corresponding to the param
        filepath
        :param start: Start time of the segment
        :param end: End time of the segment
        :param filepath: Path to the opensmile feature files
        :param level: 's' if the file contains features only for the segment,
                      i.e. interval (start, end), 'v' if for the entire video
        :param timestamps: relative or absolute
        :returns: List of tuples (feat_start, feat_end, feat_value)
                  corresponding to the features in the interval.
        """
        features = []
        start_time, end_time = start, end
        if timestamps == "relative":
            start_time, end_time = 0.0, end - start

        if level == 's':
            with open(filepath, 'r') as f_handle:
                for line in f_handle.readlines():
                    line = line.strip()
                    if not line:
                        break
                    feat_start = float(line.split(",")[0])
                    feat_end = float(line.split(",")[1])
                    feat_val = [float(val) for val in line.split(",")[2:]]
                    feat_val = np.asarray(feat_val)
                    features.append((max(feat_start, 0), max(feat_end, 0), feat_val))
        else:
            with open(filepath, 'r') as f_handle:
                for line in f_handle.readlines():
                    line = line.strip()
                    if not line:
                        break
                    feat_start = float(line.split(",")[0])
                    feat_end = float(line.split(",")[1])
                    feat_time = feat_end - feat_start
                    if ((feat_start <= start and feat_end > end)
                        or (feat_start >= start and feat_end < end)
                        or (feat_start <= start
                            and start - feat_start < feat_time / 2)
                        or (feat_start >= start
                            and end - feat_start > feat_time / 2)):

                        feat_start = feat_start - start
                        feat_end = feat_end - start
                        feat_val = [float(val) for val in line.split(",")[2:]]
                        feat_val = np.asarray(feat_val)
                        features.append((max(feat_start, 0), max(feat_end, 0), feat_val))
        return features

    def load_words(self, filepath, start, end, timestamps='relative', level='s'):
        """
        Load one hot embeddings for words as features from the file
        corresponding to the param filepath
        :param start: Start time of the segment
        :param end: End time of the segment
        :param filepath: Path to the opensmile feature files
        :param level: 's' if the file contains features only for the segment,
                      i.e. interval (start, end), 'v' if for the entire video
        :param timestamps: relative or absolute
        :returns: List of tuples (feat_start, feat_end, feat_value)
                  corresponding to the features in the interval.
        """
        features = []
        start_time, end_time = start, end
        if timestamps == "relative":
            start_time, end_time = 0.0, end - start

        if level == 's':
            with open(filepath, 'r') as f_handle:
                for line in f_handle.readlines():
                    line = line.strip()
                    if not line:
                        break
                    feat_start = float(line.split(",")[0])
                    feat_end = float(line.split(",")[1])
                    try:
                        feat_val = int(line.split(",")[2])
                    except:
                        print(filepath, start, end)
                    # feat_val = [float(val) for val in line.split(",")[2:]]
                    # assert len(feat_val) == 1
                    # feat_val = np.asarray(feat_val)[0]
                    #print(feat_start, feat_end)
                    #assert False
                    features.append((max(feat_start, 0), max(feat_end, 0), feat_val))
        else:
            with open(filepath, 'r') as f_handle:
                for line in f_handle.readlines():
                    line = line.strip()
                    if not line:
                        break
                    feat_start = float(line.split(",")[0])
                    feat_end = float(line.split(",")[1])
                    feat_time = feat_end - feat_start
                    if ((feat_start <= start and feat_end > end)
                        or (feat_start >= start and feat_end < end)
                        or (feat_start <= start
                            and start - feat_start < feat_time / 2)
                        or (feat_start >= start
                            and end - feat_start > feat_time / 2)):

                        feat_start = feat_start - start
                        feat_end = feat_end - start
                        feat_val = [float(val) for val in line.split(",")[2:]]
                        feat_val = np.asarray(feat_val)
                        features.append((max(feat_start, 0), max(feat_end, 0), feat_val))
        return features

    def load_openface(self, filepath, start, end, timestamps='absolute', level='s'):
        """
        Load OpenFace features from the file corresponding to the param
        filepath
        :param start: Start time of the segment
        :param end: End time of the segment
        :param filepath: Path to the opensmile feature files
        :param level: 's' if the file contains features only for the segment,
                      i.e. interval (start, end), 'v' if for the entire video
        :param timestamps: relative or absolute
        :returns: List of tuples (feat_start, feat_end, feat_value)
                  corresponding to the features in the interval.
        """
        features = []
        time_period = 0.0333333

        start_time, end_time = start, end
        if timestamps == "relative":
            start_time, end_time = 0.0, end - start

        if level == 's':
            with open(filepath, 'r') as f_handle:
                for line in f_handle.readlines()[1:]:
                    line = line.strip()
                    if not line:
                        break
                    feat_start = float(line.split(",")[1])
                    feat_end = feat_start + time_period
                    feat_val = [float(val) for val in line.split(",")[2:]]
                    feat_val = np.asarray(feat_val, dtype=np.float32)
                    features.append((max(feat_start, 0), max(feat_end, 0), feat_val))

        else:
            with open(filepath, 'r') as f_handle:
                for line in f_handle.readlines()[1:]:
                    line = line.strip()
                    if not line:
                        break
                    feat_start = float(line.split(",")[1])

                    if (feat_start >= start and feat_start < end):
                        # To adjust the timestamps
                        feat_start = feat_start - start
                        feat_end = feat_start + time_period
                        feat_val = [float(val) for val in line.split(",")[2:]]
                        feat_val = np.asarray(feat_val, dtype=np.float32)
                        features.append((max(feat_start, 0), max(feat_end, 0), feat_val))
        return features

    # note that this is implicity new facet
    def load_facet(self, filepath, start, end, timestamps='absolute', level='v'):
        """
        Load FACET features from the file corresponding to the param
        filepath
        :param start: Start time of the segment
        :param end: End time of the segment
        :param filepath: Path to the opensmile feature files
        :param level: 's' if the file contains features only for the segment,
                      i.e. interval (start, end), 'v' if for the entire video
        :param timestamps: relative or absolute
        :returns: List of tuples (feat_start, feat_end, feat_value)
                  corresponding to the features in the interval.
        """
        features = []

        # load a subset of current segment and infer its format
        start_row = 0
        start_col = 0
        with open(filepath, 'r') as f_handle:
            splitted = []
            for line in f_handle.readlines()[0:10]:
                splitted.append(line.split(","))

            # check if the first row is a header by checking if the first field is a number
            try:
                float(splitted[start_row][start_col])
            except:
                start_row = 1

            # check if the first column is a index column by checking if it increments by 1 everytime
            for i in range(1, len(splitted) - 1):
                if (float(splitted[i+1][0]) - float(splitted[i][0])) != 1:
                    start_col = 0
                    break
                start_col = 1

            time_period = float(splitted[start_row][start_col])

        start_time, end_time = start, end
        # if timestamps == "relative":
        #     start_time, end_time = 0.0, end - start

        if level == 's':
            with open(filepath, 'r') as f_handle:
                for line in f_handle.readlines()[start_row:]:
                    line = line.strip()
                    if not line:
                        break
                    feat_start = float(line.split(",")[start_col])
                    feat_end = feat_start + time_period
                    feat_val = []
                    for val in line.split(",")[start_col + 1:-1]:
                        try:
                            feat_val.append(float(val))
                        except:
                            feat_val.append(0.0)
                    feat_val = np.asarray(feat_val, dtype=np.float32)
                    features.append((max(feat_start, 0), max(feat_end, 0), feat_val))

        else:
            with open(filepath, 'r') as f_handle:
                for line in f_handle.readlines()[start_row:]:
                    line = line.strip()
                    if not line:
                        break
                    feat_start = float(line.split(",")[start_col])

                    if (feat_start >= start and feat_start < end):
                        # To adjust the timestamps
                        feat_start = feat_start - start
                        feat_end = feat_start + time_period
                        feat_val = []
                        for val in line.split(",")[start_col + 1:-1]:
                            try:
                                feat_val.append(float(val))
                            except:
                                feat_val.append(0.0)
                        feat_val = np.asarray(feat_val, dtype=np.float32)
                        features.append((max(feat_start, 0), max(feat_end, 0), feat_val))
        return features

    def load_facet1(self, filepath, start, end, timestamps='absolute', level='v'):
        """
        Load FACET features from the file corresponding to the param
        filepath
        :param start: Start time of the segment
        :param end: End time of the segment
        :param filepath: Path to the opensmile feature files
        :param level: 's' if the file contains features only for the segment,
                      i.e. interval (start, end), 'v' if for the entire video
        :param timestamps: relative or absolute
        :returns: List of tuples (feat_start, feat_end, feat_value)
                  corresponding to the features in the interval.
        """
        return self.load_facet(filepath, start, end, timestamps=timestamps, level=level)

    def load_facet2(self, filepath, start, end, timestamps='absolute', level='v'):
        """
        Load FACET features from the file corresponding to the param
        filepath
        :param start: Start time of the segment
        :param end: End time of the segment
        :param filepath: Path to the opensmile feature files
        :param level: 's' if the file contains features only for the segment,
                      i.e. interval (start, end), 'v' if for the entire video
        :param timestamps: relative or absolute
        :returns: List of tuples (feat_start, feat_end, feat_value)
                  corresponding to the features in the interval.
        """
        return self.load_facet(filepath, start, end, timestamps=timestamps, level=level)

    def align(self, align_modality):
        aligned_feat_dict = {}
        modalities = self.modalities
        alignments = self.get_alignments(align_modality)
        for modality in modalities:
            if modality == align_modality:
                aligned_feat_dict[modality] = OrderedDict(sorted(self.feature_dict[modality].items(), key=lambda x: x[0]))
                continue
            aligned_modality = self.align_modality(modality, alignments)
            aligned_feat_dict[modality] = OrderedDict(sorted(aligned_modality.items(), key=lambda x: x[0]))
        self.aligned_feature_dict = aligned_feat_dict
        return aligned_feat_dict

    def get_alignments(self, modality):
        alignments = {}
        aligned_feat_dict = self.feature_dict[modality]

        for video_id, segments in aligned_feat_dict.items():
            segment_alignments = {}
            for segment_id, features in segments.items():
                segment_alignments[segment_id] = []
                for value in features:
                    timing = (value[0], value[1])
                    segment_alignments[segment_id].append(timing)
            alignments[video_id] = segment_alignments
        return alignments

    def align_modality(self, modality, alignments, merge_type="mean"):
        aligned_feat_dict = {}
        modality_feat_dict = self.feature_dict[modality]
        warning_hist = set() # Keep track of all the warnings

        for video_id, segments in alignments.items():
            aligned_video_feats = {}

            for segment_id, feat_intervals in segments.items():
                aligned_segment_feat = []

                for start_interval, end_interval in feat_intervals:
                    time_interval = end_interval - start_interval
                    feats = modality_feat_dict[video_id][segment_id]
                    try:
                        if modality == "words":
                            aligned_feat = feats[0][2] - feats[0][2]
                        else:
                            aligned_feat = np.zeros(len(feats[0][2]))
                    except:
                        if (video_id, segment_id) not in warning_hist:
                            print("\nModality {} for video {} segment {} is (partially) missing and is thus being replaced by zeros!\n".format(modality.split("_")[-1], video_id, segment_id))
                            warning_hist.add((video_id, segment_id))
                        # print(modality, video_id, segment_id, feats)
                        for sid, seg_data in modality_feat_dict[video_id].items():
                            if seg_data != []:
                                feats = seg_data
                                break
                    try:
                        if modality == "words":
                            aligned_feat = feats[0][2] - feats[0][2]
                        else:
                            aligned_feat = np.zeros(len(feats[0][2]))
                    except:
                        if modality == "words":
                            aligned_feat = 0
                        else:
                            aligned_feat = np.zeros(0)

                    for feat_tuple in feats:
                        feat_start = feat_tuple[0]
                        feat_end = feat_tuple[1]
                        feat_val = feat_tuple[2]
                        if (feat_start < end_interval
                                and feat_end >= start_interval):
                            feat_weight = (min(end_interval, feat_end) -
                                           max(start_interval, feat_start)) / time_interval
                            weighted_feat = np.multiply(feat_val, feat_weight)
                            if np.shape(aligned_feat) == (0,):
                                aligned_feat = weighted_feat
                            else:
                                aligned_feat = np.add(aligned_feat, weighted_feat)

                    aligned_feat_tuple = (start_interval, end_interval,
                                          aligned_feat)
                    aligned_segment_feat.append(aligned_feat_tuple)
                aligned_video_feats[segment_id] = aligned_segment_feat
            aligned_feat_dict[video_id] = aligned_video_feats

        return aligned_feat_dict

    @staticmethod
    def merge(dataset1, dataset2):
        # ensure the merged objects are indeed Datasets
        assert isinstance(dataset1, Dataset)
        assert isinstance(dataset2, Dataset)
        # merge the feature_dict and modalities attributes
        merged_modalities = Dataset.merge_dict(dataset1.modalities, dataset2.modalities)
        merged_feat_dict = Dataset.merge_dict(dataset1.feature_dict, dataset2.feature_dict)
        mergedDataset = Dataset()
        mergedDataset.feature_dict = merged_feat_dict
        mergedDataset.modalities = merged_modalities
        return mergedDataset

    @staticmethod
    def merge_dict(dict1, dict2):
        merged = deepcopy(dict1)
        merged.update(dict2)
        return merged


import math, random
import gym
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import torch.autograd as autograd
import torch.nn.functional as F
import matplotlib.pyplot as plt

USE_CUDA = torch.cuda.is_available()
Variable = lambda *args, **kwargs: autograd.Variable(*args, **kwargs).cuda() if USE_CUDA else autograd.Variable(*args, **kwargs)

from collections import deque

env_id = "CartPole-v0"
env = gym.make(env_id)
# env = env.unwrapped

path = "/Users/saumya/Desktop/CriticalStates_results/"
results_dir = "vanillaDQN"

'''
Double DQN code adapted and modified from https://github.com/higgsfield/RL-Adventure/blob/master/2.double%20dqn.ipynb
'''

class ReplayBuffer(object):
    def __init__(self, capacity):
        self.buffer = deque(maxlen=capacity)

    def push(self, state, action, reward, next_state, done):
        state = np.expand_dims(state, 0)
        next_state = np.expand_dims(next_state, 0)

        self.buffer.append((state, action, reward, next_state, done))

    def sample(self, batch_size):
        state, action, reward, next_state, done = zip(*random.sample(self.buffer, batch_size))
        return np.concatenate(state), action, reward, np.concatenate(next_state), done

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

class DQN(nn.Module):
    def __init__(self, num_inputs, num_actions):
        super(DQN, self).__init__()

        self.layers = nn.Sequential(
            # nn.Linear(env.observation_space.shape[0], 128),
            # nn.ReLU(),
            # nn.Linear(128, 128),
            # nn.ReLU(),
            # nn.Linear(128, env.action_space.n)

            # Function approximator for Q function - modified to less hidden neurons
            nn.Linear(env.observation_space.shape[0], 32),
            nn.ReLU(),
            nn.Linear(32, 32),
            nn.ReLU(),
            nn.Linear(32, env.action_space.n)
        )

    def forward(self, x):
        return self.layers(x)

    def act(self, state, epsilon):
        """
        choose action using epsilon-greedy strategy
        """
        if random.random() > epsilon:
            state = Variable(torch.FloatTensor(state).unsqueeze(0), volatile=True)
            q_value = self.forward(state)
            action = q_value.max(1)[1].item()
        else:
            action = random.randrange(env.action_space.n)
        return action

def update_target(current_model, target_model):
    target_model.load_state_dict(current_model.state_dict())

def compute_td_loss(batch_size):
    """
    Compute the TD loss after sampling transitions(of size - "batch_size") from the replay buffer
    """
    state, action, reward, next_state, done = replay_buffer.sample(batch_size)

    state = Variable(torch.FloatTensor(np.float32(state)))
    next_state = Variable(torch.FloatTensor(np.float32(next_state)))
    action = Variable(torch.LongTensor(action))
    reward = Variable(torch.FloatTensor(reward))
    done = Variable(torch.FloatTensor(done))

    q_values = current_model(state)
    next_q_values = current_model(next_state)
    next_q_state_values = target_model(next_state)

    q_value = q_values.gather(1, action.unsqueeze(1)).squeeze(1)
    next_q_value = next_q_state_values.gather(1, torch.max(next_q_values, 1)[1].unsqueeze(1)).squeeze(1)
    expected_q_value = reward + gamma * next_q_value * (1 - done)

    loss = (q_value - Variable(expected_q_value.data)).pow(2).mean()

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    return loss

def plot(frame_idx, rewards, losses, iter):
    # clear_output(True)
    plt.figure(figsize=(20,5))
    plt.subplot(131)
    # plt.title('frame %s. reward: %s' % (frame_idx, np.mean(rewards[-10:])))
    plt.title('frame %s' % (frame_idx))
    plt.plot(rewards)
    plt.subplot(132)
    plt.title('loss')
    plt.plot(losses)
    plt.savefig(path+results_dir+"/cartpole_dqn_plots_iter_"+str(iter))

def load_model(model_path):
    current_model = DQN(env.observation_space.shape[0], env.action_space.n)
    current_model.load_state_dict(torch.load(model_path,map_location=torch.device('cpu')))

    return current_model

def play(model_path):
    """
    Play or rollout the learnt policy and observe the mean reward obtained over 1000 episodes
    """
    current_model = load_model(model_path)
    avg_test_reward = []
    for t in range(1000):
        # print('play: ',t)
        state = env.reset()
        done = False
        reward_per_episode = 0
        while not done:

            action = current_model.act(state, 0)
            next_state, reward, done, info = env.step(action)
            # env.render()
            reward_per_episode+=reward

            if done:
                # print('rewards: ',reward_per_episode)
                avg_test_reward.append(reward_per_episode)
                break
            else:
                state = next_state

    env.close()

    print(np.mean(avg_test_reward))

if __name__ == "__main__":
    ## Hyperparameters
    epsilon_start = 1.0
    epsilon_final = 0.01
    epsilon_decay = 500
    num_frames = 400000 # increased num of timesteps from 160000
    batch_size = 64
    gamma = 0.99
    update_target_net = 100
    learning_rate = 1e-4 # reduced learning rate from 1e-3

    epsilon_by_frame = lambda frame_idx: epsilon_final + (epsilon_start - epsilon_final) * math.exp(
        -1. * frame_idx / epsilon_decay)

    ## Running for 5 iteration to obtain a mean and std of the reward plots
    for iter in range(5):

        print("iteration: ",iter)

        current_model = DQN(env.observation_space.shape[0], env.action_space.n)
        target_model = DQN(env.observation_space.shape[0], env.action_space.n)

        if USE_CUDA:
            current_model = current_model.cuda()
            target_model = target_model.cuda()

        optimizer = optim.Adam(current_model.parameters(), lr = learning_rate)

        replay_buffer = ReplayBuffer(100000) # increased buffer size from 1000
        update_target(current_model, target_model)

        losses = []
        all_rewards = []
        episode_reward = 0
        ep_num = 0

        ## If the environment is solved is_win is set true
        is_win = False
        state = env.reset()
        for frame_idx in range(1, num_frames + 1):

            epsilon = epsilon_by_frame(frame_idx)
            action = current_model.act(state, epsilon)
            next_state, reward, done, _ = env.step(action)
            replay_buffer.push(state, action, reward, next_state, done)
            state = next_state
            episode_reward += reward

            if done:
                state = env.reset()
                all_rewards.append(episode_reward)
                episode_reward = 0
                ep_num+=1
                avg_reward = float(np.mean(all_rewards[-100:]))
                print('Best 100-episodes average reward', ep_num, avg_reward)

                ## Using the following "solving" criteria
                if len(all_rewards) >= 100 and avg_reward >= 198 and all_rewards[-1] > 198:
                    if not is_win:
                        is_win = True
                        torch.save(current_model.state_dict(), path+results_dir+'/CartPole_dqn_model_iter_'+str(iter))
                    print('Ran %d episodes best 100-episodes average reward is %3f. Solved after %d trials ✔' % (
                    ep_num, avg_reward, ep_num - 100))
                    last_saved = ep_num
                    torch.save(current_model.state_dict(),
                                path+results_dir+'/Final_CartPole_dqn_model_iter_' + str(
                                   iter))

            ## Update the loss
            if len(replay_buffer) > batch_size:
                loss = compute_td_loss(batch_size)
                losses.append(loss.item())

            if frame_idx % 200 == 0:
                plot(frame_idx, all_rewards, losses, iter)

            ## Update the target network
            if frame_idx % update_target_net == 0:
                update_target(current_model, target_model)

        ## Save the reward list - rewards obtained per episode
        np.save(path+results_dir+"/rewards_iter_"+str(iter),all_rewards)

        if not is_win:
            print('Did not solve after %d episodes' % ep_num)
            torch.save(current_model.state_dict(), path+results_dir+'/CartPole_dqn_model_iter_'+str(iter))

        # play(path+results_dir+'/CartPole_dqn_model_iter_'+str(iter))
        # play(path+results_dir+'/Final_CartPole_dqn_model_iter_' + str(iter))
        # Iteration: 0
        # 199.969
        # 200.0
        # iteration: 1
        # 200.0
        # 195.842
        # iteration: 2
        # 200.0
        # 182.442
        # iteration: 3
        # 200.0
        # 200.0
        # iteration: 4
        # 197.461
        # 199.972


# https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/

from typing import List

# Definition for a binary tree node.
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        if not preorder:
            return None

        if len(preorder) == 1:
            return TreeNode(preorder[0], None, None)

        root = TreeNode(preorder[0])
        leftLen = inorder.index(preorder[0])
        root.left = self.buildTree(preorder[1:1+leftLen], inorder[:leftLen])
        root.right = self.buildTree(preorder[leftLen+1:], inorder[leftLen+1:])

        return root


"""add admin flag to user

Revision ID: dd535b1f37a1
Revises: 4519159d3019
Create Date: 2019-01-06 13:39:21.042745

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = 'dd535b1f37a1'
down_revision = '4519159d3019'
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    with op.batch_alter_table('users', schema=None) as batch_op:
        batch_op.add_column(sa.Column('is_admin', sa.Boolean(), nullable=True))

    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    with op.batch_alter_table('users', schema=None) as batch_op:
        batch_op.drop_column('is_admin')

    # ### end Alembic commands ###


import customtkinter as ctk

class ConfirmDeleteOldestBackupDialog(ctk.CTkToplevel):
    def __init__(self, parent, controller, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)

        # Configure variables
        self.controller = controller
        self.label_text = "You are only allowed 10 backup files. If you save\nthis backup the oldest backup file will be deleted.\n\nAre you sure you want to continue with the backup?"

        # Configure window
        self.geometry("400x180")
        self.title = f"Confirm delete last backup."

        # Configure grid layout
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure((0, 1), weight=1)

        # Create label
        self.label = ctk.CTkLabel(self, text=self.label_text)
        self.label.grid(row=0, column=0, columnspan=2, padx=20, pady=20,
                        sticky="nsew")

        # Create button YES
        self.yes_button = ctk.CTkButton(self, text="Yes", command=lambda: self.controller.save_backup_dialog_event(input=True, dialog=self))
        self.yes_button.grid(row=1, column=0, padx=20, pady=20, sticky="nsew")

        # Create button NO
        self.no_button = ctk.CTkButton(self, text="Cancel", command=lambda: self.controller.save_backup_dialog_event(input=False, dialog=self))
        self.no_button.grid(row=1, column=1, padx=20, pady=20, sticky="nsew")


#coding: latin-1
#
# Exemple 5.5 dimensionnemnt approche suédoise
#
from geothermal_md import *
import numpy as np
from  matplotlib.pyplot import *
from time import *
# fichier de fonction g (Eskilson) tabuléees pour champ 2 x 2 pour b = 0.05,0.1,0.2,0.4,0.8
zo = 0
rb = 0.25/2
b = 0.05
H = 4
z = 1.8
Ht = H/rb
bt = b/rb
t = np.arange(0,1500,50)
Cp = 800
k = 0.26
rho = 1397
alp = k/(rho*Cp)
alm = alp*60
Fov = alm*t/rb**2
r = 0.25
rt = r/rb
zot = zo/rb
zt = z/rb
phi = 0
q = 400
qp = q/H
nt = len(Fov)
g1 = np.zeros(nt)
g2  = np.zeros(nt)
g3  = np.zeros(nt)
g4  = np.zeros(nt)
g5  = np.zeros(nt)
tic = time()
for i in range(0,nt):
    Fo = Fov[i]
    g1[i] = qp*G_function_fsc(Fo,rt,zt,Ht,zot)/k
toc = time() - tic
print ('fin du calcul de la fonction g',toc )
tic = time()
for i in range(0,nt):
    Fo = Fov[i]
    g2[i] =  qp*G_function_ring(Fo,rt,zt,Ht,zot,bt)/k
toc = time() - tic
print ('fin du calcul de la fonction g',toc )
tic = time()
for i in range(0,nt):
    Fo = Fov[i]
    g3[i] =  qp*G_function_spiral(Fo,rt,zt,phi,Ht,zot,bt)/k
toc = time() - tic
print ('fin du calcul de la fonction g',toc )
tic = time()
for i in range(0,nt):
    Fo = Fov[i]
    g4[i] =  qp*G_function_ils(Fo,rt)/k
toc = time() - tic
print ('fin du calcul de la fonction g',toc )
tic = time()
for i in range(0,nt):
    Fo = Fov[i]
    g5[i] =  qp*G_function_ics(Fo,rt)/k
toc = time() - tic
print ('fin du calcul de la fonction g',toc )
x = np.log(Fov)
#x = t
p1 = plot(x,g1,color = 'k',linewidth = 2,label = 'Man''s solid cylinder')
p2 = plot(x,g2,color = 'k',linestyle = 'None',marker = 'o',markersize=8,label = 'Cui ring''s model')
p3 = plot(x,g3,color = 'k',linestyle = 'None',marker = 'x',markersize=8,label = 'Park''s spiral model')
p4 = plot(x,g4,color = 'k',linestyle = 'None',marker = '+',markersize=8,label = 'ILS')
p5 = plot(x,g5,color = 'k',linestyle = 'None',marker = 's',markersize=8,label = 'ICS')
legend(fontsize = 14)
ax = gca()
grid(True,which='both')
fts = 16
ftst = 14

xlabel(' log(Fo)',fontname='Times new Roman',fontsize = fts)
ylabel(' $ \Delta T$',fontsize = fts,fontname='Times new Roman')
xticks(fontsize=ftst)
yticks(fontsize=ftst)
tight_layout()
show()


from django.urls import path
from .views import ListingsView, ListingView, SearchView

# Declare the URL for the listings app here.

urlpatterns = [
    path('', ListingsView.as_view(),name="ListALL"),
    path('search', SearchView.as_view()),
    path('<slug>', ListingView.as_view()),      # Used for lising a particular view, not by PK(id) but by Slug field.
]


import numpy as np
import pandas as pd
import itertools
from sklearn import metrics
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import cross_val_score
# models that are being considered
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import StackingClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.svm import SVC
from sklearn.neural_network import MLPClassifier

"""
def grid_search_cross_validation(x_train, y_train, grid, model):
    gridCV = GridSearchCV(model, grid, cv=10)
    gridCV.fit(x_train, y_train.T.squeeze())
    return gridCV.best_params_

def get_svc_best_params(x_train, y_train):
    kernel = ['poly', 'sigmoid']
    degree = [3, 4, 5]
    tol = [ 10**(-3)]
    grid = {
        'kernel' : kernel,
        'degree' : degree,
    }
    res = grid_search_cross_validation(x_train, y_train, grid, SVC())
    print(res)
"""

def print_accuracy_scores(performance_data):
    print('Accuracy scores:')
    for i, data in enumerate(performance_data):
        model_name = data[0]
        pred = data[1]
        test = data[2]
        acc = metrics.accuracy_score(y_true=pred, y_pred=test, normalize=True)
        print(model_name + ' accuracy: ', acc)

def print_f1_score(performance_data):
    print('f1 scores:')
    for i, data in enumerate(performance_data):
        model_name = data[0]
        pred = data[1]
        test = data[2]
        acc = metrics.f1_score(y_true=pred, y_pred=test, average='macro')
        print(model_name + ' f1 score: ', acc)

def cross_validation_acc_score(x, y, clf):
    skfold = StratifiedKFold(n_splits=10).split(x, y)
    score = cross_val_score(clf, x, y, cv=skfold)
    print('Accuracy {}%'.format(score.mean()*100))

"""
def find_model():
    label='Vote'
    x_train = pd.read_csv("x_train.csv", header=0)
    y_train = pd.read_csv("y_train.csv", squeeze=True, header=None)
    x_valid = pd.read_csv("x_valid.csv", header=0)
    y_valid = pd.read_csv("y_valid.csv", squeeze=True, header=None)
    x_test = pd.read_csv("x_test.csv", header=0)
    y_test = pd.read_csv("y_test.csv", squeeze=True, header=None)
    #get_random_forest_best_params(x_train, y_train)
    x = x_train
    y = y_train

    # Best parameters for Random Tree Forest: {'criterion': 'gini', 'max_depth': 30, 'max_features': 'auto', 'min_samples_split': 2, 'n_estimators': 50}
    rand_forest_clf = RandomForestClassifier(criterion='gini', max_depth=50, min_samples_split=5, n_estimators=50)
    cross_validation_acc_score(x, y, rand_forest_clf)
    rand_forest_clf.fit(x, y)
    prediction_rand_forest = rand_forest_clf.predict(x_valid)

    # Best parameters for SVC {'degree': 4, 'kernel': 'poly'}
    svm_poly_clf = SVC(kernel='poly', degree=4, probability=True)
    svm_poly_clf.fit(x, y)
    prediction_svm_poly = svm_poly_clf.predict(x_valid)

    # Multi-layer perceptron classifier
    perceptron_clf = MLPClassifier(activation="relu", alpha=0.1, hidden_layer_sizes=(10, 10, 10),
                                   learning_rate="constant", max_iter=2000)
    perceptron_clf.fit(x, y)
    prediction_perceptron = perceptron_clf.predict(x_valid)

    estimators = [
        ('Random Forest',  RandomForestClassifier(criterion='gini', max_depth=50, min_samples_split=5, n_estimators=50)),
        ('SVC', SVC(kernel='poly', degree=4, probability=True)),
        ('Percepton', MLPClassifier(activation="relu", alpha=0.1, hidden_layer_sizes=(10, 10, 10),
                                   learning_rate="constant", max_iter=2000))
    ]
    blend_clf = StackingClassifier(estimators)
    blend_clf.fit(x, y)
    prediction_blend = blend_clf.predict(x_valid)
    # evaluate and plot confusion matrices

    performance_data = [('Random Forest', prediction_rand_forest, y_valid),
                        ('SVM Polinomial Kernel', prediction_svm_poly, y_valid),
                        ('Perceptron', prediction_perceptron, y_valid),
                        ('Blending ', prediction_blend, y_valid)
                        ]
    print_accuracy_scores(performance_data)
    print_f1_score(performance_data)

    prediction = prediction_blend
    parties = np.unique(prediction)

    num_votes_for_party = lambda party: len([vote for vote in prediction if vote == party])
    list_of_parties = [(party, num_votes_for_party(party)) for party in parties]
    num_votes = len(y_test.index)

    winner = max(list_of_parties, key=lambda item: item[1])
    print('Party with most probable majority of votes')
    print(winner[0], ':', winner[1], ',', winner[1] * 100 / num_votes, '%')

    # 2. Division of voters between the parties
    print('Amount of votes per party')
    for party_votes in sorted(list_of_parties, key=lambda votes: votes[1], reverse=True):
        print(party_votes[0], ':', party_votes[1], ',', party_votes[1] * 100 / num_votes, '%')
"""

if __name__ == '__main__':
    label = 'Vote'
    x_train = pd.read_csv("x_train.csv", header=0)
    y_train = pd.read_csv("y_train.csv", squeeze=True, header=None)
    x_valid = pd.read_csv("x_valid.csv", header=0)
    y_valid = pd.read_csv("y_valid.csv", squeeze=True, header=None)
    x_test = pd.read_csv("x_test.csv", header=0)
    y_test = pd.read_csv("y_test.csv", squeeze=True, header=None)
    # get_random_forest_best_params(x_train, y_train)
    x = x_train
    y = y_train

    estimators = [
        ('Random Forest',  RandomForestClassifier(criterion='gini', max_depth=50, min_samples_split=5, n_estimators=50)),
        ('SVC', SVC(kernel='poly', degree=4, probability=True)),
        ('Percepton', MLPClassifier(activation="relu", alpha=0.1, hidden_layer_sizes=(10, 10, 10),
                                   learning_rate="constant", max_iter=2000))
    ]
    blend_clf = StackingClassifier(estimators)
    blend_clf.fit(x, y)
    prediction = blend_clf.predict(x_test)
    # evaluate and plot confusion matrices

    parties = np.unique(prediction)

    num_votes_for_party = lambda party: len([vote for vote in prediction if vote == party])
    list_of_parties = [(party, num_votes_for_party(party)) for party in parties]
    num_votes = len(y_test.index)

    winner = max(list_of_parties, key=lambda item: item[1])
    print('Party with most probable majority of votes')
    print(winner[0], ':', winner[1], ',', winner[1] * 100 / num_votes, '%')

    # 2. Division of voters between the parties
    print('Amount of votes per party')
    for party_votes in sorted(list_of_parties, key=lambda votes: votes[1], reverse=True):
        print(party_votes[0], ':', party_votes[1], ',', party_votes[1] * 100 / num_votes, '%')

    performance_data = [('Blending ', prediction, y_test)]
    print_accuracy_scores(performance_data)
    print_f1_score(performance_data)


#!/usr/bin/env python3
import glob
import os.path
import re
import statistics
import sys
from collections import defaultdict
from typing import List, Dict

"""
USAGE:
./simple_spec_summary.py   # all files in /spec/result/
./simple_spec_summary.py 1 10   # result 1-10 from /spec/result/
./simple_Spec_summary.py <list> <of> <csv> <files>
"""

def draw_table(table: List[List[str]], hline_after=()):
    column_width = defaultdict(lambda: 0)
    for row in table:
        for i, col in enumerate(row):
            column_width[i] = max(column_width[i], len(col))
    txt = []
    for i, row in enumerate(table):
        for j, col in enumerate(row):
            txt.append(col + ' ' * (column_width[j] - len(col)))
            if j < len(row) - 1:
                txt.append(' | ')
        txt.append('\n')
        if i in hline_after:
            # txt.append('-' * (sum(column_width.values()) + 3 * len(row) - 3) + '\n')
            txt.append('-|-'.join('-' * v for k, v in sorted(column_width.items(), key=lambda x: x[0])) + '\n')
    return ''.join(txt)

def load_spec_files(files: List[str]) -> Dict[str, Dict[str, List[float]]]:
    """
    :param files:
    :return: {benchmark type: {benchmark name: [list, of, results]}}
    """
    results = {}
    for fname in files:
        if not os.path.exists(fname):
            print('MISSING FILE', fname)
            continue
        with open(fname, 'r') as f:
            text = f.read()
            name = [l[12:-1] for l in text.split('\n') if l.startswith('"test name: ')][0]
            if name == 'llvm-o3-typegraph':
                name = 'llvm-o3-typro'
            if name not in results:
                results[name] = {}
            table = text.split('"Selected Results Table"')[1].split('"Run number:"')[0]
            for l in table.split('\n'):
                if l.startswith('4'):
                    elements = l.split(',')
                    if elements[2]:
                        bench_name = elements[0]
                        if re.match(r'\d{3}\.\w+', bench_name):
                            bench_name = bench_name.split('.', 1)[1]
                        if bench_name not in results[name]:
                            results[name][bench_name] = []
                        results[name][bench_name].append(float(elements[2]))
    return results

def summarize_spec_files(files: List[str]):
    results = load_spec_files(files)
    assert 'llvm-o3-typro' in results, 'No typro runs!'
    assert 'llvm-o3-ref' in results, 'No reference runs!'
    benchmarks = list(sorted(results['llvm-o3-typro']))
    table = [['Benchmark', 'Typro runtime (stdev)', 'Ref runtime (stdev)', 'Overhead']]
    for bench in benchmarks:
        runtime_typro = sum(results['llvm-o3-typro'][bench]) / len(results['llvm-o3-typro'][bench])
        runtime_ref = sum(results['llvm-o3-ref'][bench]) / len(results['llvm-o3-ref'][bench])
        stdev_typro = statistics.stdev(results['llvm-o3-typro'][bench]) / runtime_typro
        stdev_ref = statistics.stdev(results['llvm-o3-ref'][bench]) / runtime_ref
        overhead = runtime_typro / runtime_ref - 1
        table.append([
            bench,
            f'{runtime_typro:5.1f} s     (+-{stdev_typro*100:4.1f}%)',
            f'{runtime_ref:5.1f} s   (+-{stdev_ref*100:4.1f}%)',
            f'{overhead * 100:5.2f}%'])
    print(draw_table(table, (0,)))

if __name__ == '__main__':
    if len(sys.argv) == 3 and re.match(r'\d+', sys.argv[1]) and re.match(r'\d+', sys.argv[2]):
        files = []
        for i in range(int(sys.argv[1]), int(sys.argv[2]) + 1):
            files.append(f'/spec/result/CINT2006.{i:03d}.ref.csv')
            files.append(f'/spec/result/CFP2006.{i:03d}.ref.csv')
        summarize_spec_files(files)
    elif len(sys.argv) > 1:
        summarize_spec_files(sys.argv[1:])
    else:
        summarize_spec_files(glob.glob('/spec/result/*.ref.csv'))


import sqlite3

#Her oprettes en forbindelse til databasefilen
#Hvis filen ikke findes, vil sqlite oprette en ny tom database.
con = sqlite3.connect('start.db')
print('Database åbnet')

try:
    con.execute("""CREATE TABLE personer (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		navn STRING,
        alder INTEGER)""")
    print('Tabel oprettet')
except Exception as e:
    print('Tabellen findes allerede')

c = con.cursor()
c.execute('INSERT INTO personer (navn,alder) VALUES (?,?)', ("Hans", 38))
c.execute('INSERT INTO personer (navn,alder) VALUES (?,?)', ("Kim", 37))

#Efter at have ændret i databasen skal man kalde funktionen commit.
con.commit()

#Denne variabel bruges til at modtage input fra brugeren
inp = ''

print('')
print('Kommandoer: ')
print('  vis - Viser alle personer i databasen')
print('  ny  - Opret ny person')
print('  q   - Afslut program')

while not inp.startswith('q'):
    inp = input('> ')

    if inp == 'vis':
        c = con.cursor()
        c.execute('SELECT navn,alder FROM personer')

        for p in c:
            print('{} er {} år'.format(p[0], p[1]))

    elif inp == 'ny':
        n = input('Indtast navn: ')
        a = input('Indtast alder: ')
        c = con.cursor()
        c.execute('INSERT INTO personer (navn,alder) VALUES (?,?)', (n, a))
        con.commit()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Apr 18 21:28:29 2021

@author: apolloseeds
"""

from dataset import *

import matplotlib.pyplot as plt
import numpy as np
from scipy.io import loadmat
from sklearn import model_selection
from toolbox_02450 import train_neural_net, draw_neural_net, visualize_decision_boundary
import torch
from scipy import stats
from toolbox_02450 import feature_selector_lr, bmplot, rlr_validate, mcnemar
from toolbox_02450 import train_neural_net, draw_neural_net, visualize_decision_boundary

N2, M2 = contX.shape

def trainANN(X,y,h, K = 10): #returns the optimal h (number of hidden units)
    CV = model_selection.KFold(K,shuffle=True)
    n_replicates = 1        # number of networks trained in each k-fold
    max_iter = 10000

    # Define the model structure

    # The lambda-syntax defines an anonymous function, which is used here to
    # make it easy to make new networks within each cross validation fold
    model = lambda: torch.nn.Sequential(
                        torch.nn.Linear(M2, h), #M features to H hiden units
                        # 1st transfer function, either Tanh or ReLU:
                        torch.nn.Tanh(),                            #torch.nn.ReLU(),
                        torch.nn.Linear(h, 1) # H hidden units to 1 output neuron
                        )

    loss_fn = torch.nn.MSELoss() # notice how this is now a mean-squared-error loss

    print('Training model of type:\n\n{}\n'.format(str(model())))
    errors = [] # make a list for storing generalizaition error in each loop

    for (k, (train_index, test_index)) in enumerate(CV.split(X,y)):
        print('\nCrossvalidation fold: {0}/{1}'.format(k+1,K))

        # Extract training and test set for current CV fold, convert to tensors
        X_train = torch.Tensor(X[train_index,:])
        y_train = torch.Tensor(y[train_index])
        X_test = torch.Tensor(X[test_index,:])
        y_test = torch.Tensor(y[test_index])

        for i in range(0, len(h)):
            #Iterate over every h
            testedH = h[i]
            # Train the net on training data
            net, final_loss, learning_curve = train_neural_net(model,
                                                               loss_fn,
                                                               X=X_train,
                                                               y=y_train,
                                                               n_replicates=n_replicates,
                                                               max_iter=max_iter)

            print('\n\tBest loss: {}\n'.format(final_loss))

            # Determine estimated class labels for test set
            y_sigmoid = net(X_test)
            y_test_est = (y_sigmoid>.5).type(dtype=torch.uint8)

            # Determine errors and errors
            y_test = y_test.type(dtype=torch.uint8)

            e = y_test_est != y_test
            error_rate = (sum(e).type(torch.float)/len(y_test)).data.numpy()
            errors.append(error_rate) # store error rate for current CV fold

        optimalHIndex = errors.index(min(errors))
        optimalH = h[optimalHIndex]

        # Print the average classification error rate
        print('\nEstimated generalization error, RMSE: {0}'.format(round(np.sqrt(np.mean(errors)), 4)))

        return optimalH

def annRegression(X_train, X_test, y_train, y_test, hRange, K = 10):
    # Parameters for neural network classifier
    n_replicates = 1        # number of networks trained in each k-fold
    max_iter = 10000         # stop criterion 2 (max epochs in training)
    loss_fn = torch.nn.MSELoss() # notice how this is now a mean-squared-error loss

    opt_hidden_unit = trainANN(X_train, y_train, hRange, K)
    model = lambda: torch.nn.Sequential(
                        torch.nn.Linear(M, opt_hidden_unit), #M features to H hiden units
                        torch.nn.Tanh(),   # 1st transfer function,
                        torch.nn.Linear(opt_hidden_unit, 1), # H hidden units to 1 output neuron
                        )
    # print('Training model of type:\n\n{}\n'.format(str(model())))

    X_train = torch.Tensor(X_train)
    y_train = torch.Tensor(y_train)
    X_test = torch.Tensor(X_test)
    y_test = torch.Tensor(y_test)

    # Train the net on training data
    net, final_loss, learning_curve = train_neural_net(model,
                                                    loss_fn,
                                                    X=X_train,
                                                    y=y_train,
                                                    n_replicates=n_replicates,
                                                    max_iter=max_iter)

    print('\n\tBest loss: {}\n'.format(final_loss))

    # Determine estimated class labels for test set
    y_test_est = net(X_test)

    # Determine errors and errors
    se = (y_test_est.float()-y_test.float())**2 # squared error
    mse = (sum(se).type(torch.float)/len(y_test)).data.numpy() #mean
    return opt_hidden_unit, mse, y_test_est

C = 2
# Normalize data
annX = stats.zscore(contX)

# Parameters for neural network classifier
h = 1 # number of hidden units, !!!!SELECT A RANGE BY TESTING

serumC = np.array(np.asarray(X[:, 7]), dtype=int)
#y_rings = np.array(np.asarray(rings), dtype=np.int).reshape(-1, 1)

K = 5
lambdas = np.linspace(0.01, 10, 1000)
inner_cvf = 10
CV = model_selection.KFold(K, shuffle=True)
coefficient_norm = np.zeros(K)
# Parameters for neural network classifier
hRange = range(1, 8)
n_replicates = 2        # number of networks trained in each k-fold
max_iter = 10000         # stop criterion 2 (max epochs in training)

square_err_regression_base = np.empty(K)
square_err_regression_RLR = np.empty(K)
square_err_regression_ANN = np.empty(K)
regression_RLR_opt_lambdas = np.empty(K)
regression_opt_hidden_units = np.empty(K)

error_rate_classification_base = np.empty(K)
error_rate_classification_logistic = np.empty(K)
error_rate_classification_ANN = np.empty(K)
classification_opt_hidden_units = np.empty(K)
classification_opt_lambdas = np.empty(K)
w_est_logistic_arr = np.empty((K, X.shape[1]))

y_est_Reg_ANN = []
y_est_Reg_RLR = []
y_est_claf_ANN = []
y_est_claf_logistic = []
y_sex_real = []
y_rings_real = []

for k, (train_index, test_index) in enumerate(CV.split(annX,serumC)):
        X_train = annX[train_index,:]
        X_test = annX[test_index,:]
        y_train = serumC[train_index]
        y_test = serumC[test_index]
        """
        y_rings_train = y_rings[train_index]
        y_rings_test = y_rings[test_index]
        y_sex_real.append(y_sex_test)
        y_rings_real.append(y_rings_test)
        """

        regression_opt_hidden_unit, ANN_mse, y_est_ANN_regression =  annRegression(X_train, X_test, y_train, y_test, hRange, inner_cvf)
        regression_opt_hidden_units[k] = regression_opt_hidden_unit
        square_err_regression_ANN[k] = ANN_mse
        y_est_Reg_ANN.append(y_est_ANN_regression)

print("square_err_regression_ANN: ", square_err_regression_ANN)


import random

usernames_array = ["giraffe", "raccoon", "ant", "tiger", "sheep", "deer", "panda", "liger", "fox", "hippo", "alligator",
                   "dog", "dolphin", "eagle", "zebra", "rabbit", "bear", "monkey", "leopard", "frog", "squirrel",
                   "elephant", "bee", "duck", "kangaroo", "penguin"]

username_groupID = ['1', '1', '2', '2', '2', '3', '3', '3', '4', '4', '4', '5', '5', '5', '6', '6', '6', '7', '7',
                    '7', '8', '8', '8', '9', '9', '9']

dictionary = dict(zip(usernames_array, username_groupID))
# print(dictionary)

group1 = {}
group2 = {}
group3 = {}
group4 = {}
group5 = {}
group6 = {}

length = len(username_groupID)
#print(length)

def swapelement(group, id_rep, name_rep):
    for ID in group:
        if ID not in group6:
            group6[ID] = group[ID]
            del group[ID]
            break
    group[id_rep] = name_rep;

print("Total number of animals: "+str(len(dictionary.keys())))
random.shuffle(usernames_array)

class randomGroupGenerator():
    def creategroup(self):
        group_list = []
        length = len(username_groupID)
        while length > 0:
            name = usernames_array.pop()
            ID = dictionary[name]
            if len(list(group1.keys()))< 6 and ID not in group1.keys():
                group1[ID] = name

            elif len(list(group2.keys()))< 4 and ID not in group2.keys():
                group2[ID] = name

            elif len(list(group3.keys()))<4 and ID not in group3.keys():
                group3[ID] = name

            elif len(list(group4.keys()))<4 and ID not in group4.keys():
                group4[ID] = name

            elif len(list(group5.keys()))<4 and ID not in group5.keys():
                group5[ID] = name

            elif len(list(group6.keys())) < 4 and ID not in group6.keys():
                group6[ID] = name

            else:
                if ID not in group1.keys():
                    swapelement(group1, ID, name)
                elif ID not in group2.keys():
                    swapelement(group2, ID, name)
                elif ID not in group3.keys():
                    swapelement(group2, ID, name)
                elif ID not in group4.keys():
                    swapelement(group2, ID, name)
                elif ID not in group5.keys():
                    swapelement(group2, ID, name)
            length -= 1

        #convert dictionaries into list
        group_list.append([x for x in group1.values()])
        group_list.append([x for x in group2.values()])
        group_list.append([x for x in group3.values()])
        group_list.append([x for x in group4.values()])
        group_list.append([x for x in group5.values()])
        group_list.append([x for x in group6.values()])

        return group_list;

# creategroup()
# print(group1)
# print(group2)
# print(group3)
# print(group4)
# print(group5)
# print(group6)


# Import libraries
import numpy as np
import pandas as pd
pd.options.mode.chained_assignment = None
from sqlalchemy import create_engine
from googlesearch import search
from tqdm import tqdm
tqdm.pandas()

# Read data
df = pd.read_csv('data/user-item-interactions.csv')
df_content = pd.read_csv('data/articles.csv')
del df['Unnamed: 0']
del df_content['Unnamed: 0']

# <----- CLEAN DATA [start] ----->
# Remove duplicate articles
df_content = df_content.drop_duplicates(keep='first').reset_index(drop=True)
df_content = df_content.drop_duplicates(subset='article_id', keep='first').reset_index(drop=True)

# Format matching columns to same type
df = df.astype({'article_id': int})

# Make User-id column in df to identify users
user_id_dict = dict()
i=0
for email in df.email:
    if email not in user_id_dict:
        user_id_dict[email] = i
        i+=1
df['user_id'] = df.email.apply(lambda x: user_id_dict[x])
df.drop('email', axis=1, inplace=True)

# Fill in missing document descriptions with empty strings
df_content.doc_description[df_content.doc_description.isnull()] = ''
# <----- CLEAN DATA [finished] ----->

# Merge data-sets on article id
df_merged = df.drop('title', axis=1).merge(df_content[['article_id', 'doc_full_name', 'doc_description']], on='article_id', how='outer')

# Fill in missing document titles
no_title_ids = df_merged.article_id[df_merged.doc_full_name.isnull()].unique().tolist()
for id in no_title_ids:
    title = df.title[df.article_id == id].tolist()[0]
    df_merged.doc_full_name[df_merged.article_id == id] = title

# Fill in missing descriptions with empty string
df_merged.doc_description[df_merged.doc_description.isnull()] = ''

# Make subset of merged dataframe and drop all duplicates
df_subset = df_merged[['article_id', 'doc_full_name', 'doc_description']].drop_duplicates(keep='first').reset_index(drop=True)

# Extract article links through google searches for all articles in the subset dataframe
doc_identifier = df_subset.doc_full_name + ' ' + df_subset.doc_description
def extract_link(text):
    try:
        link = list(search(text, tld="com", num=1, stop=1))[0]
    except:
        link = "https://www.google.com"
    return link
df_subset['link'] = doc_identifier.progress_apply(extract_link)

# Distribute links to all rows of the merged dataframe
df_merged['link'] = df_merged.article_id.apply(lambda x: df_subset.link[df_subset.article_id==x].tolist()[0])

# Save data to database
engine = create_engine('sqlite:///data/data.db')
df_merged.to_sql('user-article-interactions', engine, index=False, if_exists='replace')


import pickle
import json
import yaml
import numpy as np
import torch
import torch.optim as optim
import time
from data_manager import DataManager
from model import BiLSTMCRF
from utils import f1_score, get_tags, format_result
from torch.utils.tensorboard import SummaryWriter

writer = SummaryWriter(log_dir='./tensorboard/bioes')

class ChineseNER(object):

    def __init__(self, entry="train"):
        self.load_config()
        self.__init_model(entry)

    def __init_model(self, entry):
        if entry == "train":
            self.train_manager = DataManager(batch_size=self.batch_size, tags=self.tags)
            self.total_size = len(self.train_manager.batch_data)
            data = {
                "batch_size": self.train_manager.batch_size,
                "input_size": self.train_manager.input_size,
                "vocab": self.train_manager.vocab,
                "tag_map": self.train_manager.tag_map,
            }
            self.save_params(data)
            self.dev_manager = DataManager(batch_size=60, data_type="dev")
            # 验证集
            # self.dev_batch = self.dev_manager.iteration()

            self.model = BiLSTMCRF(
                tag_map=self.train_manager.tag_map,
                batch_size=self.batch_size,
                vocab_size=len(self.train_manager.vocab),
                dropout=self.dropout,
                embedding_dim=self.embedding_size,
                hidden_dim=self.hidden_size,
            )
            self.model = self.model.cuda()
            self.restore_model()
        elif entry == "predict" or "evaluate":
            # python main.py predict
            data_map = self.load_params()
            input_size = data_map.get("input_size")
            self.tag_map = data_map.get("tag_map")
            self.vocab = data_map.get("vocab")
            print('input_size',input_size)
            print('tag_map',self.tag_map)
            self.model = BiLSTMCRF(
                tag_map=self.tag_map,
                vocab_size=input_size,
                embedding_dim=self.embedding_size,
                hidden_dim=self.hidden_size
            )
            self.model = self.model.cuda()
            self.test_manager = DataManager(batch_size=60, data_type="dev")
            self.restore_model()
    # 加载配置项
    def load_config(self):
        try:
            fopen = open("models/config.yml")
            config = yaml.load(fopen)
            fopen.close()
        except Exception as error:
            print("Load config failed, using default config {}".format(error))
            fopen = open("models/config.yml", "w")
            config = {
                "embedding_size": 300,
                "hidden_size": 128,
                "batch_size": 30,
                "dropout":0.5,
                "model_path": "models/",
                "tags": ["TREATMENT", "BODY","SIGNS","CHECK","DISEASE"]
            }
            yaml.dump(config, fopen)
            fopen.close()
        self.embedding_size = config.get("embedding_size")
        self.hidden_size = config.get("hidden_size")
        self.batch_size = config.get("batch_size")
        self.model_path = config.get("model_path")
        self.tags = config.get("tags")
        self.dropout = config.get("dropout")

    # 保存模型各种训练参数
    def restore_model(self):
        try:
            self.model.load_state_dict(torch.load(self.model_path + "params_6all.pkl"))
            print("model restore success!")
        except Exception as error:
            print("model restore faild! {}".format(error))

    # 保存模型超参数
    def save_params(self, data):
        with open("models/data_6all.pkl", "wb") as fopen:
            pickle.dump(data, fopen)

    # 加载模型超参数
    def load_params(self):
        with open("models/data_6all.pkl", "rb") as fopen:
            data_map = pickle.load(fopen)
        return data_map

    def train(self):
        optimizer = optim.Adam(self.model.parameters(),weight_decay=0.002,lr=0.0000004) # 0.000001
        # optimizer = optim.SGD(self.model.parameters(), lr=0.00000008,weight_decay=0.001,momentum=0.9) #4e-7
        scheduler_lr = optim.lr_scheduler.ReduceLROnPlateau(optimizer,mode='min',factor=0.5,patience=2,cooldown=5,verbose=True,min_lr=1e-8,eps=1e-8)
        best_loss = 240
        lossList = [0] * self.total_size
        for epoch in range(268,401):
            losses = []
            index = 0
            startTime = time.process_time()
            for batch in self.train_manager.get_batch():
                start = time.process_time()
                index += 1
                self.model.zero_grad()

                sentences, tags, length = zip(*batch)
                # lenght 是句子的原本长度
                # shape (batch_size,max.len(sentence) (20,332) batch_size 和 每个batch最长句子的长度
                sentences_tensor = torch.tensor(sentences, dtype=torch.long).cuda()
                tags_tensor = torch.tensor(tags, dtype=torch.long).cuda()
                length_tensor = torch.tensor(length, dtype=torch.long).cuda()

                loss = self.model.neg_log_likelihood(sentences_tensor, tags_tensor, length_tensor)
                losses.append(loss.cpu().item())
                progress = ("█"*int(index * 60 / self.total_size)).ljust(60)
                loss.backward()
                optimizer.step()
                # torch.save(self.model.state_dict(), self.model_path + 'params_6all.pkl')
                end = time.process_time()
                dur = end - start
                print("""epoch [{}] |{}| {}/{}\n\tloss {:.3f}\t\tlast_loss {:.3f}\t\ttime {}\t\tbest_avg_loss {:.3f}""".format(
                    epoch, progress, index, self.total_size, loss.cpu().tolist()[0],lossList[index - 1],str(dur),best_loss
                    )
                )
                lossList[index - 1] = loss.cpu().item()
                print("-" * 90)
            endTime = time.process_time()
            totalTime = endTime - startTime
            avg_loss = np.mean(losses)
            # 保存最好的模型
            if avg_loss < best_loss:
                best_loss = avg_loss
                torch.save(self.model.state_dict(), self.model_path + 'params_6all.pkl')
            writer.add_scalar('BiLstm_CRF:avg_loss-epoch', avg_loss, epoch)
            print('epoch ',epoch,'   avg_loss ', avg_loss,'   total_time ',totalTime)
            if epoch % 5 == 0:
                self.evaluate(epoch/5,manager=self.dev_manager)
            print("-"*100)
            scheduler_lr.step(avg_loss)
        writer.close()
    # train: BODY 7507, SIGNS 6355, CHECK 6965, DISEASE 474, TREATMENT 805
    # test:
    # 计算f1,评估模型
    def evaluate(self,epoch,manager,add_scalar = True):
        print('正在开始评估')
        all_origins = all_founds = all_rights = 0
        for tag in self.tags:
            origins = founds = rights = 0
            for batch in manager.get_batch():
                sentences, labels, length = zip(*batch)
                _, paths = self.model(sentences)
                origin, found, right = f1_score(labels, paths, tag, self.model.tag_map)
                origins += origin
                founds += found
                rights += right
            all_origins += origins
            all_founds += founds
            all_rights += rights
            recall = 0. if origins == 0 else (rights / origins)
            precision = 0. if founds == 0 else (rights / founds)
            f1 = 0. if recall + precision == 0 else (2 * precision * recall) / (precision + recall)
            print("\t{}\torigins:{}\t\t\tfounds:{}\t\t\trights:{}".format(tag, origins, founds, rights))
            print("\t\t\trecall:{}\tprecision:{}\tf1:{}".format(recall, precision, f1))
            if add_scalar:
                tag_epoch = tag + '-5epoch'
                writer.add_scalars(tag_epoch,{
                    'recall':recall,
                    'precision':precision,
                    'f1':f1
                }, epoch)
        all_recall = 0. if all_origins == 0 else (all_rights / all_origins)
        all_precision = 0. if all_founds == 0 else (all_rights / all_founds)
        all_f1 = 0. if all_recall + all_precision == 0 else (2 * all_precision * all_recall) / (all_precision + all_recall)
        print("\tall_origins:{}\t\t\tall_founds:{}\t\t\tall_rights:{}".format(all_origins, all_founds, all_rights))
        print("\tall_recall:{}\tall_precision:{}\tall_f1:{}".format(all_recall, all_precision, all_f1))
        if add_scalar:
            writer.add_scalars("ALL-5epoch", {
                'all_recall': all_recall,
                'all_precision': all_precision,
                'all_f1': all_f1
            }, epoch)
        print('评估结束')
        return all_recall, all_precision, all_f1
    # 预测方法
    def predict(self, input_str=""):
        if not input_str:
            input_str = input("请输入文本: ")
        # 获取输入句子所有汉字的在vocab的索引
        input_vec = [self.vocab.get(i, 0) for i in input_str]
        # convert to tensor
        sentences = torch.tensor(input_vec,dtype=torch.long).view(1, -1)
        sentences = sentences.cuda()
        # paths 预测出来的标签索引 shape 为 [1,1]
        _, paths = self.model(sentences)

        entities = []
        # "tags": ["ORG", "PER"]
        for tag in self.tags:
            tags = get_tags(paths[0], tag, self.tag_map)
            entities += format_result(tags, input_str, tag)
        print(entities)
        print(json.dumps(entities,indent=4,ensure_ascii=False))
        return entities

if __name__ == "__main__":
    entry = input('请输入train or predict or evaluate: ')
    while entry:
        if entry == 'train':
            cn = ChineseNER("train")
            cn.train()
            break
        elif entry == 'predict':
            cn = ChineseNER("predict")
            while True:
                inputText = input("请输入文本(q退出): ")
                if inputText != 'q':
                    cn.predict(inputText)
                else:
                    break
            break
        elif entry == 'evaluate':
            cn = ChineseNER("evaluate")
            cn.evaluate(epoch=0,manager=cn.test_manager,add_scalar=False)
            break
        else:
            print("请输入正确的命令(train or predict or evaluate)")
            entry = input('请输入train or predict or evaluate: ')


import requests
from bs4 import BeautifulSoup
import time
import plotly
import numpy as np
import pandas as pd
import datetime as dt
import cufflinks as cf

import subprocess
import traceback
from sys import exit
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import base64
import pickle
import os.path
from googleapiclient.discovery import build

# Set working directory to the project folder
os.chdir("<replace_with_path_to_project_folder>")

def extract_price(game_page):
    """Finds and returns the price of a game on a playstation store
    web page. If no price is found, returns null.
    """
    soup = BeautifulSoup(game_page.text, features="html.parser")
    price = soup.find('span', class_='psw-h3')
    if price is None:
        return np.nan
    else:
        # Remove '£' from price and return the value
        return float(price.get_text().replace('£', ''))

def get_latest_non_null(row):
    """Returns the most recent, non-null price of a row.
    Returns -1 if all prices in row are null.
    """
    # Value returned if no non-null value exists
    price = -1
    # Loops through the row backwards and returns first non-null value
    for element in reversed(row):
        # Check element is not null (null values dont equal themselves)
        if element == element:
            price = element
            break
    return price

def create_message(sender, to, subject, price_drop, failures, nans):
    """Create, Encode and return Gmail email.

    Checks if there are rows for the price_drop, failures and nans
    dataframes. If a dataframe has rows, it is included as a table
    in the html body of them email.
    """
    message = MIMEMultipart()
    # html contains the HTML code for the email body
    html = """

      """
    # If price_drop df has rows, its table is included in the email
    if price_drop.shape[0] > 0:
        html += '
Price Drops:
'
        html += price_drop.to_html(escape=False, index = False, justify = 'center')
    # If failures df has rows, its table is included in the email
    if len(failures) > 0:
        html += '

Failed to Scrape:
'
        html += '
'.join(failures)
    # If nans df has rows, its table is included in the email
    if nans.shape[0] > 0:
        html += '

Price Not Found:
'
        html += nans.to_html()
    html += """

        """

    part1 = MIMEText(html, 'html')
    message.attach(part1)

    message['to'] = to
    message['from'] = sender
    message['subject'] = subject
    # Message encoded as required for the Gmail API
    raw_message = base64.urlsafe_b64encode(message.as_string().encode("utf-8"))
    return {'raw': raw_message.decode("utf-8")}

# Wait 10 seconds in case computer was asleep (give time for
# an internet connection to be established)
time.sleep(10)

# Attempt to retrieve google.com to confirm internet connection,
# wait 5 minutes and try again if there is a error (no connection).
# If an error occurs the second time, a Pop-up error message is
# displayed and script is terminated.
try:
    requests.get('https://www.google.com/')
except:
    time_of_error = time.time()
    while time.time() - time_of_error < 300:
        time.sleep(1)
    try:
        requests.get('https://www.google.com/')
    except:
        # Create Mac OS popup error message
        applescript = """
        display dialog "Playstation_scraper could not connect to the internet." ¬
        with title "Internet Connection Error" ¬
        with icon caution ¬
        buttons {"OK"}
        """
        subprocess.call("osascript -e '{}'".format(applescript), shell=True)
        exit('exit')

# The game price data is stored in Game_prices.csv. Each row
# corresponds to a different game. The first column ('game')
# contains the name of the game. The second column ('game id')
# contains the unique ID for the game on the playstation store.
# The remaining columns contain the price of the game on
# each day the script was run. The header for each column is
# the date the price was found. When the script is run for the
# first time there will be no price data (there will only be
# the 'game' and 'game id' columns)
df = pd.read_csv('game_prices.csv', ',', index_col='game')

# Convert the date column headers to date-time format
category_headers = df.columns[:1].tolist()
date_headers = df.columns[1:]
converted_date_headers = pd.to_datetime(date_headers, format='%d/%m/%Y').date.tolist()
df.columns = category_headers + converted_date_headers

# The full url for a game is the base url with the game ID added at
# the end.
base_url = 'https://store.playstation.com/en-gb/product/'

# time_delay is the seconds waited between subsequent GET requests
time_delay = 10
# game_price records the price of each game today
game_price = []
time_last_request = time.time()
# failures records the game url's which result in an error when requested.
failures = []
# The game_id column of df defines the game_id for each game.
# The code loops through this and for each game id it makes a
# get request and scrapes the price of that game from its webpage.
for game_id in df.game_id:
    # Waits between subsequent GET requests.
    while time.time() - time_last_request < time_delay:
        time.sleep(1)

    try:
        # full game url is base_url + game id
        game_page = requests.get(base_url + game_id)
        time_last_request = time.time()
        game_price.append(extract_price(game_page))
    # If GET request or price extraction failed, wait 300 seconds
    # and try again
    except:
        time_error = time.time()
        while time.time() - time_error < 300:
            time.sleep(1)
        try:
            game_page = requests.get(base_url + game_id)
            time_last_request = time.time()
            game_price.append(extract_price(game_page))
        except:
            # both GET requests failed so record as failure
            failures.append(base_url + game_id)
            # Record game price today as null
            game_price.append(np.nan)

# Add todays game prices as new column in df
date = dt.date.today()
df[date] = game_price

# Below generates a separate plot of price over time for each game in df.
n_rows = df.shape[0]
# plotly layout used to define the layout of the plot.
layout1 = cf.Layout(xaxis=dict(autorange=True, dtick='M1'),
                    yaxis=dict(title=dict(standoff=0, text='')),
                    height=150 * n_rows,
                    width=1200,
                    margin=dict(pad=0, t=100, l=0.9, r=0.9, b=1),
                    showlegend=False,
                    title=dict(text='Price of Games on Playstation Store',
                               x=0.5, y=0.99, xanchor='center')
                )
# df is transposed so each column is a game, with the price on
# each dates in the rows. The game_id column in excluded
# by .iloc[1:,]
plotting_df = df.T.iloc[1:, ]
# Sub-plots will be in 2 columns, this is defined by the shape
# paramater, which takes a tuple (rows, columns). To calculate
# the rows we divide the number of games (total rows in df) by 1.9 and
# round the answer. e.g. if there are 7 games, we divide by 1.9 and
# round up giving us 4 rows. We use 1.9 because if we divide by 2 Python
# sometimes rounds numbers ending in 0.5 down rather than up.
shape1 = (round(n_rows / 1.9), 2)
# Plot price variation over time for each game
fig = plotting_df.iplot(subplots=True, shape=shape1,
                        subplot_titles=True, vertical_spacing=0.08,
                        horizontal_spacing=0.1, layout=layout1,
                        asFigure=True, color='orange', width=2)
fig.update_layout(hovermode='x unified')

# Fixes the opacity of the lines so all lines are fully visible
# (by default cufflinks gave variable opacity to the lines).
for i in fig['data']:
    i['line']['color'] = "rgba(255, 153, 51, 1.0)"
# Sets color and style of the subplot titles
for i in fig['layout']['annotations']:
    i['font'] = dict(size=14, color='orange')
# Adds date selector buttons (e.g. 'last month') to plots
fig.update_xaxes(
    rangeselector = dict(
        yanchor='bottom',
        buttons=list([
            dict(count=1, label="1m", step="month", stepmode="backward"),
            dict(count=6, label="6m", step="month", stepmode="backward"),
            dict(count=1, label="YTD", step="year", stepmode="todate"),
            dict(count=1, label="1y", step="year", stepmode="backward"),
            dict(step="all")
                       ])
    )
)
# Set y axis range
fig.update_yaxes(nticks=8, rangemode='tozero', range=[0,60])
fig.write_html("Game Prices.html")

# The next section identifies price drops and prices that couldn't
# be found (nan_prices)
price_drops = []
nan_prices = []

# Excludes dataframes with data from only 1 date and only runs if the latest
# data is from today
if (df.shape[1] > 2):
    # We want to find the latest price before todays data so
    # we exclude todays column and the game_id column
    # This is to account for any NAN values in the data.
    df_prices_before_today = df.iloc[:, 1:-1]
    # Most recent non-null price for each game is found. Note that if
    # no non-null old price exists, the most recent price will be -1
    most_recent_price = [get_latest_non_null(row)
                         for row in df_prices_before_today.to_numpy()]
    # Loops through the games and identifies any price drops
    for game, game_id, new_price, old_price in zip(df.index, df.game_id,
                                                   game_price, most_recent_price):

        # Price drops only calculated if there is a valid price for
        # today (the value is not null) and a valid last price to
        # compare it to (most_recent_price is not -1)
        if (new_price == new_price) & (old_price > 0):
            price_delta = old_price - new_price
            # Only notify price drops larger than £0.5
            if price_delta > 0.5:
                html_link = '
' \
                            + game + '
'
                price_drops.append([html_link, old_price, new_price, price_delta])
        # Also tracks any prices today that have returned a nan value
        elif new_price != new_price:
            nan_prices.append([game, base_url + game_id])
    # Replace nan prices today in df with the latest non-null value
    # (assume price has stayed the same if no price was found today)
    for price_today, game, most_recent_price in zip(game_price,
                                                    df.index.values.tolist(),
                                                    most_recent_price):
        if (price_today != price_today) & (most_recent_price >0):
            df.loc[game,date] = most_recent_price

drops = len(price_drops)
fails = len(failures)
nans = len(nan_prices)

# Checks if there is anything to email (will email price drops,
# request failures and nan prices).
if drops + fails + nans > 0:
    # Builds subject line for email including number of drops. failures
    # or null prices
    subject = 'Rupe Playstation Price Drop Alerts: '
    if drops > 0:
        subject += str(drops) + ' Drops, '
    if fails > 0:
        subject += str(fails) + ' Failures, '
    if nans > 0:
        subject += str(nans) + ' Price Not Found'
    # Create dataframe of price drop info to be emailed as a table
    price_drop_df = pd.DataFrame(price_drops,
                                 columns=['Game', 'Old Price',
                                          'New Price', 'Price Drop']
                            )
    price_drop_df = price_drop_df.sort_values(by=['Price Drop'],
                                              ascending = False)
    # Create dataframe of null prices (no price found) to be emailed
    # as a table
    nan_prices_df = pd.DataFrame(nan_prices, columns=['Game', 'Game_ID'])
    # Create email using Gmail API
    try:
        # Create email message
        mail = create_message('me', 'ruperthart92@gmail.com', subject,
                              price_drop_df, failures, nan_prices_df)
        # Check that a token.pickle exists containing the gmail
        # credentials and load them
        if os.path.exists('token.pickle'):
            with open('token.pickle', 'rb') as token:
                creds = pickle.load(token)
        # Create the gmail service using credentials and send message
        service = build('gmail', 'v1', credentials=creds)
        message = (service.users().messages().send(userId='me', body=mail)
                   .execute())
        print('email sent')
    except:
        # Mac OS error alert in case gmail email fails to send
        applescript = """
        display dialog "Playstation_scraper email failed to send." ¬
        with title "Playstation_scraper: Email Failed" ¬
        with icon caution ¬
        buttons {"OK"}
        """
        subprocess.call("osascript -e '{}'".format(applescript), shell=True)
        print('email failed')
        traceback.print_exc()

# Convert date time headers to strings with the same format as the
# original csv (this is the format that excel uses when you save as csv)
dates = df.columns[1:].tolist()
dates_as_strings = [date_obj.strftime('%d/%m/%Y') for date_obj in dates]
df.columns = df.columns[:1].tolist() + dates_as_strings

df.to_csv('game_prices.csv')
print('ran on ', date)


def see_and_say_util(number_str):
    i = 0
    result = []
    while i < len(number_str):
        j = i
        count = 0
        while j < len(number_str) and number_str[j] == number_str[i]:
            j += 1
            count += 1
        result.append(str(count))
        result.append(number_str[i])
        i = j
    return ''.join(result)

def see_and_say(n):
    cur = 1
    result = [cur]
    for i in range(n):
        next = see_and_say_util(str(cur))
        result.append(next)
        cur = next

    return result

if __name__ == "__main__":
    print(see_and_say(3))
    print(see_and_say(5))
    print(see_and_say(7))


#This tool is useful whenever you get some rough data deliverables
#I used this whenever I had ~100 individually names folders, all containing a single shapefile with the same name, "Data.shp"
#The goal was to rename the shapes from Data.shp, to the parent folder's name.shp

import os, arcpy
from subprocess import Popen
import subprocess
from os import listdir
from os.path import isfile, join

myDir = ("Path to Folder, containing all of the sub folders")
shpList = []
csvList = []
newshpList = []

for root, dirs, files in os.walk(myDir):
    for file in files:
        if file.endswith(".shp"):
            shpList.append(os.path.join(root, file))

for rawShapefile in shpList:
    folderName = str(os.path.basename(os.path.dirname(rawShapefile)))
    renameShapefile = str(myDir)+"\\"+folderName+"\\"+folderName+".shp"
    print(renameShapefile)
    arcpy.Rename_management(rawShapefile,renameShapefile)


import numpy as np
import matplotlib.pyplot as plt

def plot_with_exponential_averaging(x, y, label, alpha):
    y_ema = [y[0],]
    for y_i in y[1:]:
        y_ema.append(y_ema[-1] * alpha + y_i * (1 - alpha))

    p = plt.plot(x, y_ema, label=label)

    plt.plot(x, y, color=p[0].get_color(), alpha=0.2)

def plot_train_result(result, label="", alpha=0.95, save_path="./", threshold=None):
    rewards = [r['r'] for r in result]
    lengths = [r['l'] for r in result]

    plot_with_exponential_averaging(np.cumsum(lengths), rewards, label, alpha)
    plt.axhline(y=threshold if threshold else int(max(rewards)*1.1), color='grey', linestyle='-')
    plt.xlabel("Training Steps")
    plt.ylabel("Episode Reward")

    plt.legend()
    plt.title(label)
    plt.savefig(save_path)
    plt.cla()


#!/usr/local/python3/bin/python3
import sys
sys.path.append("..")
import tushare as ts
import re
import datetime
import basicdata.basic_mgr as sk
import time
import os
import pandas as pd

g_update_newest=False #True|False
#是否下载最新的概念，一般不需要

g_ctcode_name=None
#g_ctcode_name['TS56']='电改'

g_tscode_concept=None
#g_tscode_concept['000008.SZ']={'id':['TS56','TS59'],'name':['电改','特斯拉']}

def get_ctname_by_tscode(pro, tscode):
    global g_tscode_concept
    if g_tscode_concept is None:
        init_tscode_concept(pro)
    return g_tscode_concept[tscode]['name']

def get_ctcode_by_tscode(pro, tscode):
    global g_tscode_concept
    if g_tscode_concept is None:
        init_tscode_concept(pro)
    return g_tscode_concept[tscode]['id']

def init_tscode_concept(pro):
    global g_tscode_concept
    global g_update_newest
    if g_tscode_concept is None:
        g_tscode_concept = {}
        ts_codes=sk.get_tscodes(pro)
        for i in range(len(ts_codes)):
            ts_code=ts_codes[i]
            path='./concept-data/'+ts_code+'.concept.csv'
            if g_update_newest == False and os.path.exists(path) == True:
                conceptdf=pd.read_csv(path)
            else:
                conceptdf=pro.concept_detail(ts_code=ts_code)
                if conceptdf is not None:
                    conceptdf.to_csv(path)
                time.sleep(1)
                print("download", path)
            if conceptdf is not None:
                conceptids=conceptdf['id'].values.tolist()
                conceptnames=conceptdf['concept_name'].values.tolist()
                g_tscode_concept[ts_code]={'id':conceptids, 'name':conceptnames}

def get_concept_map(pro):
    global g_ctcode_name
    if g_ctcode_name is None:
        init_ctcode_name(pro)
    return g_ctcode_name

def get_name(pro, code):
    global g_ctcode_name
    if g_ctcode_name is None:
        init_ctcode_name(pro)
    return g_ctcode_name[code]

def init_ctcode_name(pro):
    global g_ctcode_name
    if g_ctcode_name is None:
        g_ctcode_name = {}
        conceptdf=pro.concept(src='ts')
        conceptcodes=conceptdf['code'].values.tolist()
        conceptnames=conceptdf['name'].values.tolist()
        for i  in range(len(conceptcodes)):
            g_ctcode_name[conceptcodes[i]]= conceptnames[i]

if __name__== '__main__':
    pro = ts.pro_api('08aedc1cc54171e54a64bbe834ec1cb45026fa2ab39e9e4cb8208cad')
    init_ctcode_name(pro)
    print(g_ctcode_name)
    print(get_name(pro, 'TS2'))
    print(get_ctcode_by_tscode(pro, '600848.SH'))
    #conceptdf.to_csv('./concept.csv')


# -*- coding: utf-8 -*-
from numpy import zeros
from copy import deepcopy
from numpy import cast
from numpy import dot
from numpy import linalg

class SSMOperation:

	def localNormalize(self, M):
		M = deepcopy(M)

		maxValue = M.max()

		for rowIdx in range(M.shape[0]):
			M[rowIdx] = map(lambda value: value / maxValue, M[rowIdx])

		return M

	def enhance(self, M, length):
		M = deepcopy(M)
		forwardM = zeros(M.shape)
		backwardM = zeros(M.shape)

		for i in range(M.shape[0]):

			for j in range(M.shape[1]):

				#forwardM[i, j] = (M[slice(i - self.__ENLEN / 2, i + self.__ENLEN / 2),
							#slice(j - self.__ENLEN / 2, j + self.__ENLEN / 2)].trace()) / self.__ENLEN

				forwardM[i, j] = (M[slice(i, i + length), slice(j, j + length)].trace()) / length

				startI = i - length
				startJ = j - length

				if startI < 0:
					startI = 0

				if startJ <0:
					startJ = 0

				#backwardM[i, j] = (M[slice(startI, i), slice(startJ, j)].trace()) / self.__ENLEN

		#M = (forwardM + backwardM) / 2
		return forwardM

	def secondOrder(self, M):
		M = deepcopy(M)

		secondM = zeros(M.shape)

		for i in range(secondM.shape[0]):
			for j in range(secondM.shape[1]):
				rowVec = M[i, :]
				colVec = M[:, j]

				secondM[i, j] = dot(rowVec, colVec) / (linalg.norm(rowVec) * linalg.norm(colVec))

		return secondM

		#return dot(M, M)
		#return dot(M, M)

	def masker(self, M, threshold):
		M = deepcopy(M)

		for rowIdx in range(M.shape[0]):
			M[rowIdx] = map(lambda similarity: (similarity >= threshold), M[rowIdx])

		return cast['int'](M)