#!/usr/bin/env python
# coding: utf-8

# # 生成电视剧剧本
#
# 在这个项目中，你将使用 RNN 创作你自己的[《辛普森一家》](https://zh.wikipedia.org/wiki/%E8%BE%9B%E6%99%AE%E6%A3%AE%E4%B8%80%E5%AE%B6)电视剧剧本。你将会用到《辛普森一家》第 27 季中部分剧本的[数据集](https://www.kaggle.com/wcukierski/the-simpsons-by-the-data)。你创建的神经网络将为一个在 [Moe 酒馆](https://simpsonswiki.com/wiki/Moe's_Tavern)中的场景生成一集新的剧本。
#
# ## 获取数据
# 我们早已为你提供了数据`./data/Seinfeld_Scripts.txt`。我们建议你打开文档来看看这个文档内容。
#
# >* 第一步，我们来读入文档，并看几段例子。
# * 然后，你需要定义并训练一个 RNN 网络来生成新的剧本！

# In[1]:

"""
DON'T MODIFY ANYTHING IN THIS CELL
"""
# load in data
import helper
data_dir = './data/Seinfeld_Scripts.txt'
text = helper.load_data(data_dir)

# ## 探索数据
# 使用 `view_line_range` 来查阅数据的不同部分，这个部分会让你对整体数据有个基础的了解。你会发现，文档中全是小写字母，并且所有的对话都是使用 `\n` 来分割的。

# In[2]:

view_line_range = (0, 10)

"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
import numpy as np

print('Dataset Stats')
print('Roughly the number of unique words: {}'.format(len({word: None for word in text.split()})))

lines = text.split('\n')
print('Number of lines: {}'.format(len(lines)))
word_count_line = [len(line.split()) for line in lines]
print('Average number of words in each line: {}'.format(np.average(word_count_line)))

print()
print('The lines {} to {}:'.format(*view_line_range))
print('\n'.join(text.split('\n')[view_line_range[0]:view_line_range[1]]))

# ---
# ## 实现预处理函数
# 对数据集进行的第一个操作是预处理。请实现下面两个预处理函数：
#
# - 查询表
# - 标记符号
#
# ### 查询表
# 要创建词嵌入，你首先要将词语转换为 id。请在这个函数中创建两个字典：
#
# - 将词语转换为 id 的字典，我们称它为 `vocab_to_int`
# - 将 id 转换为词语的字典，我们称它为 `int_to_vocab`
#
# 请在下面的元组中返回这些字典
#  `(vocab_to_int, int_to_vocab)`

# In[3]:

import problem_unittests as tests
from collections import Counter

def create_lookup_tables(text):
    """
    Create lookup tables for vocabulary
    :param text: The text of tv scripts split into words
    :return: A tuple of dicts (vocab_to_int, int_to_vocab)
    """
    # TODO: Implement Function
    word_counts = Counter(text)
    sorted_vocab = sorted(word_counts, key=word_counts.get, reverse=True)
    int_to_vocab = {ii: word for ii, word in enumerate(sorted_vocab)}
    vocab_to_int = {word: ii for ii, word in int_to_vocab.items()}
    # return tuple
    return (vocab_to_int, int_to_vocab)

"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
tests.test_create_lookup_tables(create_lookup_tables)

# ### 标记符号的字符串
# 我们会使用空格当作分隔符，来将剧本分割为词语数组。然而，句号和感叹号等符号使得神经网络难以分辨“再见”和“再见！”之间的区别。
#
# 实现函数 `token_lookup` 来返回一个字典，这个字典用于将 “!” 等符号标记为 “||Exclamation_Mark||” 形式。为下列符号创建一个字典，其中符号为标志，值为标记。
#
# - period ( . )
# - comma ( , )
# - quotation mark ( " )
# - semicolon ( ; )
# - exclamation mark ( ! )
# - question mark ( ? )
# - left parenthesis ( ( )
# - right parenthesis ( ) )
# - dash ( -- )
# - return ( \n )
#
# 这个字典将用于标记符号并在其周围添加分隔符（空格）。这能将符号视作单独词汇分割开来，并使神经网络更轻松地预测下一个词汇。请确保你并没有使用容易与词汇混淆的标记。与其使用 “dash” 这样的标记，试试使用“||dash||”。

# In[4]:

def token_lookup():
    """
    Generate a dict to turn punctuation into a token.
    :return: Tokenized dictionary where the key is the punctuation and the value is the token
    """
    # TODO: Implement Function
    token_dict = {
        '.': '||period||',
        ',': '||comma||',
        '"': '||quotation_mark||',
        ';': '||semicolon||',
        '!': '||exclamation_mark||',
        '?': '||question_mark||',
        '(': '||left_parentheses||',
        ')': '||right_parentheses||',
        '-': '||dash||',
        '\n': '||return||'
    }

    return token_dict

"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
tests.test_tokenize(token_lookup)

# ## 预处理并保存所有数据
# 运行以下代码将预处理所有数据，并将它们保存至文件。建议你查看`helpers.py` 文件中的 `preprocess_and_save_data` 代码来看这一步在做什么，但是你不需要修改`helpers.py`中的函数。

# In[5]:

"""
DON'T MODIFY ANYTHING IN THIS CELL
"""
# pre-process training data
helper.preprocess_and_save_data(data_dir, token_lookup, create_lookup_tables)

# # 检查点
# 这是你遇到的第一个检点。如果你想要回到这个 notebook，或需要重新打开 notebook，你都可以从这里开始。预处理的数据都已经保存完毕。

# In[6]:

"""
DON'T MODIFY ANYTHING IN THIS CELL
"""
import helper
import problem_unittests as tests

int_text, vocab_to_int, int_to_vocab, token_dict = helper.load_preprocess()

# ## 创建神经网络
# 在本节中，你会构建 RNN 中的必要 Module，以及 前向、后向函数。
#
# ### 检查 GPU 访问权限

# In[7]:

"""
DON'T MODIFY ANYTHING IN THIS CELL
"""
import torch

# Check for a GPU
train_on_gpu = torch.cuda.is_available()
if not train_on_gpu:
    print('No GPU found. Please use a GPU to train your neural network.')

# ## 输入
# 让我们开始预处理输入数据。我们会使用 [TensorDataset](http://pytorch.org/docs/master/data.html#torch.utils.data.TensorDataset) 来为数据库提供一个数据格式；以及一个 [DataLoader](http://pytorch.org/docs/master/data.html#torch.utils.data.DataLoader), 该对象会实现 batching，shuffling 以及其他数据迭代功能。
#
# 你可以通过传入 特征 和目标 tensors 来创建 TensorDataset，随后创建一个 DataLoader 。
# ```
# data = TensorDataset(feature_tensors, target_tensors)
# data_loader = torch.utils.data.DataLoader(data,
#                                           batch_size=batch_size)
# ```
#
# ### Batching
#  通过 `TensorDataset` 和 `DataLoader` 类来实现  `batch_data` 函数来将 `words` 数据分成 `batch_size` 批次。
#
# >你可以使用 DataLoader 来分批 单词, 但是你可以自由设置 `feature_tensors` 和 `target_tensors` 的大小以及 `sequence_length`。
#
# 比如，我们有如下输入:
# ```
# words = [1, 2, 3, 4, 5, 6, 7]
# sequence_length = 4
# ```
#
# 你的第一个 `feature_tensor` 会包含:
# ```
# [1, 2, 3, 4]
# ```
# 随后的 `target_tensor` 会是接下去的一个字符值:
# ```
# 5
# ```
# 那么，第二组的`feature_tensor`, `target_tensor` 则如下所示:
# ```
# [2, 3, 4, 5]  # features
# 6             # target
# ```

# In[8]:

from torch.utils.data import TensorDataset, DataLoader

def batch_data(words, sequence_length, batch_size):
    """
    Batch the neural network data using DataLoader
    :param words: The word ids of the TV scripts
    :param sequence_length: The sequence length of each batch
    :param batch_size: The size of each batch; the number of sequences in a batch
    :return: DataLoader with batched data
    """
    # TODO: Implement function
    num_features = len(words) - sequence_length
    train_x = np.zeros((num_features, sequence_length), dtype = int)
    train_y = np.zeros(num_features)

    for i in range(num_features):
        train_x[i] = words[i:i+sequence_length]
        train_y[i] = words[i+sequence_length]

    data = TensorDataset(torch.from_numpy(np.array(train_x, np.int64)), torch.from_numpy(np.array(train_y,np.int64)))
    dataloader = DataLoader(data, batch_size = batch_size, shuffle = True)
    return dataloader

# there is no test for this function, but you are encouraged to create
# print statements and tests of your own
words = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
length = 3
loader = batch_data(np.array(words), length, 2)

for feature, target in loader:
    print("Feature is : "+ str(feature))
    print("Target is : "+str(target))

# ### 测试你的 dataloader
#
# 你需要改写下述代码来测试 batching 函数，改写后的代码会现在的比较类似。
#
# 下面，我们生成了一些测试文本数据，并使用了一个你上面写 dataloader 。然后，我们会得到一些使用`sample_x`输入以及`sample_y`目标生成的文本。
#
# 你的代码会返回如下结果(通常是不同的顺序，如果你 shuffle 了你的数据):
#
# ```
# torch.Size([10, 5])
# tensor([[ 28,  29,  30,  31,  32],
#         [ 21,  22,  23,  24,  25],
#         [ 17,  18,  19,  20,  21],
#         [ 34,  35,  36,  37,  38],
#         [ 11,  12,  13,  14,  15],
#         [ 23,  24,  25,  26,  27],
#         [  6,   7,   8,   9,  10],
#         [ 38,  39,  40,  41,  42],
#         [ 25,  26,  27,  28,  29],
#         [  7,   8,   9,  10,  11]])
#
# torch.Size([10])
# tensor([ 33,  26,  22,  39,  16,  28,  11,  43,  30,  12])
# ```
#
# ### 大小
# 你的 sample_x 应该是 `(batch_size, sequence_length)`的 大小 或者是(10, 5)， sample_y 应该是 一维的: batch_size (10)。
#
# ### 值
#
# 你应该也会发现 sample_y, 是 test_text 数据中的*下一个*值。因此，对于一个输入的序列 `[ 28,  29,  30,  31,  32]` ，它的结尾是 `32`, 那么其相应的输出应该是 `33`。

# In[9]:

# test dataloader

test_text = range(50)
t_loader = batch_data(test_text, sequence_length=5, batch_size=10)

data_iter = iter(t_loader)
sample_x, sample_y = data_iter.next()

print(sample_x.shape)
print(sample_x)
print()
print(sample_y.shape)
print(sample_y)

# ---
# ## 构建神经网络
# 使用 PyTorch [Module class](http://pytorch.org/docs/master/nn.html#torch.nn.Module) 来实现一个 循环神经网络 RNN。你需要选择一个 GRU 或者 一个 LSTM。为了完成循环神经网络。为了实现 RNN，你需要实现以下类:
#  - `__init__` - 初始化函数
#  - `init_hidden` - LSTM/GRU 隐藏组昂泰的初始化函数
#  - `forward` - 前向传播函数
#
# 初始化函数需要创建神经网络的层数，并保存到类。前向传播函数会使用这些网络来进行前向传播，并生成输出和隐藏状态。
#
# 在该流程完成后，**该模型的输出是 *最后的* 文字分数结果** 对于每段输入的文字序列，我们只需要输出一个单词，也就是，下一个单词。
#
# ### 提示
#
# 1. 确保 lstm 的输出会链接一个 全链接层，你可以参考如下代码 `lstm_output = lstm_output.contiguous().view(-1, self.hidden_dim)`
# 2. 你可以通过 reshape 模型最后输出的全链接层，来得到最终的文字分数:
#
# ```
# # reshape into (batch_size, seq_length, output_size)
# output = output.view(batch_size, -1, self.output_size)
# # get last batch
# out = output[:, -1]
# ```

# In[10]:

import torch.nn as nn

class RNN(nn.Module):

    def __init__(self, vocab_size, output_size, embedding_dim, hidden_dim, n_layers, dropout=0.5):
        """
        Initialize the PyTorch RNN Module
        :param vocab_size: The number of input dimensions of the neural network (the size of the vocabulary)
        :param output_size: The number of output dimensions of the neural network
        :param embedding_dim: The size of embeddings, should you choose to use them
        :param hidden_dim: The size of the hidden layer outputs
        :param dropout: dropout to add in between LSTM/GRU layers
        """
        super(RNN, self).__init__()
        # TODO: Implement function

        # set class variables
        self.output_size = output_size
        self.n_layers = n_layers
        self.hidden_dim = hidden_dim
        self.vocab_size = vocab_size
        self.embedding_dim = embedding_dim
        self.dropout = nn.Dropout(dropout)

        # define model layers
        self.embedding = nn.Embedding(vocab_size, embedding_dim)
        self.lstm = nn.LSTM(embedding_dim, hidden_dim, n_layers, dropout = dropout, batch_first=True)

        self.fc = nn.Linear(hidden_dim, output_size)

    def forward(self, nn_input, hidden):
        """
        Forward propagation of the neural network
        :param nn_input: The input to the neural network
        :param hidden: The hidden state
        :return: Two Tensors, the output of the neural network and the latest hidden state
        """
        # TODO: Implement function

        # return one batch of output word scores and the hidden state
        batch_size = nn_input.size(0)
        nn_input = nn_input.long()
        embeds = self.embedding(nn_input)
        lstm_out , hidden = self.lstm(embeds,hidden)
        output = self.dropout(lstm_out)
        output = self.fc(output)

        output = output.view(batch_size, -1, self.output_size)
        out_final = output[:, -1]
        return out_final, hidden

    def init_hidden(self, batch_size):
        '''
        Initialize the hidden state of an LSTM/GRU
        :param batch_size: The batch_size of the hidden state
        :return: hidden state of dims (n_layers, batch_size, hidden_dim)
        '''
        # Implement function

        # initialize hidden state with zero weights, and move to GPU if available
        weight = next(self.parameters()).data

        if train_on_gpu:
            hidden = (weight.new(self.n_layers, batch_size , self.hidden_dim).zero_().cuda(),
                      weight.new(self.n_layers, batch_size , self.hidden_dim).zero_().cuda())
        else:
            hidden = (weight.new(self.n_layers, batch_size , self.hidden_dim).zero_(),
                      weight.new(self.n_layers, batch_size , self.hidden_dim).zero_())

        return hidden

"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
tests.test_rnn(RNN, train_on_gpu)

# ### 定义前向及后向传播
#
# 通过你实现的 RNN 类来进行前向及后项传播。你可以在训练循环中，不断地调用如下代码来实现：
# ```
# loss = forward_back_prop(decoder, decoder_optimizer, criterion, inp, target)
# ```
#
# 函数中需要返回一个批次以及其隐藏状态的loss均值，你可以调用一个函数`RNN(inp, hidden)`来实现。记得，你可以通过调用`loss.item()` 来计算得到该loss。
#
# **如果使用 GPU，你需要将你的数据存到 GPU 的设备上。**

# In[11]:

def forward_back_prop(rnn, optimizer, criterion, inp, target, hidden):
    """
    Forward and backward propagation on the neural network
    :param decoder: The PyTorch Module that holds the neural network
    :param decoder_optimizer: The PyTorch optimizer for the neural network
    :param criterion: The PyTorch loss function
    :param inp: A batch of input to the neural network
    :param target: The target output for the batch of input
    :return: The loss and the latest hidden state Tensor
    """

    # TODO: Implement Function

    # move data to GPU, if available
    if train_on_gpu:
        inp, target = inp.cuda(), target.cuda()

    # perform backpropagation and optimization
    hidden = tuple([each.data for each in hidden])
    rnn.zero_grad()
    output, hidden = rnn(inp, hidden)
    loss = criterion(output, target)
    loss.backward()

    nn.utils.clip_grad_norm_(rnn.parameters(), 5)
    optimizer.step()
    # return the loss over a batch and the hidden state produced by our model
    return loss.item(), hidden

# Note that these tests aren't completely extensive.
# they are here to act as general checks on the expected outputs of your functions
"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
tests.test_forward_back_prop(RNN, forward_back_prop, train_on_gpu)

# ## 神经网络训练
#
# 神经网络结构完成以及数据准备完后，我们可以开始训练网络了。
#
# ### 训练循环
#
# 训练循环是通过 `train_decoder` 函数实现的。该函数将进行 epochs 次数的训练。模型的训练成果会在一定批次的训练后，被打印出来。这个“一定批次”可以通过`show_every_n_batches` 来设置。你会在下一节设置这个参数。

# In[12]:

"""
DON'T MODIFY ANYTHING IN THIS CELL
"""

def train_rnn(rnn, batch_size, optimizer, criterion, n_epochs, show_every_n_batches=100):
    batch_losses = []

    rnn.train()

    print("Training for %d epoch(s)..." % n_epochs)
    for epoch_i in range(1, n_epochs + 1):

        # initialize hidden state
        hidden = rnn.init_hidden(batch_size)

        for batch_i, (inputs, labels) in enumerate(train_loader, 1):

            # make sure you iterate over completely full batches, only
            n_batches = len(train_loader.dataset)//batch_size
            if(batch_i > n_batches):
                break

            # forward, back prop
            loss, hidden = forward_back_prop(rnn, optimizer, criterion, inputs, labels, hidden)
            # record loss
            batch_losses.append(loss)

            # printing loss stats
            if batch_i % show_every_n_batches == 0:
                print('Epoch: {:>4}/{:<4}  Loss: {}\n'.format(
                    epoch_i, n_epochs, np.average(batch_losses)))
                batch_losses = []

    # returns a trained rnn
    return rnn

# ### 超参数
#
# 设置并训练以下超参数:
# -  `sequence_length`，序列长度
# -  `batch_size`，分批大小
# -  `num_epochs`，循环次数
# -  `learning_rate`，Adam优化器的学习率
# -  `vocab_size`，唯一标示词汇的数量
# -  `output_size`，模型输出的大小
# -  `embedding_dim`，词嵌入的维度，小于 vocab_size
# -  `hidden_dim`， 隐藏层维度
# -  `n_layers`， RNN的层数
# -  `show_every_n_batches`，打印结果的频次
#
# 如果模型没有获得你预期的结果，调整 `RNN`类中的上述参数。

# In[13]:

# Data params
# Sequence Length
sequence_length = 10   # of words in a sequence
# Batch Size
batch_size = 256

# data loader - do not change
train_loader = batch_data(int_text, sequence_length, batch_size)

# In[16]:

# Training parameters
# Number of Epochs
num_epochs = 25
# Learning Rate
learning_rate = 0.001

# Model parameters
# Vocab size
vocab_size = len(vocab_to_int)
# Output size
output_size = vocab_size
# Embedding Dimension
embedding_dim = 200
# Hidden Dimension
hidden_dim = 300
# Number of RNN Layers
n_layers = 2

# Show stats for every n number of batches
show_every_n_batches = 500

# ### 训练
# 下一节，通过预处理数据来训练神经网络。如果你的loss结果不好，可以通过调整超参数来修正。通常情况下，大的隐藏层及层数会带来比较好的效果，但同时也会消耗较长的时间来训练。
# > **你应该努力得到一个低于3.5的loss**
#
# 你也可以试试不同的序列长度，该参数表明模型学习的范围大小。

# In[17]:

"""
DON'T MODIFY ANYTHING IN THIS CELL
"""

# create model and move to gpu if available
rnn = RNN(vocab_size, output_size, embedding_dim, hidden_dim, n_layers, dropout=0.5)
if train_on_gpu:
    rnn.cuda()

# defining loss and optimization functions for training
optimizer = torch.optim.Adam(rnn.parameters(), lr=learning_rate)
criterion = nn.CrossEntropyLoss()

# training the model
trained_rnn = train_rnn(rnn, batch_size, optimizer, criterion, num_epochs, show_every_n_batches)

# saving the trained model
helper.save_model('./save/trained_rnn', trained_rnn)
print('Model Trained and Saved')

# ### 问题: 你如何决定你的模型超参数？
# 比如，你是否试过不同的 different sequence_lengths 并发现哪个使得模型的收敛速度变化？那你的隐藏层数和层数呢？你是如何决定使用这个网络参数的？

# **答案:** (在这里写下)
#
# sequence_length = 10
# 我参考了一些论文后发现10是一个较为常用的参数，因此选择了10。
#
# batch_size = 256
# batch_size如果太小的话，训练结果可能会很差；batch_size值增加，能提高训练速度，但是可能收敛结果变差。我尝试了128,256和512，发现256能使得模型的收敛速度最佳。
#
# num_epochs = 25
# 我尝试了10,15,20和25，发现25能使得模型的收敛速度最佳。
#
# learning_rate = 0.001
# 学习率决定了模型中权重更新的速度，在迭代更新权重值的过程中，设置过大的值容易使训练的模型跨过最优值，导致过拟合；设置过小会使梯度下降过程过慢。尝试了0.01,0.001和0.0001后，发现0.001能使得模型的收敛速度最佳。
#
# embedding_dim = 200
# 我尝试了100，200和300后，发现200能使得模型的收敛速度最佳。
#
# hidden_dim = 300
# 根据课程中讲到的内容，hidden_dim参数最好选择200到500之间的值，在尝试了200,300,400和500后，发现300能使得模型的收敛速度最佳。
#
# n_layers = 2
# 根据课程中讲到的内容，n_layers参数最好选择1-3之间的值，在尝试了2和3之后，发现2能使得模型的收敛速度最佳。

# ---
# # 检查点
#
# 通过运行上面的训练单元，你的模型已经以`trained_rnn`名字存储，如果你存储了你的notebook， **你可以在之后的任何时间来访问你的代码和结果**. 下述代码可以帮助你重载你的结果!

# In[18]:

"""
DON'T MODIFY ANYTHING IN THIS CELL
"""
import torch
import helper
import problem_unittests as tests

_, vocab_to_int, int_to_vocab, token_dict = helper.load_preprocess()
trained_rnn = helper.load_model('./save/trained_rnn')

# ## 生成电视剧剧本
# 你现在可以生成你的“假”电视剧剧本啦！
#
# ### 生成文字
# 你的神经网络会不断重复生成一个单词，直到生成满足你要求长度的剧本。使用 `generate` 函数来完成上述操作。首先，使用 `prime_id` 来生成word id，之后确定生成文本长度 `predict_len`。同时， topk 采样来引入文字选择的随机性!

# In[19]:

"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
import torch.nn.functional as F

def generate(rnn, prime_id, int_to_vocab, token_dict, pad_value, predict_len=100):
    """
    Generate text using the neural network
    :param decoder: The PyTorch Module that holds the trained neural network
    :param prime_id: The word id to start the first prediction
    :param int_to_vocab: Dict of word id keys to word values
    :param token_dict: Dict of puncuation tokens keys to puncuation values
    :param pad_value: The value used to pad a sequence
    :param predict_len: The length of text to generate
    :return: The generated text
    """
    rnn.eval()

    # create a sequence (batch_size=1) with the prime_id
    current_seq = np.full((1, sequence_length), pad_value)
    current_seq[-1][-1] = prime_id
    predicted = [int_to_vocab[prime_id]]

    for _ in range(predict_len):
        if train_on_gpu:
            current_seq = torch.LongTensor(current_seq).cuda()
        else:
            current_seq = torch.LongTensor(current_seq)

        # initialize the hidden state
        hidden = rnn.init_hidden(current_seq.size(0))

        # get the output of the rnn
        output, _ = rnn(current_seq, hidden)

        # get the next word probabilities
        p = F.softmax(output, dim=1).data
        if(train_on_gpu):
            p = p.cpu() # move to cpu

        # use top_k sampling to get the index of the next word
        top_k = 5
        p, top_i = p.topk(top_k)
        top_i = top_i.numpy().squeeze()

        # select the likely next word index with some element of randomness
        p = p.numpy().squeeze()
        word_i = np.random.choice(top_i, p=p/p.sum())

        # retrieve that word from the dictionary
        word = int_to_vocab[word_i]
        predicted.append(word)

        # the generated word becomes the next "current sequence" and the cycle can continue
        current_seq = np.roll(current_seq, -1, 1)
        current_seq[-1][-1] = word_i

    gen_sentences = ' '.join(predicted)

    # Replace punctuation tokens
    for key, token in token_dict.items():
        ending = ' ' if key in ['\n', '(', '"'] else ''
        gen_sentences = gen_sentences.replace(' ' + token.lower(), key)
    gen_sentences = gen_sentences.replace('\n ', '\n')
    gen_sentences = gen_sentences.replace('( ', '(')

    # return all the sentences
    return gen_sentences

# ### 生成一个新剧本
# 是时候生成一个剧本啦。设置`gen_length` 剧本长度，设置 `prime_word`为以下任意词来开始生成吧:
# - "jerry"
# - "elaine"
# - "george"
# - "kramer"
#
# 你可以把prime word 设置成 _任意 _ 单词, 但是使用名字开始会比较好(任何其他名字也是可以哒!)

# In[20]:

# run the cell multiple times to get different results!
gen_length = 400 # modify the length to your preference
prime_word = 'jerry' # name for starting the script

"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
pad_word = helper.SPECIAL_WORDS['PADDING']
generated_script = generate(trained_rnn, vocab_to_int[prime_word + ':'], int_to_vocab, token_dict, vocab_to_int[pad_word], gen_length)
print(generated_script)

# #### 存下你最爱的片段
#
# 一旦你发现一段有趣或者好玩的片段，就把它存下啦！

# In[21]:

# save script to a text file
f =  open("generated_script_1.txt","w")
f.write(generated_script)
f.close()

# # 这个电视剧剧本是无意义的
# 如果你的电视剧剧本不是很有逻辑也是ok的。下面是一个例子。
#
# ### 生成剧本案例
#
# >jerry: what about me?
# >
# >jerry: i don't have to wait.
# >
# >kramer:(to the sales table)
# >
# >elaine:(to jerry) hey, look at this, i'm a good doctor.
# >
# >newman:(to elaine) you think i have no idea of this...
# >
# >elaine: oh, you better take the phone, and he was a little nervous.
# >
# >kramer:(to the phone) hey, hey, jerry, i don't want to be a little bit.(to kramer and jerry) you can't.
# >
# >jerry: oh, yeah. i don't even know, i know.
# >
# >jerry:(to the phone) oh, i know.
# >
# >kramer:(laughing) you know...(to jerry) you don't know.
#
#
# 如果这个电视剧剧本毫无意义，那也没有关系。我们的训练文本不到一兆字节。为了获得更好的结果，你需要使用更小的词汇范围或是更多数据。幸运的是，我们的确拥有更多数据！在本项目开始之初我们也曾提过，这是[另一个数据集](https://www.kaggle.com/wcukierski/the-simpsons-by-the-data)的子集。我们并没有让你基于所有数据进行训练，因为这将耗费大量时间。然而，你可以随意使用这些数据训练你的神经网络。当然，是在完成本项目之后。
# # 提交项目
# 在提交项目时，请确保你在保存 notebook 前运行了所有的单元格代码。请将 notebook 文件保存为 "dlnd_tv_script_generation.ipynb"，并将它作为 HTML 文件保存在 "File" -> "Download as" 中。请将 "helper.py" 和 "problem_unittests.py" 文件一并打包成 zip 文件提交。

# In[ ]:


import array
import os
import pickle
import json
from os import listdir
import random
import numpy as np
import pandas as pd
from keras.models import load_model
from learner.evolution.sascorer import calculateScore
from learner.models import coeff_determination
from learner.seq2seq.sampler import latent_to_smiles
from rdkit import Chem
from rdkit.Chem import Descriptors, AllChem
from rdkit.DataStructs.cDataStructs import TanimotoSimilarity
from sklearn.externals import joblib
import matplotlib.pyplot as plt
import seaborn
from deap import algorithms, base, benchmarks, tools, creator
from eva import uniform, get_models

df = pd.read_csv('C:\PycharmProjects\ml.services\Source\sds_tools\learner\evolution\\000b0000-ac12-0242-6daf-08d61ef809b0-result.csv')
df = df.iloc[:,3:259]
df = df.loc[:, (df != 0).any(axis=0)]
BOUND_UP = list(df.max())
BOUND_LOW = list(df.min())
BOUND_LOW.append(0)
df = df.iloc[0:200]
print(df)

df = df.to_json(orient='values',path_or_buf='starters.json')

smi_to_lat_model = load_model('C:\PycharmProjects\ml.services\Source\sds_tools\learner\seq2seq\ENUM2CAN\smi2lat.h5')
latent_to_states_model = load_model('C:\PycharmProjects\ml.services\Source\sds_tools\learner\seq2seq\ENUM2CAN\lat2state.h5')
sample_model = load_model('C:\PycharmProjects\ml.services\Source\sds_tools\learner\seq2seq\ENUM2CAN\samplemodel.h5')
charset = pickle.load(open('C:\PycharmProjects\ml.services\Source\sds_tools\learner\seq2seq\ENUM2CAN\charset.obj','rb'))
char_to_int = pickle.load(open('C:\PycharmProjects\ml.services\Source\sds_tools\learner\seq2seq\ENUM2CAN\char_to_int.obj','rb'))
smiles_len = pickle.load(open('C:\PycharmProjects\ml.services\Source\sds_tools\learner\seq2seq\ENUM2CAN\smiles_len.obj','rb'))
int_to_char = pickle.load(open('C:\PycharmProjects\ml.services\Source\sds_tools\learner\seq2seq\ENUM2CAN\int_to_char.obj','rb'))

given = '0.0,19.511613845825195,0.0,0.0,101.84548950195312,0.0,39.129398345947266,0.0,77.08615112304688,0.0,49.40758514404297,0.0,103.42547607421875,40.400203704833984,0.0,47.51445007324219,54.97690200805664,0.0,40.9547233581543,35.57423782348633,67.10903930664062,0.0,0.0,0.0,51.20613479614258,0.0,0.0,0.0,56.0975456237793,40.890567779541016,111.85730743408203,0.0,0.0,106.42203521728516,47.93569564819336,0.0,0.0,0.0,74.60598754882812,0.0,57.73796463012695,0.0,111.13580322265625,129.27513122558594,49.06441116333008,0.0,83.46475219726562,0.0,85.15855407714844,71.87627410888672,0.0,58.48630142211914,52.50952911376953,0.0,73.61285400390625,0.0,60.828861236572266,72.5362319946289,121.24211120605469,42.407691955566406,34.95346450805664,89.34648132324219,55.49089431762695,94.58905029296875,0.0,172.74020385742188,54.723663330078125,0.0,102.90151977539062,85.12236785888672,0.0,0.0,72.23360443115234,0.0,0.0,0.0,74.85816192626953,128.95040893554688,82.17964172363281,0.0,94.42498016357422,155.02664184570312,43.157535552978516,96.37812805175781,0.0,0.0,0.0,42.752899169921875,50.5090217590332,0.0,43.621829986572266,135.06605529785156,40.02004623413086,0.0,63.804588317871094,92.11248016357422,0.0,48.265132904052734,0.0,0.0,121.40558624267578,49.56248092651367,63.2352180480957,61.311546325683594,44.794803619384766,49.87528991699219,0.0,0.0,0.0,92.6469955444336,0.0,184.18992614746094,139.27108764648438,0.0,218.90431213378906,65.92842102050781,6.008143901824951,84.60381317138672,0.0,58.405704498291016,22.1331729888916,77.85977935791016,0.0,41.90808868408203,107.62860107421875,27.135223388671875,0.0,0.0,0.0,0.0,0.0,108.96160125732422,59.50330352783203,118.14600372314453,50.14487075805664,0.0,79.09473419189453,64.2027587890625,87.27764129638672,0.0,0.0,75.57288360595703,0.0,48.861812591552734,0.0,51.18389129638672,64.14624786376953,92.549560546875,61.64143753051758,97.0406265258789,0.0,0.0,0.0,0.0,65.12122344970703,0.0,53.5643310546875,76.93698120117188,0.0,44.362091064453125,116.67655944824219,38.66350555419922,0.0,0.0,0.0,0.0,0.0,100.56484985351562,38.19453430175781,0.0,70.59272766113281,58.61124038696289,0.0,0.0,23.79081916809082,0.0,58.61326599121094,154.3920440673828,56.0794792175293,31.37140464782715,115.35797119140625,64.31336212158203,55.418785095214844,0.0,38.841766357421875,113.07113647460938,0.0,0.0,0.0,53.31327438354492,70.05583953857422,110.50933074951172,0.0,0.0,0.0,52.083133697509766,0.0,0.0,29.5716609954834,36.2513542175293,0.0,36.090911865234375,0.0,101.65526580810547,99.85685729980469,0.0,66.85289764404297,61.308746337890625,0.0,0.0,59.77785110473633,94.66217803955078,89.78416442871094,0.0,89.25480651855469,75.43046569824219,61.54755401611328,101.5020523071289,0.0,45.96051788330078,97.4427261352539,0.0,0.0,57.75127410888672,84.43328857421875,39.7234001159668,0.0,68.55062103271484,98.57598876953125,103.91691589355469,45.874420166015625,31.459779739379883,40.12807846069336,49.90414047241211,0.0,0.0,0.0,25.711496353149414,121.99552917480469,0.0,51.547080993652344,75.97683715820312,72.50204467773438,33.95379638671875,0.0,118.1387939453125,51.91447448730469,0.0,0.0,0.0,0.0,120.96520233154297,77.3481674194336,0.0,0.0,91.91608428955078'
given = given.split(',')
given = [float(x) for x in given]
given_test = [x for x in given if x != 0.0]

non_zero_index = []
for i in range(len(given)):
    if given[i] != 0.0:
        non_zero_index.append(i)

print(len(non_zero_index))

def problem_drug_likeness(individual):

    final_vector = [0.0 for x in range(256)]
    individual_latent_vector = [x for x in individual]
    counter = 0
    for i in range(256):
        if i in non_zero_index:
            final_vector[i] = individual_latent_vector[counter]
            counter += 1

    final_vector = np.reshape(final_vector,(1, 256))
    smiles = latent_to_smiles(
        charset,smiles_len,char_to_int,int_to_char,
        latent_to_states_model,sample_model,final_vector,
        type='2_layers'
    )
    molecule = Chem.MolFromSmiles(smiles)
    if molecule:
        try:
            logP = Descriptors.MolLogP(molecule)
            logP_score = (1.575 - logP)**2
            SA_score = calculateScore(molecule)
            print(Chem.MolToSmiles(molecule))
            bad_drug = logP_score + SA_score
            mol_fp = AllChem.GetMorganFingerprintAsBitVect(molecule, 2)
            ref = AllChem.GetMorganFingerprintAsBitVect(Chem.MolFromSmiles('c1ccccc1'),2)
            dissimilarity_to_benzene = (1 - TanimotoSimilarity(mol_fp,ref))
            print(dissimilarity_to_benzene)
            return bad_drug, dissimilarity_to_benzene
        except:
            return 9999,9999
    else:
        return 9999,9999

print(problem_drug_likeness(given_test))

plt.rc('text', usetex=True)
plt.rc('font', family='serif')
plt.rcParams['text.latex.preamble'] ='\\usepackage{libertine}\n\\usepackage[utf8]{inputenc}'

seaborn.set(style='whitegrid')
seaborn.set_context('notebook')

creator.create("FitnessMin", base.Fitness, weights=(-1.0, -1.0))
creator.create("Individual", list, fitness=creator.FitnessMin)

def initIndividual(icls, content):
    return icls(content)

def initPopulation(pcls, ind_init, filename):
    with open(filename, "r") as pop_file:
        contents = json.load(pop_file)
    return pcls(ind_init(c) for c in contents)

toolbox = base.Toolbox()

NDIM = 151

toolbox.register("attr_float", uniform, BOUND_LOW, BOUND_UP, NDIM)
toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.attr_float)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox.register("individual_guess", initIndividual, creator.Individual)
toolbox.register("population_guess", initPopulation, list, toolbox.individual_guess, "starters.json")
toolbox.register("evaluate", problem_drug_likeness)
toolbox.register("mate", tools.cxSimulatedBinaryBounded, low=BOUND_LOW, up=BOUND_UP, eta=20)
toolbox.register("mutate", tools.mutPolynomialBounded, low=BOUND_LOW, up=BOUND_UP, eta=5, indpb=0.05)
toolbox.register("select", tools.selNSGA2)

toolbox.pop_size = 50
mu = 50
toolbox.max_gen = 1000
toolbox.mut_prob = 0.3
toolbox.cross_prob = 0.7

def run_ea(toolbox, stats=None, verbose=False):
    pop = toolbox.population_guess()
    pop = toolbox.select(pop, len(pop))
    return algorithms.eaMuPlusLambda(pop, toolbox, mu=mu,
                                     lambda_=toolbox.pop_size,
                                     cxpb=toolbox.cross_prob,
                                     mutpb=toolbox.mut_prob,
                                     stats=stats,
                                     ngen=toolbox.max_gen,
                                     verbose=verbose)

res,_ = run_ea(toolbox)
fronts = tools.emo.sortLogNondominated(res, len(res))
df = pd.DataFrame(fronts)
df.to_csv('results.csv')


import numpy as np
import cv2
from tensorflow.keras.models import model_from_json
model_json_file = 'model.json'
model_weights_file = 'model_weights.h5'
with open(model_json_file, "r") as json_file:
    loaded_model_json = json_file.read()
    loaded_model = model_from_json(loaded_model_json)
    loaded_model.load_weights(model_weights_file)
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
cap = cv2.VideoCapture(0)
import copy

while True:

    ret, frame = cap.read()
    img = copy.deepcopy(frame)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, 1.3, 5)
    for (x, y, w, h) in faces:
        fc = gray[y:y + h, x:x + w]

        roi = cv2.resize(fc, (48, 48))
        pred = loaded_model.predict(roi[np.newaxis, :, :, np.newaxis])
        text_idx = np.argmax(pred)
        text_list = ['Angry', 'Disgust', 'Fear', 'Happy', 'Neutral', 'Sad', 'Surprise']
        if text_idx == 0:
            text = text_list[0]
        if text_idx == 1:
            text = text_list[1]
        elif text_idx == 2:
            text = text_list[2]
        elif text_idx == 3:
            text = text_list[3]
        elif text_idx == 4:
            text = text_list[4]
        elif text_idx == 5:
            text = text_list[5]
        elif text_idx == 6:
            text = text_list[6]
        cv2.putText(img, text, (x, y - 5),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.45, (255, 0, 255), 2)
        img = cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 2)

    cv2.imshow("frame", img)
    key = cv2.waitKey(1) & 0xFF
    if key == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()


from jax.config import config

config.update("jax_enable_x64", True)

import unittest
import numpy as np

from rdkit import Chem
from rdkit.Chem import AllChem
from ff.handlers import nonbonded, bonded
from ff.handlers.serialize import bin_to_str
from ff.handlers.deserialize import deserialize_handlers

def test_harmonic_bond():

    patterns = [
        ["[#6X4:1]-[#6X4:2]", 0.1, 0.2],
        ["[#6X4:1]-[#6X3:2]", 99.0, 99.0],
        ["[#6X4:1]-[#6X3:2]=[#8X1+0]", 99.0, 99.0],
        ["[#6X3:1]-[#6X3:2]", 99.0, 99.0],
        ["[#6X3:1]:[#6X3:2]", 99.0, 99.0],
        ["[#6X3:1]=[#6X3:2]", 99.0, 99.0],
        ["[#6:1]-[#7:2]", 0.1, 0.2],
        ["[#6X3:1]-[#7X3:2]", 99.0, 99.0],
        ["[#6X4:1]-[#7X3:2]-[#6X3]=[#8X1+0]", 99.0, 99.0],
        ["[#6X3:1](=[#8X1+0])-[#7X3:2]", 99.0, 99.0],
        ["[#6X3:1]-[#7X2:2]", 99.0, 99.0],
        ["[#6X3:1]:[#7X2,#7X3+1:2]", 99.0, 99.0],
        ["[#6X3:1]=[#7X2,#7X3+1:2]", 99.0, 99.0],
        ["[#6:1]-[#8:2]", 99.0, 99.0],
        ["[#6X3:1]-[#8X1-1:2]", 99.0, 99.0],
        ["[#6X4:1]-[#8X2H0:2]", 0.3, 0.4],
        ["[#6X3:1]-[#8X2:2]", 99.0, 99.0],
        ["[#6X3:1]-[#8X2H1:2]", 99.0, 99.0],
        ["[#6X3a:1]-[#8X2H0:2]", 99.0, 99.0],
        ["[#6X3:1](=[#8X1])-[#8X2H0:2]", 99.0, 99.0],
        ["[#6:1]=[#8X1+0,#8X2+1:2]", 99.0, 99.0],
        ["[#6X3:1](~[#8X1])~[#8X1:2]", 99.0, 99.0],
        ["[#6X3:1]~[#8X2+1:2]~[#6X3]", 99.0, 99.0],
        ["[#6X2:1]-[#6:2]", 99.0, 99.0],
        ["[#6X2:1]-[#6X4:2]", 99.0, 99.0],
        ["[#6X2:1]=[#6X3:2]", 99.0, 99.0],
        ["[#6:1]#[#7:2]", 99.0, 99.0],
        ["[#6X2:1]#[#6X2:2]", 99.0, 99.0],
        ["[#6X2:1]-[#8X2:2]", 99.0, 99.0],
        ["[#6X2:1]-[#7:2]", 99.0, 99.0],
        ["[#6X2:1]=[#7:2]", 99.0, 99.0],
        ["[#16:1]=[#6:2]", 99.0, 99.0],
        ["[#6X2:1]=[#16:2]", 99.0, 99.0],
        ["[#7:1]-[#7:2]", 99.0, 99.0],
        ["[#7X3:1]-[#7X2:2]", 99.0, 99.0],
        ["[#7X2:1]-[#7X2:2]", 99.0, 99.0],
        ["[#7:1]:[#7:2]", 99.0, 99.0],
        ["[#7:1]=[#7:2]", 99.0, 99.0],
        ["[#7+1:1]=[#7-1:2]", 99.0, 99.0],
        ["[#7:1]#[#7:2]", 99.0, 99.0],
        ["[#7:1]-[#8X2:2]", 99.0, 99.0],
        ["[#7:1]~[#8X1:2]", 99.0, 99.0],
        ["[#8X2:1]-[#8X2:2]", 99.0, 99.0],
        ["[#16:1]-[#6:2]", 99.0, 99.0],
        ["[#16:1]-[#1:2]", 99.0, 99.0],
        ["[#16:1]-[#16:2]", 99.0, 99.0],
        ["[#16:1]-[#9:2]", 99.0, 99.0],
        ["[#16:1]-[#17:2]", 99.0, 99.0],
        ["[#16:1]-[#35:2]", 99.0, 99.0],
        ["[#16:1]-[#53:2]", 99.0, 99.0],
        ["[#16X2,#16X1-1,#16X3+1:1]-[#6X4:2]", 99.0, 99.0],
        ["[#16X2,#16X1-1,#16X3+1:1]-[#6X3:2]", 99.0, 99.0],
        ["[#16X2:1]-[#7:2]", 99.0, 99.0],
        ["[#16X2:1]-[#8X2:2]", 99.0, 99.0],
        ["[#16X2:1]=[#8X1,#7X2:2]", 99.0, 99.0],
        ["[#16X4,#16X3!+1:1]-[#6:2]", 99.0, 99.0],
        ["[#16X4,#16X3:1]~[#7:2]", 99.0, 99.0],
        ["[#16X4,#16X3:1]-[#8X2:2]", 99.0, 99.0],
        ["[#16X4,#16X3:1]~[#8X1:2]", 99.0, 99.0],
        ["[#15:1]-[#1:2]", 99.0, 99.0],
        ["[#15:1]~[#6:2]", 99.0, 99.0],
        ["[#15:1]-[#7:2]", 99.0, 99.0],
        ["[#15:1]=[#7:2]", 99.0, 99.0],
        ["[#15:1]~[#8X2:2]", 99.0, 99.0],
        ["[#15:1]~[#8X1:2]", 99.0, 99.0],
        ["[#16:1]-[#15:2]", 99.0, 99.0],
        ["[#15:1]=[#16X1:2]", 99.0, 99.0],
        ["[#6:1]-[#9:2]", 99.0, 99.0],
        ["[#6X4:1]-[#9:2]", 0.6, 0.7],
        ["[#6:1]-[#17:2]", 99.0, 99.0],
        ["[#6X4:1]-[#17:2]", 99.0, 99.0],
        ["[#6:1]-[#35:2]", 99.0, 99.0],
        ["[#6X4:1]-[#35:2]", 99.0, 99.0],
        ["[#6:1]-[#53:2]", 99.0, 99.0],
        ["[#6X4:1]-[#53:2]", 99.0, 99.0],
        ["[#7:1]-[#9:2]", 99.0, 99.0],
        ["[#7:1]-[#17:2]", 99.0, 99.0],
        ["[#7:1]-[#35:2]", 99.0, 99.0],
        ["[#7:1]-[#53:2]", 99.0, 99.0],
        ["[#15:1]-[#9:2]", 99.0, 99.0],
        ["[#15:1]-[#17:2]", 99.0, 99.0],
        ["[#15:1]-[#35:2]", 99.0, 99.0],
        ["[#15:1]-[#53:2]", 99.0, 99.0],
        ["[#6X4:1]-[#1:2]", 99.0, 99.0],
        ["[#6X3:1]-[#1:2]", 99.0, 99.0],
        ["[#6X2:1]-[#1:2]", 99.0, 99.0],
        ["[#7:1]-[#1:2]", 99.0, 99.0],
        ["[#8:1]-[#1:2]", 99.0, 99.1],
    ]

    smirks = [x[0] for x in patterns]
    params = np.array([[x[1], x[2]] for x in patterns])
    props = None
    hbh = bonded.HarmonicBondHandler(smirks, params, None)

    obj = hbh.serialize()
    all_handlers = deserialize_handlers(bin_to_str(obj))

    assert len(all_handlers) == 1

    new_hbh = all_handlers[0]
    np.testing.assert_equal(new_hbh.smirks, hbh.smirks)
    np.testing.assert_equal(new_hbh.params, hbh.params)

    assert new_hbh.props == hbh.props

def test_proper_torsion():

    # proper torsions have a variadic number of terms

    patterns = [
        ["[*:1]-[#6X3:2]=[#6X3:3]-[*:4]", [[99.0, 99.0, 99.0]]],
        ["[*:1]-[#6X3:2]=[#6X3:3]-[#35:4]", [[99.0, 99.0, 99.0]]],
        ["[#9:1]-[#6X3:2]=[#6X3:3]-[#35:4]", [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]],
        ["[#35:1]-[#6X3:2]=[#6X3:3]-[#35:4]", [[7.0, 8.0, 9.0], [1.0, 3.0, 5.0], [4.0, 4.0, 4.0]]],
        ["[#9:1]-[#6X3:2]=[#6X3:3]-[#9:4]", [[7.0, 8.0, 9.0]]],
    ]

    smirks = [x[0] for x in patterns]
    params = [x[1] for x in patterns]
    props = None

    ph = bonded.ProperTorsionHandler(smirks, params, None)
    obj = ph.serialize()
    all_handlers = deserialize_handlers(bin_to_str(obj))

    assert len(all_handlers) == 1

    new_ph = all_handlers[0]
    np.testing.assert_equal(new_ph.smirks, ph.smirks)
    np.testing.assert_equal(new_ph.params, ph.params)
    assert new_ph.props == ph.props

def test_improper_torsion():

    patterns = [
        ["[*:1]~[#6X3:2](~[*:3])~[*:4]", 1.5341333333333333, 3.141592653589793, 2.0],
        ["[*:1]~[#6X3:2](~[#8X1:3])~[#8:4]", 99.0, 99.0, 99.0],
        ["[*:1]~[#7X3$(*~[#15,#16](!-[*])):2](~[*:3])~[*:4]", 99.0, 99.0, 99.0],
        ["[*:1]~[#7X3$(*~[#6X3]):2](~[*:3])~[*:4]", 1.3946666666666667, 3.141592653589793, 2.0],
        ["[*:1]~[#7X3$(*~[#7X2]):2](~[*:3])~[*:4]", 99.0, 99.0, 99.0],
        ["[*:1]~[#7X3$(*@1-[*]=,:[*][*]=,:[*]@1):2](~[*:3])~[*:4]", 99.0, 99.0, 99.0],
        ["[*:1]~[#6X3:2](=[#7X2,#7X3+1:3])~[#7:4]", 99.0, 99.0, 99.0],
    ]

    smirks = [x[0] for x in patterns]
    params = np.array([[x[1], x[2], x[3]] for x in patterns])
    imph = bonded.ImproperTorsionHandler(smirks, params, None)

    obj = imph.serialize()
    all_handlers = deserialize_handlers(bin_to_str(obj))

    assert len(all_handlers) == 1

    new_imph = all_handlers[0]
    np.testing.assert_equal(new_imph.smirks, imph.smirks)
    np.testing.assert_equal(new_imph.params, imph.params)
    assert new_imph.props == imph.props

def test_simple_charge_handler():

    patterns = [
        ["[#1:1]", 99.0],
        ["[#1:1]-[#6X4]", 99.0],
        ["[#1:1]-[#6X4]-[#7,#8,#9,#16,#17,#35]", 99.0],
        ["[#1:1]-[#6X4](-[#7,#8,#9,#16,#17,#35])-[#7,#8,#9,#16,#17,#35]", 99.0],
        ["[#1:1]-[#6X4](-[#7,#8,#9,#16,#17,#35])(-[#7,#8,#9,#16,#17,#35])-[#7,#8,#9,#16,#17,#35]", 99.0],
        ["[#1:1]-[#6X4]~[*+1,*+2]", 99.0],
        ["[#1:1]-[#6X3]", 99.0],
        ["[#1:1]-[#6X3]~[#7,#8,#9,#16,#17,#35]", 99.0],
        ["[#1:1]-[#6X3](~[#7,#8,#9,#16,#17,#35])~[#7,#8,#9,#16,#17,#35]", 99.0],
        ["[#1:1]-[#6X2]", 99.0],
        ["[#1:1]-[#7]", 99.0],
        ["[#1:1]-[#8]", 99.0],
        ["[#1:1]-[#16]", 99.0],
        ["[#6:1]", 0.7],
        ["[#6X2:1]", 99.0],
        ["[#6X4:1]", 0.1],
        ["[#8:1]", 99.0],
        ["[#8X2H0+0:1]", 0.5],
        ["[#8X2H1+0:1]", 99.0],
        ["[#7:1]", 0.3],
        ["[#16:1]", 99.0],
        ["[#15:1]", 99.0],
        ["[#9:1]", 1.0],
        ["[#17:1]", 99.0],
        ["[#35:1]", 99.0],
        ["[#53:1]", 99.0],
        ["[#3+1:1]", 99.0],
        ["[#11+1:1]", 99.0],
        ["[#19+1:1]", 99.0],
        ["[#37+1:1]", 99.0],
        ["[#55+1:1]", 99.0],
        ["[#9X0-1:1]", 99.0],
        ["[#17X0-1:1]", 99.0],
        ["[#35X0-1:1]", 99.0],
        ["[#53X0-1:1]", 99.0],
    ]

    smirks = [x[0] for x in patterns]
    params = np.array([x[1] for x in patterns])
    props = None

    sch = nonbonded.SimpleChargeHandler(smirks, params, props)
    obj = sch.serialize()
    all_handlers = deserialize_handlers(bin_to_str(obj))

    assert len(all_handlers) == 1

    new_sch = all_handlers[0]
    np.testing.assert_equal(new_sch.smirks, sch.smirks)
    np.testing.assert_equal(new_sch.params, sch.params)
    assert new_sch.props == sch.props

def test_gbsa_handler():

    patterns = [
        ["[*:1]", 99.0, 99.0],
        ["[#1:1]", 99.0, 99.0],
        ["[#1:1]~[#7]", 99.0, 99.0],
        ["[#6:1]", 0.1, 0.2],
        ["[#7:1]", 0.3, 0.4],
        ["[#8:1]", 0.5, 0.6],
        ["[#9:1]", 0.7, 0.8],
        ["[#14:1]", 99.0, 99.0],
        ["[#15:1]", 99.0, 99.0],
        ["[#16:1]", 99.0, 99.0],
        ["[#17:1]", 99.0, 99.0],
    ]

    props = {
        "solvent_dielectric": 78.3,  # matches OBC2,
        "solute_dielectric": 1.0,
        "probe_radius": 0.14,
        "surface_tension": 28.3919551,
        "dielectric_offset": 0.009,
        # GBOBC1
        "alpha": 0.8,
        "beta": 0.0,
        "gamma": 2.909125,
    }

    smirks = [x[0] for x in patterns]
    params = np.array([[x[1], x[2]] for x in patterns])

    gbh = nonbonded.GBSAHandler(smirks, params, props)

    obj = gbh.serialize()
    all_handlers = deserialize_handlers(bin_to_str(obj))

    assert len(all_handlers) == 1

    new_gbh = all_handlers[0]
    np.testing.assert_equal(new_gbh.smirks, gbh.smirks)
    np.testing.assert_equal(new_gbh.params, gbh.params)
    assert new_gbh.props == gbh.props

def test_am1bcc():

    smirks = []
    params = []
    props = None

    am1 = nonbonded.AM1BCCHandler(smirks, params, props)
    obj = am1.serialize()
    all_handlers = deserialize_handlers(bin_to_str(obj))

    am1 = all_handlers[0]
    np.testing.assert_equal(am1.smirks, am1.smirks)
    np.testing.assert_equal(am1.params, am1.params)
    assert am1.props == am1.props

def test_am1ccc():

    patterns = [
        ["[#6X4:1]-[#1:2]", 0.46323257920556493],
        ["[#6X3$(*=[#8,#16]):1]-[#6a:2]", 0.24281402370571598],
        ["[#6X3$(*=[#8,#16]):1]-[#8X1,#8X2:2]", 1.0620166764992722],
        ["[#6X3$(*=[#8,#16]):1]=[#8X1$(*=[#6X3]-[#8X2]):2]", 2.227759732057297],
        ["[#6X3$(*=[#8,#16]):1]=[#8X1,#8X2:2]", 2.8182928673804217],
        ["[#6a:1]-[#8X1,#8X2:2]", 0.5315976926761063],
        ["[#6a:1]-[#1:2]", 0.0],
        ["[#6a:1]:[#6a:2]", 0.0],
        ["[#6a:1]:[#6a:2]", 0.0],
        ["[#8X1,#8X2:1]-[#1:2]", -2.3692047944101415],
        ["[#16:1]-[#8:2]", 99.0],
    ]

    smirks = [x[0] for x in patterns]
    params = np.array([x[1] * np.sqrt(138.935456) for x in patterns])
    props = None

    am1h = nonbonded.AM1CCCHandler(smirks, params, props)
    obj = am1h.serialize()
    all_handlers = deserialize_handlers(bin_to_str(obj))

    assert len(all_handlers) == 1

    new_am1h = all_handlers[0]
    np.testing.assert_equal(new_am1h.smirks, am1h.smirks)
    np.testing.assert_equal(new_am1h.params, am1h.params)
    assert new_am1h.props == am1h.props

def test_lennard_jones_handler():

    patterns = [
        ["[#1:1]", 99.0, 999.0],
        ["[#1:1]-[#6X4]", 99.0, 999.0],
        ["[#1:1]-[#6X4]-[#7,#8,#9,#16,#17,#35]", 99.0, 999.0],
        ["[#1:1]-[#6X4](-[#7,#8,#9,#16,#17,#35])-[#7,#8,#9,#16,#17,#35]", 99.0, 999.0],
        ["[#1:1]-[#6X4](-[#7,#8,#9,#16,#17,#35])(-[#7,#8,#9,#16,#17,#35])-[#7,#8,#9,#16,#17,#35]", 99.0, 999.0],
        ["[#1:1]-[#6X4]~[*+1,*+2]", 99.0, 999.0],
        ["[#1:1]-[#6X3]", 99.0, 999.0],
        ["[#1:1]-[#6X3]~[#7,#8,#9,#16,#17,#35]", 99.0, 999.0],
        ["[#1:1]-[#6X3](~[#7,#8,#9,#16,#17,#35])~[#7,#8,#9,#16,#17,#35]", 99.0, 999.0],
        ["[#1:1]-[#6X2]", 99.0, 999.0],
        ["[#1:1]-[#7]", 99.0, 999.0],
        ["[#1:1]-[#8]", 99.0, 999.0],
        ["[#1:1]-[#16]", 99.0, 999.0],
        ["[#6:1]", 0.7, 0.8],
        ["[#6X2:1]", 99.0, 999.0],
        ["[#6X4:1]", 0.1, 0.2],
        ["[#8:1]", 99.0, 999.0],
        ["[#8X2H0+0:1]", 0.5, 0.6],
        ["[#8X2H1+0:1]", 99.0, 999.0],
        ["[#7:1]", 0.3, 0.4],
        ["[#16:1]", 99.0, 999.0],
        ["[#15:1]", 99.0, 999.0],
        ["[#9:1]", 1.0, 1.1],
        ["[#17:1]", 99.0, 999.0],
        ["[#35:1]", 99.0, 999.0],
        ["[#53:1]", 99.0, 999.0],
        ["[#3+1:1]", 99.0, 999.0],
        ["[#11+1:1]", 99.0, 999.0],
        ["[#19+1:1]", 99.0, 999.0],
        ["[#37+1:1]", 99.0, 999.0],
        ["[#55+1:1]", 99.0, 999.0],
        ["[#9X0-1:1]", 99.0, 999.0],
        ["[#17X0-1:1]", 99.0, 999.0],
        ["[#35X0-1:1]", 99.0, 999.0],
        ["[#53X0-1:1]", 99.0, 999.0],
    ]

    smirks = [x[0] for x in patterns]
    params = np.array([[x[1], x[2]] for x in patterns])
    props = None

    ljh = nonbonded.LennardJonesHandler(smirks, params, props)
    obj = ljh.serialize()
    all_handlers = deserialize_handlers(bin_to_str(obj))

    ljh = all_handlers[0]
    np.testing.assert_equal(ljh.smirks, ljh.smirks)
    np.testing.assert_equal(ljh.params, ljh.params)
    assert ljh.props == ljh.props